feat(challenge modal): remember trusted board websites

This commit is contained in:
Tommaso Casaburi
2026-05-06 19:05:26 +07:00
parent 2ebd9ecc30
commit cb05a8b905
46 changed files with 733 additions and 366 deletions
@@ -0,0 +1,62 @@
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 useTrustedBoardUrlPermissionsStore from '../../../../stores/use-trusted-board-url-permissions-store';
import TrustedBoardLinksSetting from '../trusted-board-links-setting';
(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>;
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
let container: HTMLDivElement;
let root: Root;
const render = () => {
act(() => {
root.render(createElement(TrustedBoardLinksSetting));
});
};
describe('TrustedBoardLinksSetting', () => {
beforeEach(() => {
localStorage.clear();
useTrustedBoardUrlPermissionsStore.setState({ trustedOrigins: {} });
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
container.remove();
});
it('shows an empty state when no board links are trusted', () => {
render();
expect(container.textContent).toContain('trusted_board_links_intro');
expect(container.textContent).toContain('trusted_board_links_empty');
});
it('revokes a trusted board link permission', async () => {
useTrustedBoardUrlPermissionsStore.getState().trustOrigin('https://spamblocker.bitsocial.net', 'spamblocker.bitsocial.net');
render();
expect(container.textContent).toContain('spamblocker.bitsocial.net');
const revokeButton = Array.from(container.querySelectorAll('button')).find((button) => button.textContent === 'revoke');
await act(async () => {
revokeButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }));
});
expect(useTrustedBoardUrlPermissionsStore.getState().isOriginTrusted('https://spamblocker.bitsocial.net')).toBe(false);
expect(container.textContent).toContain('trusted_board_links_empty');
});
});
@@ -0,0 +1 @@
export { default } from './trusted-board-links-setting';
@@ -0,0 +1,30 @@
.trustedBoardLinks {
margin: 0 10px 10px;
}
.description, .empty {
margin-bottom: 8px;
}
.list {
list-style: none;
padding: 0;
margin: 0;
}
.item {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 8px;
}
.site {
min-width: 0;
overflow-wrap: anywhere;
}
.expiry {
white-space: nowrap;
}
@@ -0,0 +1,38 @@
import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import useTrustedBoardUrlPermissionsStore from '../../../stores/use-trusted-board-url-permissions-store';
import styles from './trusted-board-links-setting.module.css';
const TrustedBoardLinksSetting = () => {
const { t } = useTranslation();
const trustedOriginsByOrigin = useTrustedBoardUrlPermissionsStore((state) => state.trustedOrigins);
const revokeOrigin = useTrustedBoardUrlPermissionsStore((state) => state.revokeOrigin);
const trustedOrigins = useMemo(
() => Object.values(trustedOriginsByOrigin).sort((a, b) => a.site.localeCompare(b.site) || a.origin.localeCompare(b.origin)),
[trustedOriginsByOrigin],
);
return (
<div className={styles.trustedBoardLinks}>
<div className={styles.description}>{t('trusted_board_links_intro')}</div>
{trustedOrigins.length ? (
<ul className={styles.list}>
{trustedOrigins.map((permission) => (
<li key={permission.origin} className={styles.item}>
<span className={styles.site}>
{permission.site} <span className={styles.expiry}>({t('trusted_board_link_until_revoked')})</span>
</span>
<button type='button' onClick={() => revokeOrigin(permission.origin)}>
{t('revoke')}
</button>
</li>
))}
</ul>
) : (
<div className={styles.empty}>{t('trusted_board_links_empty')}</div>
)}
</div>
);
};
export default memo(TrustedBoardLinksSetting);