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