98 lines
4.5 KiB
TypeScript
98 lines
4.5 KiB
TypeScript
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 `<input>`'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<typeof usePages> | null = null;
|
||
|
|
let designCtx: ReturnType<typeof useSiteDesign> | null = null;
|
||
|
|
const Probe: React.FC = () => {
|
||
|
|
pageCtx = usePages();
|
||
|
|
designCtx = useSiteDesign();
|
||
|
|
return null;
|
||
|
|
};
|
||
|
|
|
||
|
|
const harness = renderEditorHarness();
|
||
|
|
harness.mountChild(
|
||
|
|
<EditorConfigProvider config={{
|
||
|
|
user: 'u', apiUrl: '/api', csrfToken: 't', siteId: 1,
|
||
|
|
siteDomain: 'example.com', siteName: 'Example', backUrl: '/', isRoot: false,
|
||
|
|
}}>
|
||
|
|
<PageProvider>
|
||
|
|
<SiteDesignProvider>
|
||
|
|
<Probe />
|
||
|
|
<SiteDesignPanel />
|
||
|
|
</SiteDesignProvider>
|
||
|
|
</PageProvider>
|
||
|
|
</EditorConfigProvider>,
|
||
|
|
);
|
||
|
|
|
||
|
|
// 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();
|
||
|
|
});
|
||
|
|
});
|