mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(rules): keep board selector limited to default boards
This commit is contained in:
@@ -0,0 +1,124 @@
|
|||||||
|
import * as React from 'react';
|
||||||
|
import { createElement } from 'react';
|
||||||
|
import { createRoot, type Root } from 'react-dom/client';
|
||||||
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||||
|
import Rules from '../rules';
|
||||||
|
|
||||||
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||||
|
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
|
||||||
|
|
||||||
|
const testState = vi.hoisted(() => ({
|
||||||
|
boardIdentifier: undefined as string | undefined,
|
||||||
|
communities: {} as Record<string, { rules?: string[]; shortAddress?: string; state?: string; title?: string }>,
|
||||||
|
directories: [
|
||||||
|
{ address: 'anime-posting.eth', title: '/a/ - Anime & Manga' },
|
||||||
|
{ address: 'random-posting.eth', title: '/b/ - Random' },
|
||||||
|
] as Array<{ address: string; title?: string }>,
|
||||||
|
navigateMock: vi.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('react-i18next', () => ({
|
||||||
|
useTranslation: () => ({
|
||||||
|
t: (key: string) => key,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('react-router-dom', async () => {
|
||||||
|
const actual = await vi.importActual<typeof import('react-router-dom')>('react-router-dom');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useNavigate: () => testState.navigateMock,
|
||||||
|
useParams: () => ({
|
||||||
|
boardIdentifier: testState.boardIdentifier,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('@bitsocialnet/bitsocial-react-hooks', () => ({
|
||||||
|
useCommunity: ({ communityAddress }: { communityAddress?: string }) => (communityAddress ? testState.communities[communityAddress] : undefined),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../hooks/use-directories', async () => {
|
||||||
|
const actual = await vi.importActual<typeof import('../../../hooks/use-directories')>('../../../hooks/use-directories');
|
||||||
|
return {
|
||||||
|
...actual,
|
||||||
|
useDirectories: () => testState.directories,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
vi.mock('../../home', () => ({
|
||||||
|
Footer: () => createElement('div', { 'data-testid': 'footer' }, 'footer'),
|
||||||
|
HomeLogo: () => createElement('div', { 'data-testid': 'home-logo' }, 'home-logo'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock('../../../components/markdown', () => ({
|
||||||
|
default: ({ content }: { content: string }) => createElement('div', { 'data-testid': 'markdown' }, content),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let container: HTMLDivElement;
|
||||||
|
let root: Root;
|
||||||
|
|
||||||
|
const renderRules = async () => {
|
||||||
|
await act(async () => {
|
||||||
|
root.render(createElement(Rules));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('Rules', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
testState.boardIdentifier = undefined;
|
||||||
|
testState.communities = {};
|
||||||
|
testState.directories = [
|
||||||
|
{ address: 'anime-posting.eth', title: '/a/ - Anime & Manga' },
|
||||||
|
{ address: 'random-posting.eth', title: '/b/ - Random' },
|
||||||
|
];
|
||||||
|
window.scrollTo = vi.fn();
|
||||||
|
|
||||||
|
container = document.createElement('div');
|
||||||
|
document.body.appendChild(container);
|
||||||
|
root = createRoot(container);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
act(() => root.unmount());
|
||||||
|
container.remove();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps custom-address routes out of the default board select', async () => {
|
||||||
|
testState.boardIdentifier = 'custom-board.eth';
|
||||||
|
testState.communities = {
|
||||||
|
'custom-board.eth': {
|
||||||
|
rules: ['No custom options in the select.'],
|
||||||
|
shortAddress: 'custom-board.eth',
|
||||||
|
state: 'succeeded',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await renderRules();
|
||||||
|
|
||||||
|
const select = container.querySelector('select');
|
||||||
|
expect(select).toBeTruthy();
|
||||||
|
expect(select?.value).toBe('');
|
||||||
|
expect(Array.from(select?.options ?? []).map((option) => option.value)).toEqual(['', 'anime-posting.eth', 'random-posting.eth']);
|
||||||
|
expect(container.textContent).toContain('Rules for: custom-board.eth');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('keeps the canonical default board selected for known directories', async () => {
|
||||||
|
testState.boardIdentifier = 'a';
|
||||||
|
testState.communities = {
|
||||||
|
'anime-posting.eth': {
|
||||||
|
rules: ['Stay on topic.'],
|
||||||
|
state: 'succeeded',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await renderRules();
|
||||||
|
|
||||||
|
const select = container.querySelector('select');
|
||||||
|
expect(select).toBeTruthy();
|
||||||
|
expect(select?.value).toBe('anime-posting.eth');
|
||||||
|
expect(Array.from(select?.options ?? []).map((option) => option.value)).toEqual(['', 'anime-posting.eth', 'random-posting.eth']);
|
||||||
|
expect(container.textContent).toContain('Rules for: /a/ - Anime & Manga');
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -2,7 +2,7 @@ import { useEffect, useState, FormEvent } from 'react';
|
|||||||
import { useNavigate, useParams } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import { useCommunity } from '@bitsocialnet/bitsocial-react-hooks';
|
import { useCommunity } from '@bitsocialnet/bitsocial-react-hooks';
|
||||||
import { Footer, HomeLogo } from '../home';
|
import { Footer, HomeLogo } from '../home';
|
||||||
import { useDirectories, DirectoryCommunity } from '../../hooks/use-directories';
|
import { useDirectories, DirectoryCommunity, findDirectoryByAddress } from '../../hooks/use-directories';
|
||||||
import { getSubplebbitAddress, getBoardPath } from '../../lib/utils/route-utils';
|
import { getSubplebbitAddress, getBoardPath } from '../../lib/utils/route-utils';
|
||||||
import Markdown from '../../components/markdown';
|
import Markdown from '../../components/markdown';
|
||||||
import styles from './rules.module.css';
|
import styles from './rules.module.css';
|
||||||
@@ -103,6 +103,8 @@ const BoardSelector = ({
|
|||||||
onSelect: (address: string) => void;
|
onSelect: (address: string) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [customAddress, setCustomAddress] = useState('');
|
const [customAddress, setCustomAddress] = useState('');
|
||||||
|
const selectedDefaultBoard = findDirectoryByAddress(directories, selectedAddress);
|
||||||
|
const selectedBoardValue = selectedDefaultBoard?.address ?? '';
|
||||||
|
|
||||||
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
const value = e.target.value;
|
const value = e.target.value;
|
||||||
@@ -129,7 +131,7 @@ const BoardSelector = ({
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.boxContent}>
|
<div className={styles.boxContent}>
|
||||||
<div className={styles.selectorRow}>
|
<div className={styles.selectorRow}>
|
||||||
<select value={selectedAddress} onChange={handleSelectChange} className={styles.boardSelect}>
|
<select value={selectedBoardValue} onChange={handleSelectChange} className={styles.boardSelect}>
|
||||||
<option value=''>Select board...</option>
|
<option value=''>Select board...</option>
|
||||||
{[...directories]
|
{[...directories]
|
||||||
.sort((a, b) => getBoardShortCode(a.title).localeCompare(getBoardShortCode(b.title)))
|
.sort((a, b) => getBoardShortCode(a.title).localeCompare(getBoardShortCode(b.title)))
|
||||||
@@ -142,7 +144,6 @@ const BoardSelector = ({
|
|||||||
</option>
|
</option>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
{selectedAddress && !directories.some((sub) => sub.address === selectedAddress) && <option value={selectedAddress}>{selectedAddress}</option>}
|
|
||||||
</select>
|
</select>
|
||||||
<span className={styles.orSeparator}>or</span>
|
<span className={styles.orSeparator}>or</span>
|
||||||
<form onSubmit={handleCustomSubmit} className={styles.customAddressForm}>
|
<form onSubmit={handleCustomSubmit} className={styles.customAddressForm}>
|
||||||
|
|||||||
Reference in New Issue
Block a user