LocalDropdown.test.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158
  1. import { render, screen } from '@testing-library/react'
  2. import userEvent from '@testing-library/user-event'
  3. import type { MouseEventHandler, ReactElement, ReactNode } from 'react'
  4. import { describe, expect, it, vi } from 'vitest'
  5. import { LocalDropdown } from './LocalDropdown'
  6. const { mockRouter, mockSetTheme, mockSetLastRoute, mockToggleFeaturePreviewModal } = vi.hoisted(
  7. () => ({
  8. mockRouter: {
  9. pathname: '/project/[ref]/editor',
  10. asPath: '/project/default/editor',
  11. },
  12. mockSetTheme: vi.fn(),
  13. mockSetLastRoute: vi.fn(),
  14. mockToggleFeaturePreviewModal: vi.fn(),
  15. })
  16. )
  17. vi.mock('next/router', () => ({
  18. useRouter: () => mockRouter,
  19. }))
  20. vi.mock('next/link', () => ({
  21. default: ({
  22. href,
  23. children,
  24. onClick,
  25. }: {
  26. href: string
  27. children: ReactNode
  28. onClick?: MouseEventHandler<HTMLAnchorElement>
  29. }) => (
  30. <a href={href} onClick={onClick}>
  31. {children}
  32. </a>
  33. ),
  34. }))
  35. vi.mock('next-themes', () => ({
  36. useTheme: () => ({
  37. theme: 'dark',
  38. setTheme: mockSetTheme,
  39. }),
  40. }))
  41. vi.mock('@/state/app-state', () => ({
  42. useAppStateSnapshot: () => ({
  43. setLastRouteBeforeVisitingAccountPage: mockSetLastRoute,
  44. }),
  45. }))
  46. vi.mock('@/components/ui/ProfileImage', () => ({
  47. ProfileImage: () => <div>Avatar</div>,
  48. }))
  49. vi.mock('./App/FeaturePreview/FeaturePreviewContext', () => ({
  50. useFeaturePreviewModal: () => ({
  51. toggleFeaturePreviewModal: mockToggleFeaturePreviewModal,
  52. }),
  53. }))
  54. vi.mock('@/lib/telemetry/track', () => ({ useTrack: () => vi.fn() }))
  55. vi.mock('ui', async () => {
  56. const React = await import('react')
  57. return {
  58. Button: ({
  59. children,
  60. ...props
  61. }: React.ButtonHTMLAttributes<HTMLButtonElement> & { children?: ReactNode }) => (
  62. <button {...props}>{children}</button>
  63. ),
  64. cn: (...classes: Array<string | false | null | undefined>) => classes.filter(Boolean).join(' '),
  65. DropdownMenu: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  66. DropdownMenuTrigger: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  67. DropdownMenuContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  68. DropdownMenuGroup: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  69. DropdownMenuItem: ({
  70. children,
  71. asChild,
  72. onClick,
  73. onSelect,
  74. }: {
  75. children: ReactNode
  76. asChild?: boolean
  77. onClick?: () => void
  78. onSelect?: () => void
  79. }) =>
  80. asChild ? (
  81. <div>{children}</div>
  82. ) : (
  83. <button
  84. onClick={() => {
  85. onClick?.()
  86. onSelect?.()
  87. }}
  88. >
  89. {children}
  90. </button>
  91. ),
  92. DropdownMenuLabel: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  93. DropdownMenuSeparator: () => <hr />,
  94. DropdownMenuRadioGroup: ({
  95. children,
  96. onValueChange,
  97. }: {
  98. children: ReactNode
  99. onValueChange: (value: string) => void
  100. }) => (
  101. <div>
  102. {React.Children.map(children, (child: ReactNode) =>
  103. React.isValidElement<{ value: string; onClick?: () => void }>(child)
  104. ? React.cloneElement(child, {
  105. onClick: () => onValueChange(child.props.value),
  106. })
  107. : (child as ReactElement)
  108. )}
  109. </div>
  110. ),
  111. DropdownMenuRadioItem: ({
  112. children,
  113. onClick,
  114. }: {
  115. children: ReactNode
  116. onClick?: () => void
  117. }) => <button onClick={onClick}>{children}</button>,
  118. Tooltip: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  119. TooltipContent: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  120. TooltipTrigger: ({ children }: { children: ReactNode }) => <div>{children}</div>,
  121. singleThemes: [
  122. { value: 'dark', name: 'Dark' },
  123. { value: 'light', name: 'Light' },
  124. ],
  125. }
  126. })
  127. describe('LocalDropdown', () => {
  128. it('shows Preferences, removes Command menu, and keeps theme controls wired', async () => {
  129. const user = userEvent.setup()
  130. render(<LocalDropdown />)
  131. expect(screen.getByText('Preferences')).toBeInTheDocument()
  132. expect(screen.queryByText('Command menu')).not.toBeInTheDocument()
  133. expect(screen.getByText('Theme')).toBeInTheDocument()
  134. await user.click(screen.getByText('Preferences'))
  135. expect(mockSetLastRoute).toHaveBeenCalledWith('/project/default/editor')
  136. await user.click(screen.getByText('Feature previews'))
  137. expect(mockToggleFeaturePreviewModal).toHaveBeenCalledWith(true)
  138. await user.click(screen.getByText('Light'))
  139. expect(mockSetTheme).toHaveBeenCalledWith('light')
  140. })
  141. })