2a8a26687b
- Force font-size:16px !important on Styles-sheet/topbar/Sitesmith inputs inside the mobile media query so inline 12px/14px styles stop triggering iOS zoom-on-focus. - Lift sheet-open + Templates/Head Code modal-open state out of private useState into a shared MobileChromeContext (EditorShell), so Phase B can open/close sheets from outside MobilePanelBar. - Add an explicit z-index layer scale, portal TemplateModal to document.body (was trapped under the tab bar inside .topbar's stacking context), align Sitesmith to the same --z-modal layer, and make opening a sheet close any open modal. Also fix modal backdrops swallowing tab bar taps (mirrors the sheet backdrop's existing tab-bar cutout). - Drop BottomSheet's incorrect aria-modal; mobile-aware AssetsPanel empty state copy. - Tests: useIsMobile (matchMedia mock incl. legacy fallback + cleanup), MobileChromeContext invariants (one sheet open, sheet closes modals), MobilePanelBar wiring. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
153 lines
4.3 KiB
TypeScript
153 lines
4.3 KiB
TypeScript
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<typeof useMobileChrome> | null = null;
|
|
let sitesmith: ReturnType<typeof useSitesmithModal> | null = null;
|
|
|
|
const Consumer: React.FC = () => {
|
|
chrome = useMobileChrome();
|
|
sitesmith = useSitesmithModal();
|
|
return null;
|
|
};
|
|
|
|
function renderProviders() {
|
|
render(
|
|
<SitesmithProvider>
|
|
<MobileChromeProvider>
|
|
<Consumer />
|
|
</MobileChromeProvider>
|
|
</SitesmithProvider>,
|
|
);
|
|
}
|
|
|
|
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();
|
|
});
|
|
});
|