import { fireEvent, render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { describe, expect, it } from 'vitest'; import { OneNoteApplication } from './App.js'; import type { OneNotePackageDto, OneNoteSectionDto, } from './onenote/model/dto.js'; import { MAX_SOURCE_FILE_BYTES, sourceKindFromFileName, } from './source-file.js'; import { OneNoteWorkerClient, type WorkerPort, } from './worker/onenote.client.js'; import type { WorkerRequest, WorkerResponse, } from './worker/worker-protocol.js'; const section: OneNoteSectionDto = { format: 'one', sourceName: 'Section.one', sectionName: 'Project notes', pages: [ { id: 'page-1', title: 'First meeting', text: 'This is the complete extracted page text.', textPreview: 'This is the complete extracted page text.', createdAt: '2024-01-02T10:00:00.000Z', }, ], diagnostics: [ { severity: 'warning', code: 'UNSUPPORTED_IMAGE', message: 'An image was not rendered.', recoverable: true, }, ], parserInfo: { implementation: 'typescript', supportedVariant: 'desktop-revision-store', referenceRevision: 'test', }, }; class SuccessfulParserWorker implements WorkerPort { onmessage: ((event: MessageEvent) => void) | null = null; onerror: ((event: ErrorEvent) => void) | null = null; terminated = false; constructor(private readonly packageResult?: OneNotePackageDto) {} postMessage(message: WorkerRequest): void { queueMicrotask(() => { if (message.type === 'parse-one') { this.respond({ type: 'section', requestId: message.requestId, sessionId: 'session-one', section, }); } else if (message.type === 'parse-onepkg' && this.packageResult) { this.respond({ type: 'package', requestId: message.requestId, sessionId: 'session-package', notebook: this.packageResult, }); } else if (message.type === 'get-page') { const page = section.pages[0]!; this.respond({ type: 'page', requestId: message.requestId, sessionId: message.sessionId, page: { ...page, id: message.pageId, blocks: [ { kind: 'text', id: 'text-1', sourceText: page.text, text: page.text, runs: [ { start: 0, end: page.text.length, text: page.text, style: {}, }, ], paragraphStyle: {}, layout: {}, }, ], diagnostics: [], }, }); } }); } terminate(): void { this.terminated = true; } private respond(response: WorkerResponse): void { this.onmessage?.(new MessageEvent('message', { data: response })); } } class FailingParserWorker implements WorkerPort { onmessage: ((event: MessageEvent) => void) | null = null; onerror: ((event: ErrorEvent) => void) | null = null; postMessage(message: WorkerRequest): void { if (message.type !== 'parse-one' && message.type !== 'parse-onepkg') return; queueMicrotask(() => { this.onmessage?.( new MessageEvent('message', { data: { type: 'failure', requestId: message.requestId, error: { code: 'invalid-format', message: 'The source header is not a recognized OneNote file.', recoverable: false, structure: 'OneStore header', }, } satisfies WorkerResponse, }) ); }); } terminate(): void {} } describe('OneNoteApplication', () => { it('recognizes only supported local filename extensions', () => { expect(sourceKindFromFileName('Section.ONE')).toBe('one'); expect(sourceKindFromFileName('Notebook.OnePkg')).toBe('onepkg'); expect(sourceKindFromFileName('notes.zip')).toBeNull(); }); it('opens a section, navigates its page, shows diagnostics, and clears', async () => { const user = userEvent.setup(); const worker = new SuccessfulParserWorker(); render( new OneNoteWorkerClient(() => worker)} /> ); await user.upload( screen.getByLabelText('Choose a OneNote file'), new File([new Uint8Array([1, 2, 3])], 'Section.one') ); expect( await screen.findByRole('heading', { name: 'First meeting' }) ).toBeInTheDocument(); expect( within(screen.getByRole('article', { name: 'First meeting' })).getByText( 'This is the complete extracted page text.' ) ).toBeInTheDocument(); expect(screen.getByText('UNSUPPORTED_IMAGE')).toBeInTheDocument(); await user.click(screen.getByRole('button', { name: 'Clear file' })); expect(worker.terminated).toBe(true); expect( screen.getByRole('heading', { name: 'Open a OneNote file' }) ).toBeInTheDocument(); }); it('opens a dropped OneNote package through the local worker', async () => { const notebook: OneNotePackageDto = { format: 'onepkg', sourceName: 'Notebook.onepkg', sourceSize: 1, entries: [], sections: [], tree: { interpreted: false, nodes: [] }, diagnostics: [], }; render( new OneNoteWorkerClient(() => new SuccessfulParserWorker(notebook)) } /> ); fireEvent.drop(screen.getByTestId('file-drop-zone'), { dataTransfer: { files: [new File([new Uint8Array([1])], 'Notebook.onepkg')], }, }); expect( await screen.findByRole('heading', { name: 'Package summary' }) ).toBeInTheDocument(); }); it('keeps a failed packaged section visible beside a readable one', async () => { const notebook: OneNotePackageDto = { format: 'onepkg', sourceName: 'Notebook.onepkg', sourceSize: 4096, entries: [ { path: 'Notes/Readable.one', normalizedPath: 'Notes/Readable.one', kind: 'section', uncompressedSize: 2048, compressionMethod: 'LZX', parseStatus: 'parsed', }, { path: 'Notes/Broken.one', normalizedPath: 'Notes/Broken.one', kind: 'section', uncompressedSize: 1024, compressionMethod: 'LZX', parseStatus: 'failed', }, ], sections: [ { id: 'readable', path: 'Notes/Readable.one', displayName: 'Readable', parseStatus: 'parsed', section, diagnostics: [], }, { id: 'broken', path: 'Notes/Broken.one', displayName: 'Broken', parseStatus: 'failed', diagnostics: [ { severity: 'error', code: 'SECTION_PARSE_FAILED', message: 'This section is malformed.', recoverable: true, }, ], }, ], tree: { interpreted: true, tocPath: 'Open Notebook.onetoc2', nodes: [ { kind: 'section-group', id: 'Notes', displayName: 'Notes', children: [ { kind: 'section', id: 'readable', sectionId: 'readable', displayName: 'Readable', }, { kind: 'section', id: 'broken', sectionId: 'broken', displayName: 'Broken', }, ], }, ], }, diagnostics: [ { severity: 'warning', code: 'TEST_PACKAGE_WARNING', message: 'Example recoverable package warning.', recoverable: true, }, ], }; const user = userEvent.setup(); render( new OneNoteWorkerClient(() => new SuccessfulParserWorker(notebook)) } /> ); await user.upload( screen.getByLabelText('Choose a OneNote file'), new File([new Uint8Array([1])], 'Notebook.onepkg') ); expect( await screen.findByRole('heading', { name: 'Package summary' }) ).toBeInTheDocument(); expect(screen.getByText('Notes')).toBeVisible(); expect(screen.getByText('OneNote notebook order')).toBeVisible(); expect( screen.getByRole('button', { name: /Broken, failed/i }) ).toBeVisible(); expect(screen.getByText('TEST_PACKAGE_WARNING')).toBeInTheDocument(); expect( await within( screen.getByRole('article', { name: 'First meeting' }) ).findByText('This is the complete extracted page text.') ).toBeInTheDocument(); await user.click(screen.getByRole('button', { name: /Broken, failed/i })); expect(screen.getByText('SECTION_PARSE_FAILED')).toBeInTheDocument(); expect( screen.getByText('This section did not produce a readable page list.') ).toBeInTheDocument(); }); it('shows a controlled parser failure', async () => { const user = userEvent.setup(); render( new OneNoteWorkerClient(() => new FailingParserWorker()) } /> ); await user.upload( screen.getByLabelText('Choose a OneNote file'), new File(['not OneNote'], 'Broken.one') ); expect(await screen.findByRole('alert')).toHaveTextContent( 'The source header is not a recognized OneNote file.' ); expect(screen.getByRole('alert')).toHaveTextContent( 'invalid-format ยท OneStore header' ); }); it('rejects unrelated extensions without starting a worker', async () => { const user = userEvent.setup({ applyAccept: false }); render(); await user.upload( screen.getByLabelText('Choose a OneNote file'), new File(['not a notebook'], 'notes.zip') ); expect(await screen.findByRole('alert')).toHaveTextContent( 'Choose a file whose name ends in .one or .onepkg.' ); }); it('enforces the source-size safety limit before reading', async () => { const user = userEvent.setup(); render(); const file = new File(['tiny'], 'large.one'); Object.defineProperty(file, 'size', { configurable: true, value: MAX_SOURCE_FILE_BYTES + 1, }); await user.upload(screen.getByLabelText('Choose a OneNote file'), file); expect(await screen.findByRole('alert')).toHaveTextContent('512 MiB'); }); });