feat: add shared Toolbox header and themes

This commit is contained in:
2026-07-23 00:29:21 +02:00
parent 8f8a59e76c
commit d5574a0a44
16 changed files with 451 additions and 176 deletions

View File

@@ -1,5 +1,11 @@
import { cleanup, render, screen, waitFor } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import {
cleanup,
fireEvent,
render,
screen,
waitFor,
} from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { AppShell } from '@add-ideas/toolbox-shell-react';
import type {
ToolboxAppManifest,
@@ -7,6 +13,7 @@ import type {
} from '@add-ideas/toolbox-contract';
import { toolboxApp } from '../src/toolboxApp';
beforeEach(() => localStorage.clear());
afterEach(cleanup);
const pdfApp: ToolboxAppManifest = {
@@ -46,25 +53,51 @@ const catalog: ToolboxCatalog = {
};
describe('toolbox shell integration', () => {
it('keeps the app usable standalone with its XSLT Help action', () => {
it('shows the standard app controls when running standalone', () => {
const onHelp = vi.fn();
render(
<AppShell
app={toolboxApp}
contextOptions={{ location: 'https://example.test/deep/nested/app/' }}
appActions={<button type="button">Help</button>}
helpAction={{ onClick: onHelp, label: 'Help' }}
>
<p>XSLT workbench</p>
</AppShell>
);
expect(screen.getByText('XSLT workbench')).toBeTruthy();
expect(screen.getByRole('button', { name: 'Help' })).toBeTruthy();
fireEvent.click(screen.getByRole('button', { name: 'Help' }));
expect(onHelp).toHaveBeenCalledOnce();
expect(
screen.getByRole('link', { name: 'Source' }).getAttribute('href')
).toBe('https://git.add-ideas.de/zemion/xslt-tools/src/tag/v0.3.2');
screen
.getByRole('link', { name: 'Source for XSLT tools on Gitea' })
.getAttribute('href')
).toBe('https://git.add-ideas.de/zemion/xslt-tools');
expect(screen.getByText('Personalize')).toBeTruthy();
expect(screen.getByText('Apps')).toBeTruthy();
});
it('persists explicit light, dark, and system theme choices', () => {
const { container } = render(
<AppShell app={toolboxApp}>
<p>XSLT workbench</p>
</AppShell>
);
const shell = container.firstElementChild;
expect(shell?.getAttribute('data-toolbox-theme')).toBe('system');
fireEvent.click(screen.getByText('Personalize').closest('summary')!);
fireEvent.click(screen.getByRole('button', { name: 'Dark' }));
expect(shell?.getAttribute('data-toolbox-theme')).toBe('dark');
fireEvent.click(screen.getByRole('button', { name: 'Light' }));
expect(shell?.getAttribute('data-toolbox-theme')).toBe('light');
fireEvent.click(screen.getByRole('button', { name: 'System' }));
expect(shell?.getAttribute('data-toolbox-theme')).toBe('system');
expect(
screen.queryByRole('navigation', { name: 'Toolbox applications' })
).toBeNull();
localStorage.getItem('@add-ideas/toolbox-portal:v1:preferences')
).toContain('"theme":"system"');
});
it('loads a same-origin catalogue and carries context through switcher links', async () => {
@@ -81,6 +114,7 @@ describe('toolbox shell integration', () => {
</AppShell>
);
fireEvent.click(screen.getByText('Apps').closest('summary')!);
const navigation = await screen.findByRole('navigation', {
name: 'Toolbox applications',
});