// @vitest-environment jsdom import { act } from 'react'; import { createRoot } from 'react-dom/client'; import { afterEach, describe, expect, it, vi } from 'vitest'; import { AssessorApiClient } from '../../src/api/assessor-api-client'; import { GpoFolderImport } from '../../src/features/project-import/GpoFolderImport'; afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); }); const privateName = 'Synthetic confidential customer'; const error = (name: string) => new DOMException(`C:/Customers/${privateName}/backup.xml`, name); describe.each(['dark', 'light'])('GPO folder click diagnostics (%s)', theme => { it.each([ ['enumeration', 'Folder enumeration: NotFoundError (1)'], ['access', 'File access: NotAllowedError (1)'], ['reading', 'File reading: NotReadableError (1)'], ['size', 'File reading: SizeMismatch (1)'], ['unknown', 'File reading: UnknownError (1)'], ['empty', 'contains no files'], ['selection', 'Folder selection: SecurityError (1)'], ])('reports %s without exposing customer data or uploading', async (scenario, expected) => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); const submit = vi.spyOn(AssessorApiClient.prototype, 'submitGpoBackup'); const picker = vi.fn(async () => { if (scenario === 'selection') throw error('SecurityError'); return { kind: 'directory', name: privateName, async *values() { if (scenario === 'enumeration') throw error('NotFoundError'); if (scenario === 'empty') return; yield { kind: 'file', name: 'confidential.xml', getFile: async () => { if (scenario === 'access') throw error('NotAllowedError'); return { size: 1, arrayBuffer: async () => { if (scenario === 'size') return new ArrayBuffer(0); throw error(scenario === 'unknown' ? privateName : 'NotReadableError'); } }; } }; } }; }); vi.stubGlobal('showDirectoryPicker', picker); const container = document.createElement('div'); container.setAttribute('data-theme', theme); const root = createRoot(container); try { await act(async () => root.render()); const button = Array.from(container.querySelectorAll('button')).find(item => item.textContent === 'Import GPO Folder')!; await act(async () => button.click()); expect(picker).toHaveBeenCalledWith({ mode: 'read' }); const message = container.querySelector('[role="alert"]')?.textContent; expect(message).toContain(expected); expect(message).toContain('Import GPO ZIP'); expect(message).not.toContain(privateName); expect(message).not.toContain('confidential.xml'); expect(message).not.toContain('C:/Customers'); expect(submit).not.toHaveBeenCalled(); expect(button.disabled).toBe(false); } finally { await act(async () => root.unmount()); } }); it('cancels folder selection quietly and permits another attempt', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); const submit = vi.spyOn(AssessorApiClient.prototype, 'submitGpoBackup'); vi.stubGlobal('showDirectoryPicker', vi.fn().mockRejectedValue(error('AbortError'))); const container = document.createElement('div'); const root = createRoot(container); try { await act(async () => root.render()); const button = container.querySelector('button')!; await act(async () => button.click()); expect(container.querySelector('[role="alert"]')).toBeNull(); expect(button.disabled).toBe(false); expect(submit).not.toHaveBeenCalled(); } finally { await act(async () => root.unmount()); } }); it('reports unreadable files through the directory-input fallback', async () => { vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true); vi.stubGlobal('showDirectoryPicker', undefined); const submit = vi.spyOn(AssessorApiClient.prototype, 'submitGpoBackup'); const container = document.createElement('div'); const root = createRoot(container); try { await act(async () => root.render()); const input = container.querySelector('input[type="file"]')!; const open = vi.spyOn(input as HTMLInputElement, 'click'); await act(async () => container.querySelector('button')!.click()); expect(open).toHaveBeenCalledOnce(); Object.defineProperty(input, 'files', { value: [{ webkitRelativePath: 'Synthetic/backup.xml', size: 1, arrayBuffer: async () => { throw error('NotFoundError'); } }] }); await act(async () => input.dispatchEvent(new Event('change', { bubbles: true }))); const message = container.querySelector('[role="alert"]')?.textContent; expect(message).toContain('File reading: NotFoundError (1)'); expect(message).not.toContain(privateName); expect(submit).not.toHaveBeenCalled(); } finally { await act(async () => root.unmount()); } }); });