fix(fortune): scope s5s fortune markup (#1150)

* fix(markdown): scope fortune markup to fortune boards

* fix(fortune): store fortune output as bbcode

* fix(fortune): keep legacy fortune rendering

* fix(tests): resolve catalog button mock merge

* fix(fortune): validate hidden fortune length
This commit is contained in:
Tommaso Casaburi
2026-06-03 13:49:35 +07:00
committed by GitHub
parent 84f357cba1
commit d09d2d05d1
16 changed files with 439 additions and 93 deletions
@@ -458,6 +458,12 @@ const waitForOptionsValidation = async () => {
});
};
const waitForContentLengthValidation = async () => {
await act(async () => {
await new Promise((resolve) => setTimeout(resolve, 1020));
});
};
const dispatchChange = async (element: HTMLInputElement | HTMLSelectElement, value: string | boolean) => {
await act(async () => {
if (typeof value === 'boolean' && 'checked' in element) {
@@ -932,7 +938,7 @@ describe('PostForm', () => {
});
});
it('validates unsupported options and stores fortune output in post content', async () => {
it('validates unsupported options and keeps fortune output out of preview state until post publish', async () => {
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.25);
testState.resolvedCommunityAddress = 'random-nsfw.bso';
@@ -963,14 +969,38 @@ describe('PostForm', () => {
await dispatchInput(optionsInput as HTMLInputElement, 'fortune');
expect(container.textContent).not.toContain('Unsupported options');
expect(testState.publishPostOptions.content).toBe('fortune body<span class="fortune" style="color:#fd4d32"><br><br><b>Your fortune: Excellent Luck</b></span>');
expect(testState.publishPostOptions.content).toBe('fortune body');
await clickByText(table as HTMLTableElement, 'post');
expect(testState.publishPostMock).toHaveBeenCalledTimes(1);
expect(testState.publishPostMock).toHaveBeenCalledWith({
content: 'fortune body[fortune color=#fd4d32]Excellent Luck[/fortune]',
});
randomSpy.mockRestore();
});
it('counts hidden fortune output in post length validation without revealing it', async () => {
testState.resolvedCommunityAddress = 'random-nsfw.bso';
await renderPostForm('/b');
await clickByText(container, 'start_new_thread');
const table = container.querySelector('table') as HTMLTableElement;
const optionsInput = table.querySelector<HTMLInputElement>('input[aria-label="options"]') as HTMLInputElement;
const textarea = table.querySelector<HTMLTextAreaElement>('textarea') as HTMLTextAreaElement;
const longContent = 'x'.repeat(1930);
await dispatchInput(optionsInput, 'fortune');
await dispatchInput(textarea, longContent);
await waitForContentLengthValidation();
expect(testState.publishPostOptions.content).toBe(longContent);
expect(container.textContent).toContain('comment_field_too_long');
expect(container.textContent).not.toContain('[fortune color=');
expect(testState.publishPostMock).not.toHaveBeenCalled();
});
it('supports fortune on the /s5s/ route when directory metadata is not loaded', async () => {
const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0.25);
testState.resolvedCommunityAddress = 'silly-stuff.bso';
@@ -992,7 +1022,8 @@ describe('PostForm', () => {
await clickByText(table as HTMLTableElement, 'post');
expect(testState.publishPostMock).toHaveBeenCalledTimes(1);
expect(testState.publishedPostOptions?.content).toBe('silly fortune<span class="fortune" style="color:#fd4d32"><br><br><b>Your fortune: Excellent Luck</b></span>');
expect(testState.publishedPostOptions?.content).toBe('silly fortune[fortune color=#fd4d32]Excellent Luck[/fortune]');
expect(testState.setPublishPostOptionsMock).toHaveBeenCalledWith({ content: 'silly fortune' });
randomSpy.mockRestore();
});