// @vitest-environment jsdom import { act, cleanup, fireEvent, render, screen } from '@testing-library/react' import { afterEach, describe, expect, it, vi } from 'vitest' import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react' import { WelcomeNotice } from '../src/client/WelcomeNotice.tsx' import type { WelcomeNoticeProps } from '../src/client/WelcomeNotice.tsx' import { WelcomeNoticeStore } from '../src/client/welcome-store.ts' import { en, zh } from '../src/client/locales.ts' import { WELCOME_NOTICE_ACK_FIELD, WELCOME_NOTICE_COPY, WELCOME_NOTICE_SETTINGS_NAMESPACE, WELCOME_NOTICE_VERSION, } from '../src/onboarding-copy.ts' afterEach(() => { cleanup() document.getElementById('root')?.remove() }) function response(value: T) { return { rpcId: 'welcome-rpc' as never, result: { ok: true as const, value } } } function mount(version?: string, mutateImpl: () => Promise = () => Promise.resolve(response({}))) { const appRoot = document.createElement('div') appRoot.id = 'root' document.body.append(appRoot) const mutate = vi.fn(mutateImpl) const api = { settings: { describe: () => Promise.resolve(response({ writable: true, hasDocument: false, namespaces: [{ ns: WELCOME_NOTICE_SETTINGS_NAMESPACE, schema: {}, value: version === undefined ? {} : { [WELCOME_NOTICE_ACK_FIELD]: version }, base: {}, user: {}, applies: 'live' as const, secrets: [], revision: 0, }], })), mutate, }, } const controller = new WelcomeNoticeStore(api as never) const complete = vi.fn() const unusedHook = (() => { throw new Error('unused standard hook') }) as never const props: WelcomeNoticeProps = { stepId: 'welcome-notice', complete, openSection: vi.fn(), useSessions: unusedHook, useWorkspaces: unusedHook, controller, useWelcome: bindSnapshotSelector(controller.store), t: key => zh[key], } return { ...render(), complete, controller, mutate, appRoot } } describe('WelcomeNotice', () => { it('uses the exact owner copy in both GUI locales', () => { expect(WELCOME_NOTICE_COPY.en).toEqual(WELCOME_NOTICE_COPY.zh) expect(en.welcomeBody).toBe(WELCOME_NOTICE_COPY.en.body) expect(zh.welcomeBody).toBe(WELCOME_NOTICE_COPY.zh.body) }) it('renders one blocking modal action and focuses the title', async () => { const h = mount() const dialog = await screen.findByRole('dialog', { name: WELCOME_NOTICE_COPY.zh.title }) for (const paragraph of WELCOME_NOTICE_COPY.zh.body.split('\n\n')) { expect(screen.getByText(paragraph, { exact: true })).toBeTruthy() } expect(dialog.querySelectorAll('p')).toHaveLength(2) expect(dialog.querySelectorAll('button')).toHaveLength(1) expect(screen.getByRole('button', { name: WELCOME_NOTICE_COPY.zh.continueLabel })).toBeTruthy() expect(document.activeElement).toBe(screen.getByRole('heading', { name: WELCOME_NOTICE_COPY.zh.title })) expect(h.appRoot.inert).toBe(true) fireEvent.keyDown(document, { key: 'Escape' }) fireEvent.click(document.querySelector('[class*="mask"]')!) expect(h.complete).not.toHaveBeenCalled() expect(screen.getByRole('dialog')).toBeTruthy() }) it('completes only after the acknowledgement write commits', async () => { const h = mount() await screen.findByRole('dialog') fireEvent.click(screen.getByRole('button', { name: WELCOME_NOTICE_COPY.zh.continueLabel })) await act(async () => { await Promise.resolve() }) expect(h.mutate).toHaveBeenCalledOnce() expect(h.complete).toHaveBeenCalledOnce() }) it('skips itself when this exact version was already acknowledged', async () => { const h = mount(WELCOME_NOTICE_VERSION) await act(async () => { await h.controller.load() }) expect(screen.queryByRole('dialog')).toBeNull() expect(h.complete).toHaveBeenCalledOnce() }) it('keeps the sole action disabled while saving and reports a refused write', async () => { let resolveWrite!: (value: unknown) => void const write = new Promise((resolve) => { resolveWrite = resolve }) const h = mount(undefined, () => write) await screen.findByRole('dialog') const action = screen.getByRole('button', { name: WELCOME_NOTICE_COPY.zh.continueLabel }) fireEvent.click(action) expect(action.disabled).toBe(true) resolveWrite({ rpcId: 'welcome-refused' as never, result: { ok: false, error: { code: 'settings-rejected', message: 'read only', details: { ns: WELCOME_NOTICE_SETTINGS_NAMESPACE }, }, }, }) expect((await screen.findByRole('alert')).textContent).toBe(zh.welcomeError) expect(h.complete).not.toHaveBeenCalled() }) })