import type { ApiSettings } from './api-settings-dialog'; import { I18nProvider } from '@/lib/i18n'; import { renderInClientDom } from '@/test-utils/react-dom'; import assert from 'node:assert/strict'; import { after, before, describe, it } from 'node:test'; const emptySettings: ApiSettings = { apiKey: '', baseUrl: '' }; const savedSettings: ApiSettings = { apiKey: 'saved-key', baseUrl: 'https://example.test/v1' }; const replacementSettings: ApiSettings = { apiKey: 'replacement-key', baseUrl: 'https://replacement.example/v1' }; type ApiSettingsDialogComponent = typeof import('./api-settings-dialog').ApiSettingsDialog; let ApiSettingsDialog: ApiSettingsDialogComponent; let sharedView: Awaited>; let ownerDocument: Document; async function waitForActiveElement(target: HTMLElement, timeoutMs = 1000) { const deadline = Date.now() + timeoutMs; while (target.ownerDocument.activeElement !== target) { const remainingMs = deadline - Date.now(); if (remainingMs <= 0) return false; await new Promise((resolve) => setTimeout(resolve, Math.min(10, remainingMs))); } return true; } before(async () => { sharedView = await renderInClientDom(
); ownerDocument = sharedView.container.ownerDocument; ({ ApiSettingsDialog } = await import('./api-settings-dialog')); }); after(async () => { await sharedView?.cleanup(); }); function renderDialog( isOpen: boolean, settings: ApiSettings, options: { onOpenChange?: (open: boolean) => void; onSave?: (nextSettings: ApiSettings) => void; getReturnFocusTarget?: () => HTMLButtonElement | null; } = {} ) { return ( {})} settings={settings} onSave={options.onSave ?? (() => {})} getReturnFocusTarget={options.getReturnFocusTarget ?? (() => null)} /> ); } describe('ApiSettingsDialog draft state', { concurrency: false }, () => { it('syncs restored settings, saves the current draft, and clears settings with stable dialog lifecycle', async () => { const savedValues: ApiSettings[] = []; const openChanges: boolean[] = []; await sharedView.render(renderDialog(false, emptySettings)); await sharedView.render(renderDialog(false, savedSettings)); await sharedView.render(renderDialog(true, savedSettings)); let apiKeyInput = ownerDocument.querySelector('#api-key-input'); let baseUrlInput = ownerDocument.querySelector('#api-base-url-input'); assert.ok(apiKeyInput, 'missing API key input'); assert.ok(baseUrlInput, 'missing API URL input'); assert.equal(apiKeyInput.value, savedSettings.apiKey); assert.equal(baseUrlInput.value, savedSettings.baseUrl); await sharedView.render(renderDialog(true, replacementSettings)); apiKeyInput = ownerDocument.querySelector('#api-key-input'); assert.ok(apiKeyInput, 'missing API key input after external settings update'); assert.equal(apiKeyInput.value, savedSettings.apiKey); await sharedView.render(renderDialog(false, replacementSettings)); await sharedView.render(renderDialog(true, replacementSettings)); apiKeyInput = ownerDocument.querySelector('#api-key-input'); baseUrlInput = ownerDocument.querySelector('#api-base-url-input'); assert.ok(apiKeyInput, 'missing reopened API key input'); assert.ok(baseUrlInput, 'missing reopened API URL input'); assert.equal(apiKeyInput.value, replacementSettings.apiKey); assert.equal(baseUrlInput.value, replacementSettings.baseUrl); await sharedView.render(renderDialog(false, savedSettings)); await sharedView.render( renderDialog(true, savedSettings, { onOpenChange: (open) => openChanges.push(open), onSave: (settings) => savedValues.push(settings) }) ); const currentApiKeyInput = ownerDocument.querySelector('#api-key-input'); const saveButton = [...ownerDocument.querySelectorAll('button')].find( (button) => button.textContent === '保存' ); assert.ok(currentApiKeyInput, 'missing API key input'); assert.ok(saveButton, 'missing save button'); await sharedView.click(saveButton); assert.deepEqual(savedValues, [savedSettings]); await sharedView.render( renderDialog(false, savedSettings, { onOpenChange: (open) => openChanges.push(open), onSave: (settings) => savedValues.push(settings) }) ); await new Promise((resolve) => setTimeout(resolve, 500)); assert.deepEqual(openChanges, []); const focusTarget = ownerDocument.createElement('button'); ownerDocument.body.append(focusTarget); await sharedView.render( renderDialog(true, savedSettings, { onSave: (settings) => savedValues.push(settings), getReturnFocusTarget: () => focusTarget }) ); const clearButton = [...ownerDocument.querySelectorAll('button')].find( (button) => button.textContent === '清空' ); assert.ok(clearButton, 'missing clear button'); await sharedView.click(clearButton); assert.deepEqual(savedValues, [savedSettings, emptySettings]); await sharedView.render( renderDialog(false, savedSettings, { onSave: (settings) => savedValues.push(settings), getReturnFocusTarget: () => focusTarget }) ); const focusWasRestored = await waitForActiveElement(focusTarget); focusTarget.remove(); assert.equal(focusWasRestored, true, 'dialog focus was not restored'); }); });