import { render, screen, waitFor } from '@testing-library/react' import { LOCAL_STORAGE_KEYS } from 'common' import type { ReactNode } from 'react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { MobileSheetProvider } from '../Navigation/NavigationBar/MobileSheetContext' import { ProjectLayout } from './index' import { STUDIO_PAGE_TITLE_SEPARATOR } from '@/lib/page-title' const { mockRouter, mockSetSelectedDatabaseId, mockSetMobileMenuOpen } = vi.hoisted(() => ({ mockRouter: { pathname: '/project/[ref]/observability/query-performance', asPath: '/project/default/observability/query-performance', push: vi.fn(), replace: vi.fn(), }, mockSetSelectedDatabaseId: vi.fn(), mockSetMobileMenuOpen: vi.fn(), })) const { mockAddBanner, mockDismissBanner, mockProjectState, mockResourceWarningsState, mockBannerDismissedState, mockUseLocalStorageQuery, } = vi.hoisted(() => ({ mockAddBanner: vi.fn(), mockDismissBanner: vi.fn(), mockProjectState: { current: { ref: 'default', name: 'Project 1', status: 'ACTIVE_HEALTHY', postgrestStatus: 'ONLINE', infra_compute_size: undefined as string | undefined, integration_source: null as string | null, }, }, mockResourceWarningsState: { current: undefined as any[] | undefined }, mockBannerDismissedState: { current: false }, mockUseLocalStorageQuery: vi.fn(), })) vi.mock('next/router', () => ({ useRouter: () => mockRouter, })) vi.mock('next/head', async () => { const React = await import('react') const Head = ({ children }: { children?: ReactNode }) => { React.useEffect(() => { const titleElement = React.Children.toArray(children).find( (child) => React.isValidElement(child) && child.type === 'title' ) if (!React.isValidElement<{ children: ReactNode }>(titleElement)) return const titleText = React.Children.toArray(titleElement.props.children).join('') document.title = titleText }, [children]) return null } return { default: Head } }) vi.mock('common', () => ({ useParams: () => ({ ref: 'default' }), mergeRefs: (..._refs: any[]) => (_value: unknown) => {}, IS_PLATFORM: false, LOCAL_STORAGE_KEYS: { FREE_MICRO_UPGRADE_BANNER_DISMISSED: (ref: string) => `free-micro-upgrade-banner-dismissed-${ref}`, PROJECT_INTEGRATION_BANNER_DISMISSED: (ref: string, integrationSource: string) => `project-integration-banner-dismissed-${ref}-${integrationSource}`, }, isFeatureEnabled: () => false, })) vi.mock('framer-motion', () => ({ AnimatePresence: ({ children }: { children: ReactNode }) => <>{children}, motion: { div: ({ children, ...props }: any) =>
{children}
, create: (Component: any) => Component, }, })) vi.mock('ui', () => ({ cn: (...classes: Array) => classes.filter(Boolean).join(' '), Alert: ({ children, ...props }: any) =>
{children}
, AlertDescription: ({ children, ...props }: any) =>
{children}
, AlertTitle: ({ children, ...props }: any) =>
{children}
, CommandInput: { displayName: 'CommandInput' }, Command: { displayName: 'Command' }, CommandGroup: { displayName: 'CommandGroup' }, CommandItem: { displayName: 'CommandItem' }, CommandList: { displayName: 'CommandList' }, LogoLoader: () =>
, ResizableHandle: (props: any) =>
, ResizablePanel: ({ children, ...props }: any) =>
{children}
, ResizablePanelGroup: ({ children, ...props }: any) =>
{children}
, Sidebar: ({ children, ...props }: any) =>
{children}
, SidebarContent: ({ children, ...props }: any) =>
{children}
, SidebarFooter: ({ children, ...props }: any) =>
{children}
, SidebarGroup: ({ children, ...props }: any) =>
{children}
, SidebarMenu: ({ children, ...props }: any) =>
{children}
, SidebarMenuButton: (props: any) =>
, SidebarMenuItem: (props: any) =>
, useIsMobile: () => false, usePanelRef: () => undefined, useSidebar: () => ({ setOpen: vi.fn() }), })) vi.mock('ui-patterns/MobileSheetNav/MobileSheetNav', () => ({ default: ({ children }: { children: ReactNode }) => <>{children}, })) vi.mock('../editors/EditorsLayout.hooks', () => ({ useEditorType: () => undefined, })) vi.mock('../MainScrollContainerContext', () => ({ useSetMainScrollContainer: () => () => {}, })) vi.mock('./BuildingState', () => ({ default: () => null })) vi.mock('./ConnectingState', () => ({ default: () => null })) vi.mock('./LoadingState', () => ({ LoadingState: () => null })) vi.mock('./PausedState/ProjectPausedState', () => ({ ProjectPausedState: () => null })) vi.mock('./PauseFailedState', () => ({ PauseFailedState: () => null })) vi.mock('./PausingState', () => ({ PausingState: () => null })) vi.mock('./ProductMenuBar', () => ({ default: ({ children }: { children: ReactNode }) => <>{children}, })) vi.mock('./ResizingState', () => ({ ResizingState: () => null })) vi.mock('./RestartingState', () => ({ default: () => null })) vi.mock('./RestoreFailedState', () => ({ RestoreFailedState: () => null })) vi.mock('./RestoringState', () => ({ RestoringState: () => null })) vi.mock('./UpgradingState', () => ({ UpgradingState: () => null })) vi.mock('@/components/interfaces/BranchManagement/CreateBranchModal', () => ({ CreateBranchModal: () => null, })) vi.mock('@/components/interfaces/ProjectAPIDocs/ProjectAPIDocs', () => ({ ProjectAPIDocs: () => null, })) vi.mock('@/components/ui/ResourceExhaustionWarningBanner/ResourceExhaustionWarningBanner', () => ({ ResourceExhaustionWarningBanner: () => null, })) vi.mock('@/components/ui/ButtonTooltip', () => ({ ButtonTooltip: ({ children, ...props }: any) => , })) vi.mock('@/components/ui/PartnerIcon', () => ({ default: () =>
, })) vi.mock('@/hooks/custom-content/useCustomContent', () => ({ useCustomContent: () => ({ appTitle: 'Briven' }), })) vi.mock('@/hooks/misc/useLocalStorage', () => ({ useLocalStorageQuery: (...args: unknown[]) => mockUseLocalStorageQuery(...args), })) vi.mock('@/components/ui/BannerStack/BannerStackProvider', () => ({ BANNER_ID: { FREE_MICRO_UPGRADE: 'free-micro-upgrade-banner' }, useBannerStack: () => ({ addBanner: mockAddBanner, dismissBanner: mockDismissBanner, banners: [], }), })) vi.mock('@/components/ui/BannerStack/Banners/BannerFreeMicroUpgrade', () => ({ BannerFreeMicroUpgrade: () => null, })) vi.mock('@/data/usage/resource-warnings-query', () => ({ useResourceWarningsQuery: () => ({ data: mockResourceWarningsState.current }), })) vi.mock('@/hooks/misc/useSelectedOrganization', () => ({ useSelectedOrganizationQuery: () => ({ data: { name: 'Organization 1', slug: 'org-1' }, }), })) vi.mock('@/hooks/misc/useSelectedProject', () => ({ useSelectedProjectQuery: () => ({ data: mockProjectState.current }), })) vi.mock('@/hooks/misc/withAuth', () => ({ withAuth: (Component: any) => Component, })) vi.mock('@/hooks/ui/useFlag', () => ({ usePHFlag: () => undefined, })) vi.mock('@/state/app-state', () => ({ useAppStateSnapshot: () => ({ mobileMenuOpen: false, showSidebar: false, setMobileMenuOpen: mockSetMobileMenuOpen, }), })) vi.mock('@/state/database-selector', () => ({ useDatabaseSelectorStateSnapshot: () => ({ setSelectedDatabaseId: mockSetSelectedDatabaseId, }), })) const renderLayout = () => render(
) describe('ProjectLayout title', () => { beforeEach(() => { mockRouter.pathname = '/project/[ref]/observability/query-performance' mockRouter.asPath = '/project/default/observability/query-performance' document.title = '' mockProjectState.current = { ref: 'default', name: 'Project 1', status: 'ACTIVE_HEALTHY', postgrestStatus: 'ONLINE', infra_compute_size: undefined, integration_source: null, } mockBannerDismissedState.current = false mockUseLocalStorageQuery.mockImplementation(() => [mockBannerDismissedState.current, vi.fn()]) }) afterEach(() => { vi.clearAllMocks() document.title = '' }) it('sets a composed document title and deduplicates identical section/surface labels', async () => { render(
Page Content
) await waitFor(() => { expect(document.title).toBe( ['Settings', 'Project 1', 'Organization 1', 'Briven'].join(STUDIO_PAGE_TITLE_SEPARATOR) ) }) }) it('prefers entity-first browserTitle metadata when provided', async () => { render(
Page Content
) await waitFor(() => { expect(document.title).toBe( ['users', 'Tables', 'Database', 'Project 1', 'Organization 1', 'Briven'].join( STUDIO_PAGE_TITLE_SEPARATOR ) ) }) }) it('renders the Stripe project banner across project surfaces when the selected project is Stripe-connected', () => { mockProjectState.current = { ...mockProjectState.current, integration_source: 'stripe_projects', } renderLayout() expect(screen.getByText('This project is connected to Stripe')).toBeTruthy() expect( screen.getByText('Changes made here may affect your connected Stripe project.') ).toBeTruthy() expect(screen.getByTestId('partner-icon')).toBeTruthy() }) it('uses a project-specific dismiss key for the Stripe project banner', () => { mockProjectState.current = { ...mockProjectState.current, integration_source: 'stripe_projects', } renderLayout() expect(mockUseLocalStorageQuery).toHaveBeenCalledWith( LOCAL_STORAGE_KEYS.PROJECT_INTEGRATION_BANNER_DISMISSED('default', 'stripe_projects'), false ) }) }) describe('FREE_MICRO_UPGRADE banner', () => { beforeEach(() => { mockRouter.pathname = '/project/[ref]' mockRouter.asPath = '/project/default' mockProjectState.current = { ref: 'default', name: 'Project 1', status: 'ACTIVE_HEALTHY', postgrestStatus: 'ONLINE', infra_compute_size: 'nano', integration_source: null, } mockResourceWarningsState.current = [ { project: 'default', cpu_exhaustion: true, memory_and_swap_exhaustion: false, disk_space_exhaustion: false, }, ] mockBannerDismissedState.current = false }) afterEach(() => { vi.clearAllMocks() mockRouter.pathname = '/project/[ref]/observability/query-performance' mockRouter.asPath = '/project/default/observability/query-performance' mockProjectState.current = { ref: 'default', name: 'Project 1', status: 'ACTIVE_HEALTHY', postgrestStatus: 'ONLINE', infra_compute_size: undefined, integration_source: null, } mockResourceWarningsState.current = undefined mockBannerDismissedState.current = false }) it('calls addBanner when project is nano and compute is near exhaustion', async () => { renderLayout() await waitFor(() => { expect(mockAddBanner).toHaveBeenCalledWith( expect.objectContaining({ id: 'free-micro-upgrade-banner' }) ) }) }) it('calls dismissBanner when banner was previously dismissed', async () => { mockBannerDismissedState.current = true renderLayout() await waitFor(() => { expect(mockDismissBanner).toHaveBeenCalledWith('free-micro-upgrade-banner') }) expect(mockAddBanner).not.toHaveBeenCalled() }) it('calls dismissBanner when compute warnings are cleared', async () => { mockResourceWarningsState.current = [ { project: 'default', cpu_exhaustion: false, memory_and_swap_exhaustion: false, disk_space_exhaustion: false, }, ] renderLayout() await waitFor(() => { expect(mockDismissBanner).toHaveBeenCalledWith('free-micro-upgrade-banner') }) expect(mockAddBanner).not.toHaveBeenCalled() }) it('calls dismissBanner when project is not nano compute', async () => { mockProjectState.current = { ...mockProjectState.current, infra_compute_size: 'micro' } renderLayout() await waitFor(() => { expect(mockDismissBanner).toHaveBeenCalledWith('free-micro-upgrade-banner') }) expect(mockAddBanner).not.toHaveBeenCalled() }) })