index.test.tsx197 lines · main
1import { act, screen, waitFor } from '@testing-library/react'
2import { ResizablePanel, ResizablePanelGroup } from 'ui'
3import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
4
5import { LayoutSidebar } from './index'
6import { LayoutSidebarProvider, SIDEBAR_KEYS } from './LayoutSidebarProvider'
7import { MobileSheetProvider } from '@/components/layouts/Navigation/NavigationBar/MobileSheetContext'
8import { sidebarManagerState } from '@/state/sidebar-manager-state'
9import { render } from '@/tests/helpers'
10import { routerMock } from '@/tests/lib/route-mock'
11
12vi.mock('@/components/ui/AIAssistantPanel/AIAssistant', () => ({
13 AIAssistant: () => <div data-testid="ai-assistant-sidebar">AI Assistant</div>,
14}))
15
16vi.mock('@/components/ui/EditorPanel/EditorPanel', () => ({
17 EditorPanel: () => <div data-testid="editor-panel-sidebar">Editor Panel</div>,
18}))
19
20vi.mock('@/components/ui/AdvisorPanel/AdvisorPanel', () => ({
21 AdvisorPanel: () => <div data-testid="advisor-panel-sidebar">Advisor Panel</div>,
22}))
23
24vi.mock('nuqs', async () => {
25 let queryValue = 'ai-assistant'
26 return {
27 useQueryState: () => [queryValue, (v: string) => (queryValue = v)],
28 parseAsString: () => {},
29 }
30})
31
32const mockProject = {
33 id: 1,
34 ref: 'default',
35 name: 'Project 1',
36 status: 'ACTIVE_HEALTHY' as const,
37 organization_id: 1,
38 cloud_provider: 'AWS',
39 region: 'us-east-1',
40 inserted_at: new Date().toISOString(),
41 subscription_id: 'subscription-1',
42 db_host: 'db.supabase.co',
43 is_branch_enabled: false,
44 is_physical_backups_enabled: false,
45 restUrl: 'https://project-1.supabase.co',
46}
47
48let mockProjectData: typeof mockProject | undefined = mockProject
49
50vi.mock('@/hooks/misc/useSelectedProject', () => ({
51 useSelectedProjectQuery: () => {
52 // Access the variable at runtime when the function is called
53 return {
54 data: mockProjectData,
55 }
56 },
57}))
58
59vi.mock('@/hooks/misc/useSelectedOrganization', () => ({
60 useSelectedOrganizationQuery: () => ({
61 data: {
62 id: 1,
63 name: 'Organization 1',
64 slug: 'test-org',
65 plan: { id: 'free', name: 'Free' },
66 managed_by: 'briven',
67 is_owner: true,
68 billing_email: 'billing@example.com',
69 billing_partner: null,
70 usage_billing_enabled: false,
71 stripe_customer_id: 'stripe-1',
72 subscription_id: 'subscription-1',
73 organization_requires_mfa: false,
74 opt_in_tags: [],
75 restriction_status: null,
76 restriction_data: null,
77 organization_missing_address: false,
78 },
79 }),
80}))
81
82vi.mock('@/data/telemetry/send-event-mutation', () => ({
83 useSendEventMutation: () => ({
84 mutate: vi.fn(),
85 }),
86}))
87
88const resetSidebarManagerState = () => {
89 Object.keys(sidebarManagerState.sidebars).forEach((id) => {
90 sidebarManagerState.unregisterSidebar(id)
91 })
92 sidebarManagerState.closeActive()
93}
94
95describe('LayoutSidebar', () => {
96 beforeEach(() => {
97 routerMock.setCurrentUrl('/projects/default')
98 })
99
100 afterEach(() => {
101 resetSidebarManagerState()
102 localStorage.clear()
103 vi.clearAllMocks()
104 })
105
106 const renderSidebar = () =>
107 render(
108 <ResizablePanelGroup orientation="horizontal">
109 <ResizablePanel>
110 <div />
111 </ResizablePanel>
112 <LayoutSidebarProvider>
113 <MobileSheetProvider>
114 <LayoutSidebar />
115 </MobileSheetProvider>
116 </LayoutSidebarProvider>
117 </ResizablePanelGroup>
118 )
119
120 it('does not render when there is no active sidebar', () => {
121 renderSidebar()
122
123 expect(screen.queryByTestId('ai-assistant-sidebar')).toBeNull()
124 })
125
126 it('renders the active sidebar content when toggled on', async () => {
127 renderSidebar()
128
129 await waitFor(() => {
130 expect(sidebarManagerState.sidebars[SIDEBAR_KEYS.AI_ASSISTANT]).toBeDefined()
131 })
132
133 act(() => {
134 sidebarManagerState.toggleSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
135 })
136
137 const sidebar = await screen.findByTestId('ai-assistant-sidebar')
138 expect(sidebar).toBeTruthy()
139 })
140
141 describe('at organization level', () => {
142 beforeEach(() => {
143 routerMock.setCurrentUrl('/org/default')
144 // Set project to undefined to simulate org-level (no project)
145 mockProjectData = undefined
146 })
147
148 afterEach(() => {
149 // Reset to project data for other tests
150 mockProjectData = mockProject
151 })
152
153 it('does not register project-related sidebars when no project is available', async () => {
154 renderSidebar()
155
156 // Wait a bit to ensure sidebars have been registered
157 await waitFor(() => {
158 // Project-related sidebars should not be registered
159 expect(sidebarManagerState.sidebars[SIDEBAR_KEYS.AI_ASSISTANT]).toBeUndefined()
160 expect(sidebarManagerState.sidebars[SIDEBAR_KEYS.EDITOR_PANEL]).toBeUndefined()
161 // Advisor panel should still be available (doesn't require project)
162 expect(sidebarManagerState.sidebars[SIDEBAR_KEYS.ADVISOR_PANEL]).toBeDefined()
163 })
164 })
165
166 it('does not render project-related sidebars even when toggled', async () => {
167 renderSidebar()
168
169 await waitFor(() => {
170 expect(sidebarManagerState.sidebars[SIDEBAR_KEYS.ADVISOR_PANEL]).toBeDefined()
171 })
172
173 // Try to toggle AI_ASSISTANT - should not work since it's not registered
174 act(() => {
175 sidebarManagerState.toggleSidebar(SIDEBAR_KEYS.AI_ASSISTANT)
176 })
177
178 // Should not render since it's not registered
179 expect(screen.queryByTestId('ai-assistant-sidebar')).toBeNull()
180 expect(screen.queryByTestId('editor-panel-sidebar')).toBeNull()
181
182 // Advisor panel should work
183 act(() => {
184 sidebarManagerState.toggleSidebar(SIDEBAR_KEYS.ADVISOR_PANEL)
185 })
186
187 expect(await screen.findByTestId('advisor-panel-sidebar')).toBeTruthy()
188 })
189 })
190
191 // [Joshen] JFYI temporarily commented this one out - I'm struggling to figure out the mocking to get this to work
192 // it('auto-opens when sidebar query param matches a registered sidebar', async () => {
193 // routerMock.setCurrentUrl(`/?sidebar=${SIDEBAR_KEYS.AI_ASSISTANT}`)
194 // renderSidebar()
195 // await screen.findByTestId('ai-assistant-sidebar')
196 // })
197})