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, ToolboxCatalog, } from '@add-ideas/toolbox-contract'; import { toolboxApp } from '../src/toolboxApp'; beforeEach(() => localStorage.clear()); afterEach(cleanup); const pdfApp: ToolboxAppManifest = { ...toolboxApp, id: 'de.add-ideas.pdf-tools', name: 'PDF Workbench', version: '0.3.4', description: 'Local PDF page operations in your browser.', categories: ['documents', 'pdf'], tags: ['merge', 'split', 'rotate', 'reorder'], requirements: { ...toolboxApp.requirements, workers: true, indexedDb: true, }, }; const catalog: ToolboxCatalog = { schemaVersion: 1, id: 'integration-test', name: 'Integration test toolbox', home: './', theme: { mode: 'system', brand: 'Test Toolbox', }, apps: [ { manifest: './apps/xslt/toolbox-app.json', enabled: true, }, { manifest: './apps/pdf/toolbox-app.json', enabled: true, }, ], }; describe('toolbox shell integration', () => { it('shows the standard app controls when running standalone', () => { const onHelp = vi.fn(); render(

XSLT workbench

); expect(screen.getByText('XSLT workbench')).toBeTruthy(); fireEvent.click(screen.getByRole('button', { name: 'Help' })); expect(onHelp).toHaveBeenCalledOnce(); expect( 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(

XSLT workbench

); 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( localStorage.getItem('@add-ideas/toolbox-portal:v1:preferences') ).toContain('"theme":"system"'); }); it('loads a same-origin catalogue and carries context through switcher links', async () => { const location = 'https://example.test/deep/nested/app/?toolbox=/toolbox.catalog.json'; const fetch = vi.fn(createCatalogFetch()); const { container } = render(

XSLT workbench

); fireEvent.click(screen.getByText('Apps').closest('summary')!); const navigation = await screen.findByRole('navigation', { name: 'Toolbox applications', }); expect(navigation).toBeTruthy(); expect( container.firstElementChild?.getAttribute('data-toolbox-context') ).toBe('connected'); expect( screen.getByRole('link', { name: 'Test Toolbox' }).getAttribute('href') ).toBe('https://example.test/'); const pdfLink = screen.getByRole('link', { name: 'PDF Workbench' }); const pdfUrl = new URL(pdfLink.getAttribute('href') ?? ''); expect(pdfUrl.pathname).toBe('/apps/pdf/'); expect(pdfUrl.searchParams.get('toolbox')).toBe( 'https://example.test/toolbox.catalog.json' ); expect(fetch).toHaveBeenCalledTimes(3); await waitFor(() => { expect( screen .getByRole('link', { name: 'XSLT tools' }) .getAttribute('aria-current') ).toBe('page'); }); }); }); function createCatalogFetch() { return async (input: string | URL | Request): Promise => { const url = new URL( typeof input === 'string' ? input : input instanceof URL ? input.href : input.url ); const documents = new Map([ ['/toolbox.catalog.json', catalog], ['/apps/xslt/toolbox-app.json', toolboxApp], ['/apps/pdf/toolbox-app.json', pdfApp], ]); const document = documents.get(url.pathname); if (document === undefined) { return { ok: false, status: 404, json: async () => ({}), } as Response; } return { ok: true, status: 200, json: async () => document, } as Response; }; }