appearance-row.spec.tsx 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475
  1. // @vitest-environment jsdom
  2. /** AppearanceRow behavior: three cubes, selection follows the persisted
  3. * preference, clicks drive setTheme. */
  4. import { afterEach, describe, expect, it, vi } from 'vitest'
  5. import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
  6. import { createSnapshotStore, type SessionListState, type WorkspaceListState } from '@deepseek-ai/dsh-client-runtime/client'
  7. import { bindSnapshotSelector } from '@deepseek-ai/dsh-client-web-react'
  8. import { AppearanceRow } from '../src/client/AppearanceRow.tsx'
  9. import type { AppearanceRowComponentProps } from '../src/client/AppearanceRow.tsx'
  10. import { createAppearanceRowStore } from '../src/client/settings-store.ts'
  11. import type { ThemePreference } from '../src/client/index.ts'
  12. afterEach(cleanup)
  13. const COPY: Record<string, string> = {
  14. 'appearance.title': 'Appearance',
  15. 'appearance.light': 'Light',
  16. 'appearance.dark': 'Dark',
  17. 'appearance.system': 'System',
  18. }
  19. /** Empty global standard-kit hooks (the row reads neither). */
  20. function emptySessions() {
  21. const store = createSnapshotStore<SessionListState>(
  22. { ids: [], byId: {}, current: undefined, phase: 'ready' })
  23. return bindSnapshotSelector(store)
  24. }
  25. function emptyWorkspaces() {
  26. const store = createSnapshotStore<WorkspaceListState>({
  27. items: [], state: 'idle', phase: 'ready', error: null,
  28. baselinesReady: true, recentWorkspaceId: undefined,
  29. })
  30. return bindSnapshotSelector(store)
  31. }
  32. function mount(preference: ThemePreference = 'system') {
  33. // Real store instance — the sanctioned zero-machinery path for tests.
  34. const store = createAppearanceRowStore().create()
  35. store.actions.sync(preference, 0)
  36. const setTheme = vi.fn()
  37. const props: AppearanceRowComponentProps = {
  38. useSessions: emptySessions(),
  39. useWorkspaces: emptyWorkspaces(),
  40. useStore: bindSnapshotSelector(store),
  41. actions: store.actions,
  42. t: (key: string) => COPY[key] ?? key,
  43. setTheme,
  44. }
  45. render(<AppearanceRow {...props} />)
  46. return { store, setTheme }
  47. }
  48. const pressed = (name: RegExp): string | null =>
  49. screen.getByRole('button', { name }).getAttribute('aria-pressed')
  50. describe('AppearanceRow', () => {
  51. it('renders the title and three cubes with the preference cube selected', () => {
  52. mount('dark')
  53. expect(screen.getByText('Appearance')).toBeDefined()
  54. expect(pressed(/Dark/)).toBe('true')
  55. expect(pressed(/Light/)).toBe('false')
  56. expect(pressed(/System/)).toBe('false')
  57. })
  58. it('click drives setTheme; selection follows the store mirror, not the click echo', () => {
  59. const b = mount('dark')
  60. fireEvent.click(screen.getByRole('button', { name: /Light/ }))
  61. expect(b.setTheme).toHaveBeenCalledWith('light')
  62. // No store write yet: selection is unchanged.
  63. expect(pressed(/Dark/)).toBe('true')
  64. act(() => { b.store.actions.sync('light', 1) })
  65. expect(pressed(/Light/)).toBe('true')
  66. expect(pressed(/Dark/)).toBe('false')
  67. })
  68. })