Files
toolbox-portal/src/App.test.tsx

123 lines
4.2 KiB
TypeScript

import { fireEvent, render, screen, waitFor } 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());
describe('portal UI', () => {
it('renders app cards and persists accessible pin/hide controls', async () => {
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
const user = userEvent.setup();
render(<App />);
expect(
await screen.findByRole('heading', { name: 'PDF Workbench' })
).toBeInTheDocument();
expect(
screen.getByText(
'Local processing · no file uploads declared · no telemetry declared'
)
).toBeInTheDocument();
const sourceLinks = screen.getAllByRole('link', {
name: 'Source and license',
});
expect(sourceLinks).toHaveLength(2);
expect(
sourceLinks.find(
(link) =>
link.getAttribute('href') ===
'https://git.add-ideas.de/zemion/pdf-tools'
)
).toHaveAttribute('rel', 'noopener noreferrer');
expect(
sourceLinks.find((link) =>
link.getAttribute('href')?.endsWith('/toolbox-portal/src/tag/v0.1.0')
)
).toBeDefined();
const launch = screen.getByRole('link', { name: /open tool/i });
expect(
new URL(launch.getAttribute('href')!).searchParams.get('toolbox')
).toContain('toolbox.catalog.json');
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.getByText('Pinned')).toBeInTheDocument();
await user.click(
screen.getByRole('button', { name: 'Hide PDF Workbench' })
);
expect(
screen.queryByRole('heading', { name: 'PDF Workbench' })
).not.toBeInTheDocument();
fireEvent.click(screen.getByRole('checkbox', { name: /show hidden/i }));
expect(
screen.getByRole('heading', { name: 'PDF Workbench' })
).toBeInTheDocument();
});
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 screen.findByRole('heading', { name: 'PDF Workbench' })
).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('contains preference focus and restores it to Personalize', 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 dialog = screen.getByRole('dialog', {
name: 'Personalize your toolbox',
});
const close = screen.getByRole('button', { name: 'Close preferences' });
expect(close).toHaveFocus();
expect(container.querySelector('.site-shell')).toHaveAttribute('inert');
expect(dialog).toBeInTheDocument();
expect(screen.getByText(/does not transmit them/i)).toHaveTextContent(
/Trusted code in another Toolbox app on this site can access same-origin storage/i
);
const reset = screen.getByRole('button', { name: 'Reset' });
reset.focus();
await user.tab();
expect(close).toHaveFocus();
await user.keyboard('{Escape}');
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
expect(personalize).toHaveFocus();
});
});