591 lines
20 KiB
TypeScript
591 lines
20 KiB
TypeScript
import {
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
waitFor,
|
|
within,
|
|
} from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import App from './App';
|
|
import { PREFERENCES_KEY } from './preferences';
|
|
import { catalogue, catalogueFetch } from './test/fixtures';
|
|
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
function toolLink(name: string) {
|
|
return screen.getByRole('link', { name: `Open ${name}` });
|
|
}
|
|
|
|
function queryToolLink(name: string) {
|
|
return screen.queryByRole('link', { name: `Open ${name}` });
|
|
}
|
|
|
|
function findToolLink(name = 'PDF Workbench') {
|
|
return screen.findByRole('link', { name: `Open ${name}` });
|
|
}
|
|
|
|
describe('portal UI', () => {
|
|
it('renders compact actionable tiles and persists pin/hide controls', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
const user = userEvent.setup();
|
|
render(<App />);
|
|
const launch = await findToolLink();
|
|
const card = screen.getByTestId('app-card-de.add-ideas.pdf-tools');
|
|
expect(within(card).getByRole('heading', { level: 3 })).toHaveTextContent(
|
|
'PDF'
|
|
);
|
|
expect(within(card).getByText('Arrange PDF pages locally.')).toBeVisible();
|
|
expect(
|
|
screen.getByTestId('app-card-de.add-ideas.xslt-tools')
|
|
).toHaveTextContent('XSLT');
|
|
expect(
|
|
screen.getByTestId('app-card-de.add-ideas.onenote-tools')
|
|
).toHaveTextContent('OneNote');
|
|
expect(
|
|
screen.queryByText(
|
|
'Page-level PDF operations performed locally in the browser.'
|
|
)
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.queryByText(
|
|
'Local processing · no file uploads declared · no telemetry declared'
|
|
)
|
|
).not.toBeInTheDocument();
|
|
expect(
|
|
screen.getAllByRole('link', { name: 'Source and license' })
|
|
).toHaveLength(1);
|
|
expect(
|
|
screen.queryByRole('region', { name: 'Pinned' })
|
|
).not.toBeInTheDocument();
|
|
const showHidden = screen.getByRole('checkbox', {
|
|
name: 'Show hidden (0)',
|
|
});
|
|
expect(showHidden).toBeDisabled();
|
|
expect(showHidden).not.toBeChecked();
|
|
expect(launch).toHaveClass('app-card__launch-link');
|
|
expect(
|
|
new URL(launch.getAttribute('href')!).searchParams.get('toolbox')
|
|
).toContain('toolbox.catalog.json');
|
|
const actions = within(card).getByRole('group', {
|
|
name: 'Actions for PDF Workbench',
|
|
});
|
|
const info = within(actions).getByRole('button', {
|
|
name: 'More information about PDF Workbench',
|
|
});
|
|
expect(info).toHaveAttribute('aria-haspopup', 'dialog');
|
|
for (const control of within(actions).getAllByRole('button'))
|
|
expect(control.closest('a')).toBeNull();
|
|
expect(launch).not.toContainElement(info);
|
|
|
|
await user.click(screen.getByRole('button', { name: 'Pin PDF Workbench' }));
|
|
await waitFor(() =>
|
|
expect(localStorage.getItem(PREFERENCES_KEY)).toContain(
|
|
'de.add-ideas.pdf-tools'
|
|
)
|
|
);
|
|
expect(screen.getByRole('region', { name: 'Pinned' })).toContainElement(
|
|
screen.getByTestId('app-card-de.add-ideas.pdf-tools')
|
|
);
|
|
expect(
|
|
screen.queryByRole('button', { name: /Move PDF Workbench/i })
|
|
).not.toBeInTheDocument();
|
|
const unpin = screen.getByRole('button', {
|
|
name: 'Unpin PDF Workbench',
|
|
});
|
|
expect(unpin).toHaveAttribute('aria-pressed', 'true');
|
|
expect(unpin).toHaveClass('is-active');
|
|
await waitFor(() => expect(unpin).toHaveFocus());
|
|
expect(
|
|
screen.getByRole('button', { name: 'Reorder PDF Workbench' })
|
|
).toBeInTheDocument();
|
|
|
|
await user.click(
|
|
screen.getByRole('button', { name: 'Hide PDF Workbench' })
|
|
);
|
|
expect(queryToolLink('PDF Workbench')).not.toBeInTheDocument();
|
|
expect(showHidden).toBeEnabled();
|
|
expect(showHidden).toHaveAccessibleName('Show hidden (1)');
|
|
await waitFor(() => expect(showHidden).toHaveFocus());
|
|
fireEvent.click(showHidden);
|
|
expect(toolLink('PDF Workbench')).toBeInTheDocument();
|
|
await user.click(
|
|
screen.getByRole('button', { name: 'Show PDF Workbench' })
|
|
);
|
|
expect(showHidden).toBeDisabled();
|
|
expect(showHidden).not.toBeChecked();
|
|
await waitFor(() =>
|
|
expect(
|
|
screen.getByRole('button', { name: 'Hide PDF Workbench' })
|
|
).toHaveFocus()
|
|
);
|
|
});
|
|
|
|
it('uses the compact Regex presentation for the released tool', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(
|
|
catalogueFetch({
|
|
'/toolbox.catalog.json': Response.json({
|
|
...catalogue,
|
|
apps: [
|
|
...catalogue.apps,
|
|
{ manifest: './apps/regex/toolbox-app.json', enabled: true },
|
|
],
|
|
}),
|
|
})
|
|
)
|
|
);
|
|
render(<App />);
|
|
|
|
await findToolLink();
|
|
const card = screen.getByTestId('app-card-de.add-ideas.regex-tools');
|
|
expect(within(card).getByRole('heading', { level: 3 })).toHaveTextContent(
|
|
'Regex'
|
|
);
|
|
expect(
|
|
within(card).getByText('Explain and test regex locally.')
|
|
).toBeVisible();
|
|
});
|
|
|
|
it('moves the full disclosure into an accessible modal', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
const user = userEvent.setup();
|
|
const { container } = render(<App />);
|
|
await findToolLink();
|
|
const info = screen.getByRole('button', {
|
|
name: 'More information about PDF Workbench',
|
|
});
|
|
|
|
await user.click(info);
|
|
const dialog = screen.getByRole('dialog', { name: 'PDF Workbench' });
|
|
expect(dialog).toHaveAttribute('aria-modal', 'true');
|
|
expect(dialog).toHaveTextContent(
|
|
'Page-level PDF operations performed locally in the browser.'
|
|
);
|
|
expect(dialog).toHaveTextContent(
|
|
'Local processing · no file uploads declared · no telemetry declared'
|
|
);
|
|
expect(dialog).toHaveTextContent(
|
|
'Needs Secure context · Web workers · IndexedDB'
|
|
);
|
|
expect(dialog).toHaveTextContent('v0.4.3');
|
|
expect(
|
|
within(dialog).getByRole('button', { name: 'documents' })
|
|
).toBeInTheDocument();
|
|
expect(
|
|
within(dialog).getByRole('button', { name: '#merge' })
|
|
).toBeInTheDocument();
|
|
const sourceLink = within(dialog).getByRole('link', {
|
|
name: 'Source and license',
|
|
});
|
|
expect(sourceLink).toHaveAttribute(
|
|
'href',
|
|
'https://git.add-ideas.de/zemion/pdf-tools'
|
|
);
|
|
expect(sourceLink).toHaveAttribute('rel', 'noopener noreferrer');
|
|
expect(
|
|
within(dialog).getByRole('link', { name: 'Open PDF' })
|
|
).toBeInTheDocument();
|
|
expect(container.querySelector('.site-shell')).toHaveAttribute('inert');
|
|
expect(container.querySelector('.site-shell')).toHaveAttribute(
|
|
'aria-hidden',
|
|
'true'
|
|
);
|
|
expect(document.documentElement).toHaveStyle({ overflow: 'hidden' });
|
|
|
|
const close = within(dialog).getByRole('button', {
|
|
name: 'Close information for PDF Workbench',
|
|
});
|
|
expect(close).toHaveFocus();
|
|
within(dialog).getByRole('link', { name: 'Open PDF' }).focus();
|
|
await user.tab();
|
|
expect(close).toHaveFocus();
|
|
|
|
await user.keyboard('{Escape}');
|
|
expect(
|
|
screen.queryByRole('dialog', { name: 'PDF Workbench' })
|
|
).not.toBeInTheDocument();
|
|
expect(info).toHaveFocus();
|
|
expect(container.querySelector('.site-shell')).not.toHaveAttribute('inert');
|
|
expect(document.documentElement).not.toHaveStyle({ overflow: 'hidden' });
|
|
});
|
|
|
|
it('keeps search and category controls together and filters with each', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
const user = userEvent.setup();
|
|
render(<App />);
|
|
await findToolLink();
|
|
|
|
const search = screen.getByRole('searchbox', { name: 'Search tools' });
|
|
const category = screen.getByRole('combobox', {
|
|
name: 'Filter by category',
|
|
});
|
|
expect(search.closest('.tool-controls')).toBe(
|
|
category.closest('.tool-controls')
|
|
);
|
|
|
|
await user.type(search, 'xslt');
|
|
expect(toolLink('XSLT Workbench')).toBeInTheDocument();
|
|
expect(queryToolLink('PDF Workbench')).not.toBeInTheDocument();
|
|
|
|
await user.clear(search);
|
|
await user.selectOptions(category, 'notes');
|
|
expect(toolLink('OneNote Reader')).toBeInTheDocument();
|
|
expect(queryToolLink('XSLT Workbench')).not.toBeInTheDocument();
|
|
|
|
await user.selectOptions(category, '');
|
|
await user.click(
|
|
screen.getByRole('button', {
|
|
name: 'More information about PDF Workbench',
|
|
})
|
|
);
|
|
await user.click(
|
|
within(screen.getByRole('dialog', { name: 'PDF Workbench' })).getByRole(
|
|
'button',
|
|
{ name: 'pdf' }
|
|
)
|
|
);
|
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
expect(category).toHaveValue('pdf');
|
|
expect(toolLink('PDF Workbench')).toBeInTheDocument();
|
|
expect(queryToolLink('OneNote Reader')).not.toBeInTheDocument();
|
|
});
|
|
|
|
it('keeps remaining hidden tools visible while editing the hidden set', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
const user = userEvent.setup();
|
|
render(<App />);
|
|
await findToolLink();
|
|
|
|
await user.click(
|
|
screen.getByRole('button', { name: 'Hide PDF Workbench' })
|
|
);
|
|
const showHidden = screen.getByRole('checkbox', {
|
|
name: 'Show hidden (1)',
|
|
});
|
|
await user.click(showHidden);
|
|
await user.click(
|
|
screen.getByRole('button', { name: 'Hide XSLT Workbench' })
|
|
);
|
|
expect(showHidden).toHaveAccessibleName('Show hidden (2)');
|
|
await user.click(
|
|
screen.getByRole('button', { name: 'Show PDF Workbench' })
|
|
);
|
|
|
|
expect(showHidden).toBeChecked();
|
|
expect(showHidden).toHaveAccessibleName('Show hidden (1)');
|
|
expect(toolLink('XSLT Workbench')).toBeInTheDocument();
|
|
});
|
|
|
|
it('clears search filters without revealing hidden tools', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
const user = userEvent.setup();
|
|
render(<App />);
|
|
await findToolLink();
|
|
await user.click(
|
|
screen.getByRole('button', { name: 'Hide PDF Workbench' })
|
|
);
|
|
await user.type(
|
|
screen.getByRole('searchbox', { name: 'Search tools' }),
|
|
'no tool has this phrase'
|
|
);
|
|
await user.click(screen.getByRole('button', { name: 'Clear filters' }));
|
|
|
|
expect(queryToolLink('PDF Workbench')).not.toBeInTheDocument();
|
|
expect(toolLink('XSLT Workbench')).toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole('checkbox', { name: 'Show hidden (1)' })
|
|
).not.toBeChecked();
|
|
});
|
|
|
|
it('lists contextual destinations in the Apps menu', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
const user = userEvent.setup();
|
|
render(<App />);
|
|
await findToolLink();
|
|
await user.click(screen.getByRole('button', { name: 'Apps' }));
|
|
const navigation = screen.getByRole('navigation', {
|
|
name: 'Toolbox applications',
|
|
});
|
|
const destination = within(navigation).getByRole('link', {
|
|
name: 'XSLT Workbench',
|
|
});
|
|
expect(
|
|
new URL(destination.getAttribute('href')!).searchParams.get('toolbox')
|
|
).toContain('toolbox.catalog.json');
|
|
});
|
|
|
|
it('uses the shared accessible header identity and control order', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
render(<App />);
|
|
await findToolLink();
|
|
|
|
const header = document.querySelector<HTMLElement>('.portal-header')!;
|
|
expect(
|
|
within(header).getByRole('heading', { level: 1, name: 'Toolbox' })
|
|
).toBeInTheDocument();
|
|
expect(
|
|
within(header).getByText('Local-first browser tools')
|
|
).toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole('heading', {
|
|
level: 2,
|
|
name: 'Your document tools, in one calm place.',
|
|
})
|
|
).toBeInTheDocument();
|
|
expect(
|
|
screen.getAllByRole('heading', {
|
|
name: 'Your document tools, in one calm place.',
|
|
})
|
|
).toHaveLength(1);
|
|
|
|
const personalize = within(header).getByRole('button', {
|
|
name: 'Personalize',
|
|
});
|
|
const apps = within(header).getByRole('button', { name: 'Apps' });
|
|
const source = within(header).getByRole('link', {
|
|
name: 'Toolbox source on Gitea',
|
|
});
|
|
const help = within(header).getByRole('button', { name: 'Help' });
|
|
const brand = within(header).getByRole('link', {
|
|
name: 'add·ideas Toolbox',
|
|
});
|
|
const controls = within(header).getByRole('group', {
|
|
name: 'Toolbox controls',
|
|
});
|
|
expect(
|
|
Array.from(controls.children, (element) =>
|
|
element.getAttribute('aria-label')
|
|
)
|
|
).toEqual(['Help', 'Toolbox source on Gitea', 'Apps', 'Personalize']);
|
|
expect(help.compareDocumentPosition(source)).toBe(
|
|
Node.DOCUMENT_POSITION_FOLLOWING
|
|
);
|
|
expect(source.compareDocumentPosition(apps)).toBe(
|
|
Node.DOCUMENT_POSITION_FOLLOWING
|
|
);
|
|
expect(apps.compareDocumentPosition(personalize)).toBe(
|
|
Node.DOCUMENT_POSITION_FOLLOWING
|
|
);
|
|
expect(
|
|
Array.from(
|
|
header.querySelector('.toolbox-shell__bar')?.children ?? [],
|
|
(element) => element.className
|
|
)
|
|
).toEqual([
|
|
'toolbox-shell__brand',
|
|
'toolbox-shell__identity',
|
|
'toolbox-shell__controls',
|
|
]);
|
|
expect(brand.querySelector('img')).toHaveAttribute('src', './favicon.svg');
|
|
});
|
|
|
|
it('persists an explicit appearance and accepts cross-tab updates', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
const user = userEvent.setup();
|
|
render(<App />);
|
|
await findToolLink();
|
|
const personalize = screen.getByRole('button', { name: 'Personalize' });
|
|
await user.click(personalize);
|
|
expect(
|
|
screen.getByRole('heading', { name: 'Personalize your toolbox' })
|
|
).toBeInTheDocument();
|
|
await user.click(screen.getByRole('button', { name: 'Dark' }));
|
|
expect(document.documentElement).toHaveAttribute('data-theme', 'dark');
|
|
await waitFor(() =>
|
|
expect(localStorage.getItem(PREFERENCES_KEY)).toContain('"theme":"dark"')
|
|
);
|
|
await user.click(personalize);
|
|
|
|
fireEvent(
|
|
window,
|
|
new StorageEvent('storage', {
|
|
key: PREFERENCES_KEY,
|
|
newValue: JSON.stringify({
|
|
version: 1,
|
|
pinned: [],
|
|
order: [
|
|
'de.add-ideas.pdf-tools',
|
|
'de.add-ideas.xslt-tools',
|
|
'de.add-ideas.onenote-tools',
|
|
],
|
|
hidden: [],
|
|
theme: 'light',
|
|
}),
|
|
})
|
|
);
|
|
expect(document.documentElement).toHaveAttribute('data-theme', 'light');
|
|
});
|
|
|
|
it('supports keyboard drag ordering from each tile grip', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
render(<App />);
|
|
await findToolLink();
|
|
const cards = [
|
|
screen.getByTestId('app-card-de.add-ideas.pdf-tools'),
|
|
screen.getByTestId('app-card-de.add-ideas.xslt-tools'),
|
|
screen.getByTestId('app-card-de.add-ideas.onenote-tools'),
|
|
];
|
|
cards.forEach((card, index) => {
|
|
const rect = {
|
|
x: index * 220,
|
|
y: 0,
|
|
width: 188,
|
|
height: 188,
|
|
top: 0,
|
|
right: index * 220 + 188,
|
|
bottom: 188,
|
|
left: index * 220,
|
|
toJSON: () => ({}),
|
|
} as DOMRect;
|
|
card.getBoundingClientRect = () => rect;
|
|
card.getClientRects = () => [rect] as unknown as DOMRectList;
|
|
});
|
|
|
|
const grip = screen.getByRole('button', { name: 'Reorder PDF Workbench' });
|
|
grip.focus();
|
|
fireEvent.keyDown(grip, { key: ' ', code: 'Space' });
|
|
await waitFor(() => expect(grip).toHaveAttribute('aria-pressed', 'true'));
|
|
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
fireEvent.keyDown(document, { key: 'ArrowRight', code: 'ArrowRight' });
|
|
fireEvent.keyDown(document, { key: ' ', code: 'Space' });
|
|
await waitFor(() => {
|
|
const tools = screen.getByRole('region', { name: 'Tools' });
|
|
expect(
|
|
within(tools)
|
|
.getAllByRole('heading', { level: 3 })
|
|
.map((heading) => heading.textContent)
|
|
).toEqual(['XSLT', 'PDF', 'OneNote']);
|
|
});
|
|
expect(
|
|
JSON.parse(localStorage.getItem(PREFERENCES_KEY) ?? '{}').order
|
|
).toEqual([
|
|
'de.add-ideas.xslt-tools',
|
|
'de.add-ideas.pdf-tools',
|
|
'de.add-ideas.onenote-tools',
|
|
]);
|
|
});
|
|
|
|
it('filters by clickable tags and clears an active tag', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
const user = userEvent.setup();
|
|
render(<App />);
|
|
await findToolLink();
|
|
await user.click(
|
|
screen.getByRole('button', {
|
|
name: 'More information about PDF Workbench',
|
|
})
|
|
);
|
|
const dialog = screen.getByRole('dialog', { name: 'PDF Workbench' });
|
|
await user.click(within(dialog).getByRole('button', { name: '#merge' }));
|
|
|
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
expect(toolLink('PDF Workbench')).toBeInTheDocument();
|
|
expect(queryToolLink('XSLT Workbench')).not.toBeInTheDocument();
|
|
expect(queryToolLink('OneNote Reader')).not.toBeInTheDocument();
|
|
|
|
const activeTag = screen.getByRole('button', {
|
|
name: /#merge.*Clear tag filter/i,
|
|
});
|
|
await user.click(activeTag);
|
|
expect(await findToolLink('XSLT Workbench')).toBeInTheDocument();
|
|
expect(toolLink('OneNote Reader')).toBeInTheDocument();
|
|
});
|
|
|
|
it('opens the standard help control and explains tile interaction', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
const user = userEvent.setup();
|
|
const { container } = render(<App />);
|
|
await findToolLink();
|
|
const help = screen.getByRole('button', { name: 'Help' });
|
|
await user.click(help);
|
|
const dialog = screen.getByRole('dialog', {
|
|
name: 'Choose and arrange your tools',
|
|
});
|
|
expect(dialog).toHaveTextContent('click anywhere on a tool tile');
|
|
expect(dialog).toHaveTextContent('use the light bulb');
|
|
expect(container.querySelector('.site-shell')).toHaveAttribute('inert');
|
|
expect(container.querySelector('.site-shell')).toHaveAttribute(
|
|
'aria-hidden',
|
|
'true'
|
|
);
|
|
const close = within(dialog).getByRole('button', { name: 'Close help' });
|
|
expect(close).toHaveFocus();
|
|
await user.tab();
|
|
expect(close).toHaveFocus();
|
|
await user.keyboard('{Escape}');
|
|
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
|
|
expect(container.querySelector('.site-shell')).not.toHaveAttribute('inert');
|
|
expect(help).toHaveFocus();
|
|
});
|
|
|
|
it('shows a recoverable catalogue error', async () => {
|
|
const fetchMock = vi
|
|
.fn()
|
|
.mockResolvedValueOnce(new Response('no', { status: 503 }))
|
|
.mockImplementation(catalogueFetch());
|
|
vi.stubGlobal('fetch', fetchMock);
|
|
render(<App />);
|
|
expect(
|
|
await screen.findByRole('heading', { name: 'Toolbox unavailable' })
|
|
).toBeInTheDocument();
|
|
fireEvent.click(screen.getByRole('button', { name: 'Try again' }));
|
|
expect(await findToolLink()).toBeInTheDocument();
|
|
});
|
|
|
|
it('shows a dedicated empty state for a valid catalogue', async () => {
|
|
vi.stubGlobal(
|
|
'fetch',
|
|
vi.fn(
|
|
catalogueFetch({
|
|
'/toolbox.catalog.json': Response.json({ ...catalogue, apps: [] }),
|
|
})
|
|
)
|
|
);
|
|
render(<App />);
|
|
expect(
|
|
await screen.findByRole('heading', { name: 'No tools yet' })
|
|
).toBeInTheDocument();
|
|
});
|
|
|
|
it('hosts compact preferences in the shared Personalize popover', async () => {
|
|
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
|
|
const user = userEvent.setup();
|
|
const { container } = render(<App />);
|
|
const personalize = screen.getByRole('button', { name: 'Personalize' });
|
|
|
|
await user.click(personalize);
|
|
const heading = screen.getByRole('heading', {
|
|
name: 'Personalize your toolbox',
|
|
});
|
|
const popover = screen.getByRole('dialog', { name: 'Personalize Toolbox' });
|
|
expect(popover).toContainElement(heading);
|
|
expect(popover).toContainElement(screen.getByText('On this device'));
|
|
expect(
|
|
within(popover).getByRole('group', { name: 'Appearance' })
|
|
).toBeInTheDocument();
|
|
expect(
|
|
popover.querySelector('.toolbox-shell__preferences-content')
|
|
).toBeInTheDocument();
|
|
expect(popover).not.toHaveAttribute('aria-modal');
|
|
expect(container.querySelector('.site-shell')).not.toHaveAttribute('inert');
|
|
expect(screen.getByText(/does not transmit them/i)).toHaveTextContent(
|
|
/Trusted code in another Toolbox app on this site can access same-origin storage/i
|
|
);
|
|
expect(
|
|
screen.getByRole('button', { name: 'Export JSON' })
|
|
).toBeInTheDocument();
|
|
expect(
|
|
screen.getByRole('button', { name: 'Import JSON' })
|
|
).toBeInTheDocument();
|
|
expect(screen.getByRole('button', { name: 'Reset' })).toBeInTheDocument();
|
|
|
|
await user.keyboard('{Escape}');
|
|
expect(
|
|
screen.queryByRole('dialog', { name: 'Personalize Toolbox' })
|
|
).not.toBeInTheDocument();
|
|
expect(personalize).toHaveFocus();
|
|
});
|
|
});
|