fix(builder): mobile-A2 hardening -- 16px inputs, shared sheet/modal chrome, z-scale + portal
- 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>
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
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();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user