import { describe, test, expect } from 'vitest'; import React from 'react'; import { renderEditorHarness } from '../../test-utils/editorHarness'; import { EditorConfigProvider } from '../../state/EditorConfigContext'; import { PageProvider, usePages } from '../../state/PageContext'; import { SiteDesignProvider, useSiteDesign, DEFAULT_SITE_DESIGN } from '../../state/SiteDesignContext'; import { SiteDesignPanel } from './SiteDesignPanel'; /** React tracks a controlled ``'s value via a wrapped native setter -- * a plain `input.value = x` assignment doesn't go through it, so React never * sees the change and skips onChange. */ function setInputValue(input: HTMLInputElement, value: string) { const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value')!.set!; setter.call(input, value); input.dispatchEvent(new Event('input', { bubbles: true })); } /** * Real-provider integration test -- NO mocks. Exercises the actual * `replaceAllPages`/`setHeader`/`setFooter`/`resetToDefaults` implementations, * which route the component's blank trees through the real * `treeToCraftState` -> `sanitizeAiTree`/`flattenTreeForCraft` pipeline (see * `state/PageContext.tsx` and `utils/craft-tree.ts`). This is the check the * task brief calls out specifically: a tree shape that fails sanitisation * would silently fall back to an empty canvas (masking a bug) rather than * throwing, so asserting only on mock call arguments (as * `SiteDesignPanel.reset.test.tsx` does) would not catch a bad tree shape -- * the mocked assertions there only prove the component PASSED a * `{type:{resolvedName:'Container'}, nodes:[]}`-shaped object; they can't * prove that object is actually valid input to the real pipeline. */ describe('Reset Entire Site -- real PageContext/SiteDesignContext integration', () => { test('confirming produces a real, valid single blank Home page + header + footer + default design', () => { let pageCtx: ReturnType | null = null; let designCtx: ReturnType | null = null; const Probe: React.FC = () => { pageCtx = usePages(); designCtx = useSiteDesign(); return null; }; const harness = renderEditorHarness(); harness.mountChild( , ); // Dirty the design tokens first so resetToDefaults has something to undo. harness.act(() => { designCtx!.updateDesign({ primaryColor: '#000000' }); }); expect(designCtx!.design.primaryColor).toBe('#000000'); harness.act(() => { (harness.container.querySelector('[data-action="open-site-reset"]') as HTMLButtonElement).click(); }); const input = harness.container.querySelector('[data-testid="site-reset-domain"]') as HTMLInputElement; harness.act(() => { setInputValue(input, 'example.com'); }); harness.act(() => { (harness.container.querySelector('[data-action="confirm-site-reset"]') as HTMLButtonElement).click(); }); // Exactly one page, "Home", landing-page slug -- and its stored // craftState is real, parseable Craft.js state with a Container ROOT // and no children (i.e. sanitizeAiTree did NOT reject the tree and fall // back to a silent empty canvas for the wrong reason -- it's genuinely // ROOT with zero nodes because that's what we asked for). expect(pageCtx!.pages).toHaveLength(1); expect(pageCtx!.pages[0].name).toBe('Home'); expect(pageCtx!.pages[0].slug).toBe('index'); const homeState = JSON.parse(pageCtx!.pages[0].craftState!); expect(homeState.ROOT.type.resolvedName).toBe('Container'); expect(homeState.ROOT.nodes).toEqual([]); const headerState = JSON.parse(pageCtx!.headerPage.craftState!); expect(headerState.ROOT.type.resolvedName).toBe('Container'); expect(headerState.ROOT.nodes).toEqual([]); expect(headerState.ROOT.props.tag).toBe('header'); const footerState = JSON.parse(pageCtx!.footerPage.craftState!); expect(footerState.ROOT.type.resolvedName).toBe('Container'); expect(footerState.ROOT.nodes).toEqual([]); expect(footerState.ROOT.props.tag).toBe('footer'); // Design tokens are back to the defaults. expect(designCtx!.design).toEqual(DEFAULT_SITE_DESIGN); harness.unmount(); }); });