import { describe, test, expect } from 'vitest'; import React from 'react'; import { createRoot, Root } from 'react-dom/client'; import { act } from 'react-dom/test-utils'; import { MobileChromeProvider, useMobileChrome, MobileSheetTab } from './MobileChromeContext'; import { SitesmithProvider, useSitesmithModal } from './SitesmithContext'; /** * Mobile-A2 (review item 5): covers the two invariants item 3 depends on -- * (1) only one sheet is ever open ("tab-bar-always-wins", Phase A's existing * behavior, now backed by the shared context instead of a private * useState), and (2) opening a sheet closes any open full-screen modal * (Templates / Head Code / Sitesmith), so a sheet is never shown stacked * underneath one. * * DOM-harness pattern mirrors PageContext.pure-updaters.test.tsx: bare * createRoot + act, a Consumer component that stashes the hook result on an * outer variable so assertions can run outside of render. */ let container: HTMLDivElement; let root: Root; function render(ui: React.ReactElement) { container = document.createElement('div'); document.body.appendChild(container); act(() => { root = createRoot(container); root.render(ui); }); } function unmount() { act(() => { root.unmount(); }); container.remove(); } let chrome: ReturnType | null = null; let sitesmith: ReturnType | null = null; const Consumer: React.FC = () => { chrome = useMobileChrome(); sitesmith = useSitesmithModal(); return null; }; function renderProviders() { render( , ); } describe('MobileChromeContext', () => { test('only one sheet is ever open at a time', () => { renderProviders(); act(() => chrome!.openSheet('blocks')); expect(chrome!.activeSheet).toBe('blocks'); // Opening a different sheet replaces the previous one -- never both. act(() => chrome!.openSheet('styles')); expect(chrome!.activeSheet).toBe('styles'); act(() => chrome!.closeSheet()); expect(chrome!.activeSheet).toBeNull(); unmount(); }); test('openSheet is idempotent-safe: opening the same tab twice stays open (caller owns toggle-to-close)', () => { renderProviders(); act(() => chrome!.openSheet('pages')); expect(chrome!.activeSheet).toBe('pages'); act(() => chrome!.openSheet('pages')); expect(chrome!.activeSheet).toBe('pages'); unmount(); }); test('openSheet closes an open Templates modal', () => { renderProviders(); act(() => chrome!.setTemplateModalOpen(true)); expect(chrome!.templateModalOpen).toBe(true); act(() => chrome!.openSheet('blocks')); expect(chrome!.templateModalOpen).toBe(false); expect(chrome!.activeSheet).toBe('blocks'); unmount(); }); test('openSheet closes an open Head Code modal', () => { renderProviders(); act(() => chrome!.setHeadCodeModalOpen(true)); expect(chrome!.headCodeModalOpen).toBe(true); act(() => chrome!.openSheet('assets')); expect(chrome!.headCodeModalOpen).toBe(false); expect(chrome!.activeSheet).toBe('assets'); unmount(); }); test('openSheet closes an open Sitesmith modal', () => { renderProviders(); act(() => sitesmith!.open()); expect(sitesmith!.isOpen).toBe(true); act(() => chrome!.openSheet('layers')); // sitesmith is a stale snapshot from before the re-render the `act` // above triggered; re-read via the live ref after settling. expect(sitesmith!.isOpen).toBe(false); expect(chrome!.activeSheet).toBe('layers'); unmount(); }); test('closeSheet does not disturb modal state', () => { renderProviders(); act(() => chrome!.openSheet('styles')); act(() => chrome!.closeSheet()); expect(chrome!.activeSheet).toBeNull(); expect(chrome!.templateModalOpen).toBe(false); expect(chrome!.headCodeModalOpen).toBe(false); unmount(); }); test('sheet tabs are exactly the five mobile panels', () => { const tabs: MobileSheetTab[] = ['blocks', 'pages', 'layers', 'assets', 'styles']; renderProviders(); tabs.forEach((tab) => { act(() => chrome!.openSheet(tab)); expect(chrome!.activeSheet).toBe(tab); }); unmount(); }); });