mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
test: expand hook and utility runtime coverage
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { alertChallengeVerificationFailed, getPublicationPreview, getPublicationType, getVotePreview } from '../challenge-utils';
|
||||
|
||||
const alertMock = vi.fn();
|
||||
const originalAlert = globalThis.alert;
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
||||
const logSpy = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
|
||||
describe('challenge-utils', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
globalThis.alert = alertMock;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
globalThis.alert = originalAlert;
|
||||
});
|
||||
|
||||
it('alerts with merged object challenge errors, reason, and resolved board path', () => {
|
||||
alertChallengeVerificationFailed(
|
||||
{
|
||||
challengeErrors: {
|
||||
captcha: 'invalid captcha',
|
||||
ignored: 42,
|
||||
},
|
||||
challengeSuccess: false,
|
||||
reason: 'try again later',
|
||||
} as never,
|
||||
{ subplebbitAddress: 'business-and-finance.bso' },
|
||||
);
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'Challenge Verification Failed:',
|
||||
expect.objectContaining({ challengeSuccess: false }),
|
||||
'Publication:',
|
||||
expect.objectContaining({ subplebbitAddress: 'business-and-finance.bso' }),
|
||||
);
|
||||
expect(alertMock).toHaveBeenCalledWith('Error from /biz/: invalid captcha try again later');
|
||||
});
|
||||
|
||||
it('alerts with joined array challenge errors and falls back to the raw board address when unmapped', () => {
|
||||
alertChallengeVerificationFailed(
|
||||
{
|
||||
challengeErrors: ['first error', 'second error'],
|
||||
challengeSuccess: false,
|
||||
} as never,
|
||||
{ subplebbitAddress: 'unknown-board.eth' },
|
||||
);
|
||||
|
||||
expect(alertMock).toHaveBeenCalledWith('Error from unknown-board.eth: first error second error');
|
||||
});
|
||||
|
||||
it('warns about invalid challenge error payloads and falls back to an unknown error', () => {
|
||||
alertChallengeVerificationFailed(
|
||||
{
|
||||
challengeErrors: 'bad-shape',
|
||||
challengeSuccess: false,
|
||||
} as never,
|
||||
{},
|
||||
);
|
||||
|
||||
expect(warnSpy).toHaveBeenCalledWith('challengeVerification.challengeErrors is not an object or array:', 'bad-shape');
|
||||
expect(alertMock).toHaveBeenCalledWith('Error from unknown board: unknown error');
|
||||
});
|
||||
|
||||
it('logs successful challenge verification instead of alerting', () => {
|
||||
alertChallengeVerificationFailed({ challengeSuccess: true } as never, { subplebbitAddress: 'business-and-finance.bso' });
|
||||
|
||||
expect(logSpy).toHaveBeenCalledWith('Challenge verification succeeded:', expect.objectContaining({ challengeSuccess: true }));
|
||||
expect(alertMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('classifies publication types and vote previews', () => {
|
||||
expect(getPublicationType(undefined)).toBeUndefined();
|
||||
expect(getPublicationType({ vote: 1 })).toBe('vote');
|
||||
expect(getPublicationType({ parentCid: 'reply-parent' })).toBe('reply');
|
||||
expect(getPublicationType({ commentCid: 'comment-cid' })).toBe('edit');
|
||||
expect(getPublicationType({ title: 'new thread' })).toBe('post');
|
||||
|
||||
expect(getVotePreview(undefined)).toBe('');
|
||||
expect(getVotePreview({ vote: 1 })).toBe(' +1');
|
||||
expect(getVotePreview({ vote: -1 })).toBe(' -1');
|
||||
});
|
||||
|
||||
it('builds publication previews from title, content, links, and truncation rules', () => {
|
||||
expect(getPublicationPreview(undefined)).toBe('');
|
||||
expect(getPublicationPreview({ link: 'https://example.com/only-link' })).toBe('https://example.com/only-link');
|
||||
expect(getPublicationPreview({ title: 'Announcement', content: 'Now live' })).toBe('Announcement: Now live');
|
||||
expect(
|
||||
getPublicationPreview({
|
||||
content: 'a'.repeat(80),
|
||||
}),
|
||||
).toBe(`${'a'.repeat(50)}...`);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,284 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const testState = vi.hoisted(() => ({
|
||||
cachedThumbnails: new Map<string, string>(),
|
||||
canEmbedHosts: new Set<string>(),
|
||||
capacitorHttpGetMock: vi.fn(),
|
||||
consoleErrorMock: vi.fn(),
|
||||
fetchMock: vi.fn(),
|
||||
isNativePlatform: false,
|
||||
localForageGetItemMock: vi.fn(),
|
||||
localForageSetItemMock: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock('@bitsocialhq/bitsocial-react-hooks/dist/lib/localforage-lru/index.js', () => ({
|
||||
default: {
|
||||
createInstance: () => ({
|
||||
getItem: (url: string) => testState.localForageGetItemMock(url),
|
||||
setItem: (url: string, thumbnail: string) => testState.localForageSetItemMock(url, thumbnail),
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('../../../components/embed', () => ({
|
||||
canEmbed: (url: URL) => testState.canEmbedHosts.has(url.hostname),
|
||||
}));
|
||||
|
||||
vi.mock('@capacitor/core', () => ({
|
||||
Capacitor: {
|
||||
isNativePlatform: () => testState.isNativePlatform,
|
||||
},
|
||||
CapacitorHttp: {
|
||||
get: (options: unknown) => testState.capacitorHttpGetMock(options),
|
||||
},
|
||||
}));
|
||||
|
||||
import { fetchWebpageThumbnailIfNeeded, getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getLinkMediaInfo, getMediaDimensions } from '../media-utils';
|
||||
|
||||
const clearMemoizedCache = (fn: unknown) => {
|
||||
const memoized = fn as { clear?: () => void };
|
||||
memoized.clear?.();
|
||||
};
|
||||
|
||||
const createFetchResponse = (html: string, ok = true) => {
|
||||
let sent = false;
|
||||
|
||||
return {
|
||||
body: {
|
||||
getReader: () => ({
|
||||
read: async () => {
|
||||
if (sent) {
|
||||
return { done: true, value: undefined };
|
||||
}
|
||||
|
||||
sent = true;
|
||||
return {
|
||||
done: false,
|
||||
value: new TextEncoder().encode(html),
|
||||
};
|
||||
},
|
||||
}),
|
||||
},
|
||||
ok,
|
||||
};
|
||||
};
|
||||
|
||||
describe('media-utils', () => {
|
||||
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
testState.cachedThumbnails = new Map<string, string>();
|
||||
testState.canEmbedHosts = new Set<string>();
|
||||
testState.isNativePlatform = false;
|
||||
testState.localForageGetItemMock.mockImplementation(async (url: string) => testState.cachedThumbnails.get(url) ?? null);
|
||||
testState.localForageSetItemMock.mockImplementation(async (url: string, thumbnail: string) => {
|
||||
testState.cachedThumbnails.set(url, thumbnail);
|
||||
});
|
||||
testState.fetchMock.mockReset();
|
||||
testState.capacitorHttpGetMock.mockReset();
|
||||
vi.stubGlobal('fetch', testState.fetchMock);
|
||||
clearMemoizedCache(getHasThumbnail);
|
||||
clearMemoizedCache(getLinkMediaInfo);
|
||||
clearMemoizedCache(getMediaDimensions);
|
||||
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(testState.consoleErrorMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleErrorSpy.mockRestore();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('maps media types to translated labels', () => {
|
||||
const t = (key: string) => `translated:${key}`;
|
||||
|
||||
expect(getDisplayMediaInfoType('image', t)).toBe('translated:image');
|
||||
expect(getDisplayMediaInfoType('gif', t)).toBe('translated:gif');
|
||||
expect(getDisplayMediaInfoType('animated gif', t)).toBe('translated:animated_gif');
|
||||
expect(getDisplayMediaInfoType('iframe', t)).toBe('translated:iframe');
|
||||
expect(getDisplayMediaInfoType('video', t)).toBe('translated:video');
|
||||
expect(getDisplayMediaInfoType('audio', t)).toBe('translated:audio');
|
||||
expect(getDisplayMediaInfoType('unknown', t)).toBe('translated:webpage');
|
||||
});
|
||||
|
||||
it('recognizes which media types expose thumbnails', () => {
|
||||
expect(getHasThumbnail(undefined, 'https://example.com/file.png')).toBe(false);
|
||||
expect(getHasThumbnail({ type: 'image', url: 'https://example.com/file.png' }, 'https://example.com/file.png')).toBe(true);
|
||||
expect(getHasThumbnail({ type: 'video', url: 'https://example.com/file.mp4' }, 'https://example.com/file.mp4')).toBe(true);
|
||||
expect(getHasThumbnail({ type: 'audio', url: 'https://example.com/file.mp3' }, 'https://example.com/file.mp3')).toBe(true);
|
||||
expect(getHasThumbnail({ type: 'gif', url: 'https://example.com/file.gif' }, 'https://example.com/file.gif')).toBe(true);
|
||||
expect(getHasThumbnail({ thumbnail: 'https://example.com/thumb.png', type: 'webpage', url: 'https://example.com' }, 'https://example.com')).toBe(true);
|
||||
expect(
|
||||
getHasThumbnail(
|
||||
{ patternThumbnailUrl: 'https://img.youtube.com/vi/abc/0.jpg', type: 'iframe', url: 'https://www.youtube.com/watch?v=abc' },
|
||||
'https://www.youtube.com/watch?v=abc',
|
||||
),
|
||||
).toBe(true);
|
||||
expect(getHasThumbnail({ type: 'iframe', url: 'https://example.com/embed' }, 'https://example.com/embed')).toBe(false);
|
||||
});
|
||||
|
||||
it('classifies direct media, embeds, imgbb pages, and unknown links', () => {
|
||||
testState.canEmbedHosts = new Set(['www.youtube.com', 'streamable.com']);
|
||||
|
||||
expect(getLinkMediaInfo('not-a-url')).toBeUndefined();
|
||||
expect(getLinkMediaInfo('https://example.com/_next/image?url=%2Fposter.png')).toMatchObject({ type: 'image' });
|
||||
expect(getLinkMediaInfo('https://ibb.co/abc123')).toEqual({
|
||||
thumbnail: 'https://i.ibb.co/abc123/thumbnail.jpg',
|
||||
type: 'webpage',
|
||||
url: 'https://ibb.co/abc123',
|
||||
});
|
||||
expect(getLinkMediaInfo('https://example.com/file.gif')).toMatchObject({ type: 'gif' });
|
||||
expect(getLinkMediaInfo('https://example.com/file.png')).toMatchObject({ type: 'image' });
|
||||
expect(getLinkMediaInfo('https://example.com/file.mp4')).toMatchObject({ type: 'video' });
|
||||
expect(getLinkMediaInfo('https://example.com/file.mp3')).toMatchObject({ type: 'audio' });
|
||||
expect(getLinkMediaInfo('https://example.com/path')).toMatchObject({ type: 'webpage' });
|
||||
expect(getLinkMediaInfo('https://www.youtube.com/watch?v=abc123')).toEqual({
|
||||
patternThumbnailUrl: 'https://img.youtube.com/vi/abc123/0.jpg',
|
||||
type: 'iframe',
|
||||
url: 'https://www.youtube.com/watch?v=abc123',
|
||||
});
|
||||
expect(getLinkMediaInfo('https://streamable.com/clip123')).toEqual({
|
||||
patternThumbnailUrl: 'https://cdn-cf-east.streamable.com/image/clip123.jpg',
|
||||
type: 'iframe',
|
||||
url: 'https://streamable.com/clip123',
|
||||
});
|
||||
expect(getLinkMediaInfo('https://yt.example/watch?v=yt123')).toEqual({
|
||||
patternThumbnailUrl: 'https://img.youtube.com/vi/yt123/0.jpg',
|
||||
type: 'iframe',
|
||||
url: 'https://yt.example/watch?v=yt123',
|
||||
});
|
||||
});
|
||||
|
||||
it('builds comment media info and strips thumbnails for blacklisted domains', () => {
|
||||
testState.canEmbedHosts = new Set(['www.youtube.com']);
|
||||
|
||||
expect(getCommentMediaInfo('', '', 0, 0)).toBeUndefined();
|
||||
expect(getCommentMediaInfo('https://example.com/file.png', 'https://example.com/thumb.png', 320, 240)).toEqual({
|
||||
linkHeight: 240,
|
||||
linkWidth: 320,
|
||||
thumbnail: 'https://example.com/thumb.png',
|
||||
type: 'image',
|
||||
url: 'https://example.com/file.png',
|
||||
});
|
||||
expect(getCommentMediaInfo('https://x.com/post/123', 'https://example.com/thumb.png', 100, 50)).toEqual({
|
||||
linkHeight: 50,
|
||||
linkWidth: 100,
|
||||
patternThumbnailUrl: undefined,
|
||||
thumbnail: undefined,
|
||||
type: 'webpage',
|
||||
url: 'https://x.com/post/123',
|
||||
});
|
||||
expect(getCommentMediaInfo('https://www.youtube.com/watch?v=abc123', '', 800, 450)).toEqual({
|
||||
linkHeight: 450,
|
||||
linkWidth: 800,
|
||||
patternThumbnailUrl: 'https://img.youtube.com/vi/abc123/0.jpg',
|
||||
thumbnail: undefined,
|
||||
type: 'iframe',
|
||||
url: 'https://www.youtube.com/watch?v=abc123',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns expected media dimensions for embeds, audio, and sized media', () => {
|
||||
testState.canEmbedHosts = new Set(['www.youtube.com', 'www.reddit.com']);
|
||||
|
||||
expect(getMediaDimensions({ type: 'iframe', url: 'https://www.youtube.com/watch?v=abc123' })).toBe('800x450');
|
||||
expect(getMediaDimensions({ type: 'iframe', url: 'https://www.reddit.com/r/example/comments/abc123' })).toBe('500x520');
|
||||
expect(getMediaDimensions({ type: 'audio', url: 'https://example.com/file.mp3' })).toBe('700x240');
|
||||
expect(getMediaDimensions({ linkHeight: 480, linkWidth: 640, type: 'image', url: 'https://example.com/file.png' })).toBe('640x480');
|
||||
expect(getMediaDimensions({ linkHeight: 720, linkWidth: 1280, type: 'video', url: 'https://example.com/file.mp4' })).toBe('1280x720');
|
||||
expect(getMediaDimensions({ type: 'webpage', url: 'https://example.com' })).toBe('');
|
||||
});
|
||||
|
||||
it('uses cached webpage thumbnails before fetching the network', async () => {
|
||||
testState.cachedThumbnails.set('https://example.com/cached', 'https://cdn.example/cached.png');
|
||||
|
||||
const result = await fetchWebpageThumbnailIfNeeded({
|
||||
type: 'webpage',
|
||||
url: 'https://example.com/cached',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
thumbnail: 'https://cdn.example/cached.png',
|
||||
type: 'webpage',
|
||||
url: 'https://example.com/cached',
|
||||
});
|
||||
expect(testState.fetchMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('fetches og:image thumbnails on web and persists them', async () => {
|
||||
testState.fetchMock.mockResolvedValue(
|
||||
createFetchResponse(`
|
||||
<html>
|
||||
<head><meta property="og:image" content="https://cdn.example/og.png" /></head>
|
||||
<body></body>
|
||||
</html>
|
||||
`),
|
||||
);
|
||||
|
||||
const result = await fetchWebpageThumbnailIfNeeded({
|
||||
type: 'webpage',
|
||||
url: 'https://example.com/og-page',
|
||||
});
|
||||
|
||||
expect(testState.fetchMock).toHaveBeenCalledWith('https://example.com/og-page', expect.objectContaining({ headers: { Accept: 'text/html' } }));
|
||||
expect(testState.localForageSetItemMock).toHaveBeenCalledWith('https://example.com/og-page', 'https://cdn.example/og.png');
|
||||
expect(result).toEqual({
|
||||
thumbnail: 'https://cdn.example/og.png',
|
||||
type: 'webpage',
|
||||
url: 'https://example.com/og-page',
|
||||
});
|
||||
});
|
||||
|
||||
it('fetches first-image thumbnails on native and resolves relative urls', async () => {
|
||||
testState.isNativePlatform = true;
|
||||
testState.capacitorHttpGetMock.mockResolvedValue({
|
||||
data: `
|
||||
<html>
|
||||
<body><img src="/poster.png" /></body>
|
||||
</html>
|
||||
`,
|
||||
});
|
||||
|
||||
const result = await fetchWebpageThumbnailIfNeeded({
|
||||
type: 'webpage',
|
||||
url: 'https://example.com/native-page',
|
||||
});
|
||||
|
||||
expect(testState.capacitorHttpGetMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
connectTimeout: 5000,
|
||||
headers: { Accept: 'text/html', Range: 'bytes=0-1048575' },
|
||||
readTimeout: 5000,
|
||||
responseType: 'text',
|
||||
url: 'https://example.com/native-page',
|
||||
}),
|
||||
);
|
||||
expect(result).toEqual({
|
||||
thumbnail: 'https://example.com/poster.png',
|
||||
type: 'webpage',
|
||||
url: 'https://example.com/native-page',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns unchanged media when thumbnails already exist or fetching fails', async () => {
|
||||
const existing = {
|
||||
thumbnail: 'https://cdn.example/existing.png',
|
||||
type: 'webpage',
|
||||
url: 'https://example.com/ready',
|
||||
} as const;
|
||||
expect(await fetchWebpageThumbnailIfNeeded(existing)).toBe(existing);
|
||||
|
||||
testState.fetchMock.mockResolvedValue(createFetchResponse('<html></html>', false));
|
||||
const result = await fetchWebpageThumbnailIfNeeded({
|
||||
type: 'webpage',
|
||||
url: 'https://example.com/failure',
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
thumbnail: undefined,
|
||||
type: 'webpage',
|
||||
url: 'https://example.com/failure',
|
||||
});
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,123 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
|
||||
const testState = vi.hoisted(() => ({
|
||||
consoleErrorMock: vi.fn(),
|
||||
subplebbits: {} as Record<string, { roles?: Record<string, { role?: string }> }>,
|
||||
}));
|
||||
|
||||
vi.mock('@bitsocialhq/bitsocial-react-hooks/dist/stores/subplebbits', () => ({
|
||||
default: {
|
||||
getState: () => ({
|
||||
subplebbits: testState.subplebbits,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
import { commentMatchesPattern, displayNameMatchesPattern, matchesPattern, parsePattern, userHasRole, userIdMatchesPattern } from '../pattern-utils';
|
||||
|
||||
describe('pattern-utils', () => {
|
||||
let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
testState.subplebbits = {
|
||||
'music-posting.eth': {
|
||||
roles: {
|
||||
'author-1': { role: 'moderator' },
|
||||
'author-2': { role: 'owner' },
|
||||
},
|
||||
},
|
||||
};
|
||||
consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(testState.consoleErrorMock);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
consoleErrorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('matches whole words, exact phrases, regexes, wildcards, and logical operators', () => {
|
||||
expect(matchesPattern('That feel when the girlfriend texts back', 'feel')).toBe(true);
|
||||
expect(matchesPattern('That feel when the girlfriend texts back', 'feels')).toBe(false);
|
||||
expect(matchesPattern('That feel when the girlfriend texts back', 'feel girlfriend')).toBe(true);
|
||||
expect(matchesPattern('That feel when the girlfriend texts back', 'girlfriend|boyfriend feel')).toBe(true);
|
||||
expect(matchesPattern('That feel when the girlfriend texts back', '"feel when the girlfriend"')).toBe(true);
|
||||
expect(matchesPattern('That feeling stays forever', 'feel*')).toBe(true);
|
||||
expect(matchesPattern('MIXED Case Example', '/mixed case example/i')).toBe(true);
|
||||
});
|
||||
|
||||
it('falls back to a simple include when pattern parsing throws', () => {
|
||||
expect(matchesPattern('the broken marker foo(', 'foo(')).toBe(true);
|
||||
expect(consoleErrorSpy).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('matches user ids through full and short addresses', () => {
|
||||
const comment = {
|
||||
author: {
|
||||
address: '12D3KooWabcdef',
|
||||
shortAddress: '12D3KooWabc',
|
||||
},
|
||||
};
|
||||
|
||||
expect(userIdMatchesPattern(comment as never, 'abcdef')).toBe(true);
|
||||
expect(userIdMatchesPattern(comment as never, '12D3KooWabc')).toBe(true);
|
||||
expect(userIdMatchesPattern(comment as never, 'missing')).toBe(false);
|
||||
});
|
||||
|
||||
it('matches display names case-insensitively and treats anonymous as undefined display names', () => {
|
||||
expect(displayNameMatchesPattern({ author: { displayName: 'Alice' } } as never, 'alice')).toBe(true);
|
||||
expect(displayNameMatchesPattern({ author: {} } as never, 'anonymous')).toBe(true);
|
||||
expect(displayNameMatchesPattern({ author: { displayName: 'Bob' } } as never, 'anonymous')).toBe(false);
|
||||
});
|
||||
|
||||
it('matches roles with moderator aliases and rejects missing role metadata', () => {
|
||||
const modComment = {
|
||||
author: { address: 'author-1' },
|
||||
subplebbitAddress: 'music-posting.eth',
|
||||
};
|
||||
const ownerComment = {
|
||||
author: { address: 'author-2' },
|
||||
subplebbitAddress: 'music-posting.eth',
|
||||
};
|
||||
|
||||
expect(userHasRole(modComment as never, 'moderator')).toBe(true);
|
||||
expect(userHasRole(modComment as never, 'mod')).toBe(true);
|
||||
expect(userHasRole(ownerComment as never, 'owner')).toBe(true);
|
||||
expect(userHasRole(ownerComment as never, 'admin')).toBe(false);
|
||||
expect(userHasRole({ author: { address: 'missing' }, subplebbitAddress: 'unknown.eth' } as never, 'moderator')).toBe(false);
|
||||
});
|
||||
|
||||
it('parses mixed special filters and content filters', () => {
|
||||
expect(parsePattern('#abc ##Alice #!#mod exact phrase')).toEqual({
|
||||
contentFilter: 'exact phrase',
|
||||
specialFilters: [
|
||||
{ type: 'userId', value: 'abc' },
|
||||
{ type: 'displayName', value: 'Alice' },
|
||||
{ type: 'role', value: 'mod' },
|
||||
],
|
||||
});
|
||||
expect(parsePattern('')).toEqual({
|
||||
contentFilter: '',
|
||||
specialFilters: [],
|
||||
});
|
||||
});
|
||||
|
||||
it('matches comments against combined special filters and content filters', () => {
|
||||
const comment = {
|
||||
author: {
|
||||
address: 'author-1',
|
||||
displayName: 'Alice',
|
||||
shortAddress: 'auth1',
|
||||
},
|
||||
content: 'That feel when the girlfriend texts back',
|
||||
subplebbitAddress: 'music-posting.eth',
|
||||
title: 'TFW',
|
||||
};
|
||||
|
||||
expect(commentMatchesPattern(comment as never, '#auth1 ##Alice #!#moderator girlfriend')).toBe(true);
|
||||
expect(commentMatchesPattern(comment as never, '#auth1 ##Bob #!#moderator girlfriend')).toBe(false);
|
||||
expect(commentMatchesPattern(comment as never, '#auth1')).toBe(true);
|
||||
expect(commentMatchesPattern(comment as never, '##Alice')).toBe(true);
|
||||
expect(commentMatchesPattern(comment as never, '#!#mod')).toBe(true);
|
||||
expect(commentMatchesPattern(comment as never, 'tfw girlfriend')).toBe(true);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,55 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { findPostPageInFeed, findPostPageInLoadedBoardFeeds, isBoardFeedOptions } from '../post-page-resolution';
|
||||
|
||||
describe('post-page-resolution', () => {
|
||||
it('finds the GUI page for a post inside a feed and rejects invalid inputs', () => {
|
||||
const feed = [{ cid: 'post-1' }, { cid: 'post-2' }, { cid: 'post-3' }, { cid: 'post-4' }];
|
||||
|
||||
expect(findPostPageInFeed(feed, 'post-3', 2)).toBe(2);
|
||||
expect(findPostPageInFeed(feed, 'missing-post', 2)).toBeUndefined();
|
||||
expect(findPostPageInFeed(feed, 'post-1', 0)).toBeUndefined();
|
||||
expect(findPostPageInFeed(feed, '', 2)).toBeUndefined();
|
||||
});
|
||||
|
||||
it('only accepts strict board feed options for the active single-board feed', () => {
|
||||
const baseOptions = {
|
||||
sortType: 'active',
|
||||
subplebbitAddresses: ['music.eth'],
|
||||
};
|
||||
|
||||
expect(isBoardFeedOptions(baseOptions, 'music.eth')).toBe(true);
|
||||
expect(isBoardFeedOptions({ ...baseOptions, sortType: 'new' }, 'music.eth')).toBe(false);
|
||||
expect(isBoardFeedOptions({ ...baseOptions, subplebbitAddresses: ['music.eth', 'tech.eth'] }, 'music.eth')).toBe(false);
|
||||
expect(isBoardFeedOptions({ ...baseOptions, subplebbitAddresses: ['tech.eth'] }, 'music.eth')).toBe(false);
|
||||
expect(isBoardFeedOptions({ ...baseOptions, filter: { title: 'test' } }, 'music.eth')).toBe(false);
|
||||
expect(isBoardFeedOptions({ ...baseOptions, newerThan: 3600 }, 'music.eth')).toBe(false);
|
||||
expect(isBoardFeedOptions({ ...baseOptions, modQueue: true }, 'music.eth')).toBe(false);
|
||||
expect(isBoardFeedOptions({ ...baseOptions, accountComments: true }, 'music.eth')).toBe(false);
|
||||
});
|
||||
|
||||
it('resolves a post page from matching loaded board feeds and ignores unrelated feeds', () => {
|
||||
const feedsOptions = {
|
||||
allFeed: {
|
||||
sortType: 'active',
|
||||
subplebbitAddresses: ['all.eth'],
|
||||
},
|
||||
catalogFilterFeed: {
|
||||
filter: { title: 'match' },
|
||||
sortType: 'active',
|
||||
subplebbitAddresses: ['music.eth'],
|
||||
},
|
||||
boardFeed: {
|
||||
sortType: 'active',
|
||||
subplebbitAddresses: ['music.eth'],
|
||||
},
|
||||
};
|
||||
const loadedFeeds = {
|
||||
allFeed: [{ cid: 'post-4' }],
|
||||
boardFeed: [{ cid: 'post-1' }, { cid: 'post-2' }, { cid: 'post-3' }, { cid: 'post-4' }],
|
||||
};
|
||||
|
||||
expect(findPostPageInLoadedBoardFeeds(feedsOptions, loadedFeeds, 'music.eth', 'post-4', 2)).toBe(2);
|
||||
expect(findPostPageInLoadedBoardFeeds(feedsOptions, loadedFeeds, 'music.eth', 'missing-post', 2)).toBeUndefined();
|
||||
expect(findPostPageInLoadedBoardFeeds(feedsOptions, { boardFeed: 'not-an-array' as never }, 'music.eth', 'post-4', 2)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user