Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port upstream's hashtag handling to glitch-soc #2391

Merged
214 changes: 214 additions & 0 deletions app/javascript/flavours/glitch/components/__tests__/hashtag_bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
import { fromJS } from 'immutable';

import type { StatusLike } from '../hashtag_bar';
import { computeHashtagBarForStatus } from '../hashtag_bar';

function createStatus(
content: string,
hashtags: string[],
hasMedia = false,
spoilerText?: string,
) {
return fromJS({
tags: hashtags.map((name) => ({ name })),
contentHtml: content,
media_attachments: hasMedia ? ['fakeMedia'] : [],
spoiler_text: spoilerText,
}) as unknown as StatusLike; // need to force the type here, as it is not properly defined
}

describe('computeHashtagBarForStatus', () => {
it('does nothing when there are no tags', () => {
const status = createStatus('<p>Simple text</p>', []);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Simple text</p>"`,
);
});

it('displays out of band hashtags in the bar', () => {
const status = createStatus(
'<p>Simple text <a href="test">#hashtag</a></p>',
['hashtag', 'test'],
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual(['test']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Simple text <a href="test">#hashtag</a></p>"`,
);
});

it('does not truncate the contents when the last child is a text node', () => {
const status = createStatus(
'this is a #<a class="zrl" href="https://example.com/search?tag=test">test</a>. Some more text',
['test'],
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"this is a #<a class="zrl" href="https://example.com/search?tag=test">test</a>. Some more text"`,
);
});

it('extract tags from the last line', () => {
const status = createStatus(
'<p>Simple text</p><p><a href="test">#hashtag</a></p>',
['hashtag'],
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual(['hashtag']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Simple text</p>"`,
);
});

it('does not include tags from content', () => {
const status = createStatus(
'<p>Simple text with a <a href="test">#hashtag</a></p><p><a href="test">#hashtag</a></p>',
['hashtag'],
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Simple text with a <a href="test">#hashtag</a></p>"`,
);
});

it('works with one line status and hashtags', () => {
const status = createStatus(
'<p><a href="test">#test</a>. And another <a href="test">#hashtag</a></p>',
['hashtag', 'test'],
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p><a href="test">#test</a>. And another <a href="test">#hashtag</a></p>"`,
);
});

it('de-duplicate accentuated characters with case differences', () => {
const status = createStatus(
'<p>Text</p><p><a href="test">#éaa</a> <a href="test">#Éaa</a></p>',
['éaa'],
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual(['Éaa']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Text</p>"`,
);
});

it('handles server-side normalized tags with accentuated characters', () => {
const status = createStatus(
'<p>Text</p><p><a href="test">#éaa</a> <a href="test">#Éaa</a></p>',
['eaa'], // The server may normalize the hashtags in the `tags` attribute
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual(['Éaa']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Text</p>"`,
);
});

it('does not display in bar a hashtag in content with a case difference', () => {
const status = createStatus(
'<p>Text <a href="test">#Éaa</a></p><p><a href="test">#éaa</a></p>',
['éaa'],
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>Text <a href="test">#Éaa</a></p>"`,
);
});

it('does not modify a status with a line of hashtags only', () => {
const status = createStatus(
'<p><a href="test">#test</a> <a href="test">#hashtag</a></p>',
['test', 'hashtag'],
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p><a href="test">#test</a> <a href="test">#hashtag</a></p>"`,
);
});

it('puts the hashtags in the bar if a status content has hashtags in the only line and has a media', () => {
const status = createStatus(
'<p>This is my content! <a href="test">#hashtag</a></p>',
['hashtag'],
true,
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p>This is my content! <a href="test">#hashtag</a></p>"`,
);
});

it('puts the hashtags in the bar if a status content is only hashtags and has a media', () => {
const status = createStatus(
'<p><a href="test">#test</a> <a href="test">#hashtag</a></p>',
['test', 'hashtag'],
true,
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual(['test', 'hashtag']);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(`""`);
});

it('does not use the hashtag bar if the status content is only hashtags, has a CW and a media', () => {
const status = createStatus(
'<p><a href="test">#test</a> <a href="test">#hashtag</a></p>',
['test', 'hashtag'],
true,
'My CW text',
);

const { hashtagsInBar, statusContentProps } =
computeHashtagBarForStatus(status);

expect(hashtagsInBar).toEqual([]);
expect(statusContentProps.statusContent).toMatchInlineSnapshot(
`"<p><a href="test">#test</a> <a href="test">#hashtag</a></p>"`,
);
});
});
Loading
Loading