AccountLayout.selfhosted.test.tsx151 lines · main
1import { render, screen, waitFor } from '@testing-library/react'
2import type { PropsWithChildren, ReactNode } from 'react'
3import { beforeEach, describe, expect, it, vi } from 'vitest'
4
5import AccountLayout from './AccountLayout'
6
7const { mockRouter, mockRegisterOpenMenu, mockSetMobileSheetContent } = vi.hoisted(() => ({
8 mockRouter: {
9 pathname: '/account/me',
10 push: vi.fn(),
11 },
12 mockRegisterOpenMenu: vi.fn(),
13 mockSetMobileSheetContent: vi.fn(),
14}))
15
16vi.mock('@/lib/constants', async () => {
17 const actual = await vi.importActual<Record<string, unknown>>('@/lib/constants')
18 return {
19 ...actual,
20 IS_PLATFORM: false,
21 }
22})
23
24vi.mock('next/router', () => ({
25 useRouter: () => mockRouter,
26}))
27
28vi.mock('next/head', async () => {
29 const React = await import('react')
30
31 const Head = ({ children }: { children?: ReactNode }) => {
32 React.useEffect(() => {
33 const titleElement = React.Children.toArray(children).find(
34 (child) => React.isValidElement(child) && child.type === 'title'
35 )
36
37 if (!React.isValidElement<{ children: ReactNode }>(titleElement)) return
38
39 const titleText = React.Children.toArray(titleElement.props.children).join('')
40 document.title = titleText
41 }, [children])
42
43 return null
44 }
45
46 return { default: Head }
47})
48
49vi.mock('@/hooks/custom-content/useCustomContent', () => ({
50 useCustomContent: () => ({ appTitle: 'Briven' }),
51}))
52
53vi.mock('@/hooks/misc/useIsFeatureEnabled', () => ({
54 useIsFeatureEnabled: () => false,
55}))
56
57vi.mock('@/hooks/misc/useLocalStorage', () => ({
58 useLocalStorageQuery: () => [''],
59}))
60
61vi.mock('@/hooks/misc/withAuth', () => ({
62 withAuth: <T,>(Component: T) => Component,
63}))
64
65vi.mock('@/state/app-state', () => ({
66 useAppStateSnapshot: () => ({
67 lastRouteBeforeVisitingAccountPage: '',
68 }),
69}))
70
71vi.mock('../Navigation/NavigationBar/MobileSheetContext', () => ({
72 useMobileSheet: () => ({
73 setContent: mockSetMobileSheetContent,
74 registerOpenMenu: (callback: () => void) => {
75 mockRegisterOpenMenu(callback)
76 return () => {}
77 },
78 }),
79}))
80
81vi.mock('./WithSidebar', () => ({
82 WithSidebar: ({
83 sections,
84 children,
85 }: PropsWithChildren<{
86 sections: Array<{
87 key: string
88 heading?: string
89 links: Array<{ key: string; label: string }>
90 }>
91 }>) => (
92 <div>
93 <nav>
94 {sections.map((section) => (
95 <div key={section.key}>
96 {section.heading ? <span>{section.heading}</span> : null}
97 {section.links.map((link) => (
98 <span key={link.key}>{link.label}</span>
99 ))}
100 </div>
101 ))}
102 </nav>
103 {children}
104 </div>
105 ),
106}))
107
108vi.mock('ui', () => ({
109 cn: (...classes: Array<string | false | null | undefined>) => classes.filter(Boolean).join(' '),
110}))
111
112describe('AccountLayout (self-hosted)', () => {
113 beforeEach(() => {
114 mockRouter.pathname = '/account/me'
115 mockRouter.push.mockReset()
116 mockRegisterOpenMenu.mockReset()
117 mockSetMobileSheetContent.mockReset()
118 document.title = ''
119 })
120
121 it('keeps /account/me available and shows only the Preferences link', async () => {
122 render(
123 <AccountLayout title="Preferences">
124 <div>Preferences page</div>
125 </AccountLayout>
126 )
127
128 await waitFor(() => {
129 expect(document.title).toBe('Preferences | Briven')
130 })
131
132 expect(screen.getByText('Preferences')).toBeInTheDocument()
133 expect(screen.queryByText('Access Tokens')).not.toBeInTheDocument()
134 expect(screen.queryByText('Account Settings')).not.toBeInTheDocument()
135 expect(mockRouter.push).not.toHaveBeenCalled()
136 })
137
138 it('redirects unsupported account routes back to the project dashboard', async () => {
139 mockRouter.pathname = '/account/tokens'
140
141 render(
142 <AccountLayout title="Preferences">
143 <div>Unsupported</div>
144 </AccountLayout>
145 )
146
147 await waitFor(() => {
148 expect(mockRouter.push).toHaveBeenCalledWith('/project/default')
149 })
150 })
151})