merge: board selector default boards fix

This commit is contained in:
Tommaso Casaburi
2026-04-11 17:04:49 +07:00
2 changed files with 128 additions and 3 deletions
+124
View File
@@ -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');
});
});
+4 -3
View File
@@ -2,7 +2,7 @@ import { useEffect, useState, FormEvent } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useCommunity } from '@bitsocialnet/bitsocial-react-hooks';
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 Markdown from '../../components/markdown';
import styles from './rules.module.css';
@@ -103,6 +103,8 @@ const BoardSelector = ({
onSelect: (address: string) => void;
}) => {
const [customAddress, setCustomAddress] = useState('');
const selectedDefaultBoard = findDirectoryByAddress(directories, selectedAddress);
const selectedBoardValue = selectedDefaultBoard?.address ?? '';
const handleSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const value = e.target.value;
@@ -129,7 +131,7 @@ const BoardSelector = ({
</div>
<div className={styles.boxContent}>
<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>
{[...directories]
.sort((a, b) => getBoardShortCode(a.title).localeCompare(getBoardShortCode(b.title)))
@@ -142,7 +144,6 @@ const BoardSelector = ({
</option>
);
})}
{selectedAddress && !directories.some((sub) => sub.address === selectedAddress) && <option value={selectedAddress}>{selectedAddress}</option>}
</select>
<span className={styles.orSeparator}>or</span>
<form onSubmit={handleCustomSubmit} className={styles.customAddressForm}>