86 lines
3.0 KiB
TypeScript
86 lines
3.0 KiB
TypeScript
import { describe, test, expect, vi, afterEach } from 'vitest';
|
|||
|
|
import React from 'react';
|
||
|
|
import { createRoot, Root } from 'react-dom/client';
|
||
|
|
import { act } from 'react-dom/test-utils';
|
||
|
|
import { HeadCodeModal } from './HeadCodeModal';
|
||
|
|
import { SiteDesignProvider, useSiteDesign } from '../../state/SiteDesignContext';
|
||
|
|
|
||
|
|
/* ---------- DOM test harness -- same react-dom/client + `act` pattern used
|
||
|
|
throughout src/ui/*.test.tsx (no @testing-library/react in this repo). ---------- */
|
||
|
|
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);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
afterEach(() => {
|
||
|
|
if (container) {
|
||
|
|
act(() => { root.unmount(); });
|
||
|
|
container.remove();
|
||
|
|
}
|
||
|
|
document.body.style.overflow = '';
|
||
|
|
});
|
||
|
|
|
||
|
|
// Exposes the current headCode so assertions can read it back after a
|
||
|
|
// simulated edit -- CodeEditor writes through `updateDesign`, this just
|
||
|
|
// surfaces the result.
|
||
|
|
function Harness({ onReady }: { onReady: (headCode: string) => void }) {
|
||
|
|
const { design } = useSiteDesign();
|
||
|
|
onReady(design.headCode);
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
describe('HeadCodeModal', () => {
|
||
|
|
// HeadCodeModal portals its content to document.body (see the comment in
|
||
|
|
// HeadCodeModal.tsx), so the rendered DOM lives outside `container` --
|
||
|
|
// query document.body instead.
|
||
|
|
|
||
|
|
test('renders the CodeEditor (fallback textarea path) seeded with the current headCode', () => {
|
||
|
|
render(
|
||
|
|
<SiteDesignProvider>
|
||
|
|
<HeadCodeModal open onClose={vi.fn()} />
|
||
|
|
</SiteDesignProvider>,
|
||
|
|
);
|
||
|
|
// CodeMirror loads via async dynamic import (see CodeEditor.test.tsx);
|
||
|
|
// synchronously after mount the fallback textarea is what's live.
|
||
|
|
const textarea = document.body.querySelector<HTMLTextAreaElement>('[data-testid="code-editor-fallback"]');
|
||
|
|
expect(textarea).not.toBeNull();
|
||
|
|
expect(textarea!.value).toBe('');
|
||
|
|
expect(textarea!.dataset.language).toBe('html');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('typing in the editor writes through to SiteDesignContext.headCode', () => {
|
||
|
|
let latestHeadCode = '';
|
||
|
|
render(
|
||
|
|
<SiteDesignProvider>
|
||
|
|
<HeadCodeModal open onClose={vi.fn()} />
|
||
|
|
<Harness onReady={(v) => { latestHeadCode = v; }} />
|
||
|
|
</SiteDesignProvider>,
|
||
|
|
);
|
||
|
|
|
||
|
|
const textarea = document.body.querySelector<HTMLTextAreaElement>('[data-testid="code-editor-fallback"]')!;
|
||
|
|
act(() => {
|
||
|
|
const setter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, 'value')!.set!;
|
||
|
|
setter.call(textarea, '<meta name="x" content="y">');
|
||
|
|
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
||
|
|
});
|
||
|
|
|
||
|
|
expect(latestHeadCode).toBe('<meta name="x" content="y">');
|
||
|
|
});
|
||
|
|
|
||
|
|
test('does not render when closed', () => {
|
||
|
|
render(
|
||
|
|
<SiteDesignProvider>
|
||
|
|
<HeadCodeModal open={false} onClose={vi.fn()} />
|
||
|
|
</SiteDesignProvider>,
|
||
|
|
);
|
||
|
|
expect(document.body.querySelector('[data-testid="code-editor-fallback"]')).toBeNull();
|
||
|
|
});
|
||
|
|
});
|