feat(site-builder): per-page SEO meta + favicon + design-token CSS-var wiring + published-output a11y/perf
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,118 @@
|
||||
import { describe, test, expect, vi } from 'vitest';
|
||||
import React from 'react';
|
||||
import { createRoot, Root } from 'react-dom/client';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { PageProvider, usePages } from './PageContext';
|
||||
|
||||
/**
|
||||
* PKG-H: `updatePageSeo(pageId, seo)` merges `seo` fields onto the target
|
||||
* page's existing `seo` (creating it if absent), leaving every other page
|
||||
* and every other field on the target page untouched -- mirrors
|
||||
* `renamePage`'s existing merge-by-id pattern.
|
||||
*/
|
||||
vi.mock('@craftjs/core', () => ({
|
||||
useEditor: () => ({
|
||||
query: { serialize: () => '{}' },
|
||||
actions: { deserialize: vi.fn() },
|
||||
}),
|
||||
}));
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
describe('PageContext.updatePageSeo', () => {
|
||||
test('sets seo on a page that previously had none', async () => {
|
||||
let ctx: ReturnType<typeof usePages> | null = null;
|
||||
const Consumer: React.FC = () => {
|
||||
ctx = usePages();
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<PageProvider>
|
||||
<Consumer />
|
||||
</PageProvider>,
|
||||
);
|
||||
|
||||
expect(ctx!.pages[0].seo).toBeUndefined();
|
||||
|
||||
act(() => {
|
||||
ctx!.updatePageSeo(ctx!.pages[0].id, { metaTitle: 'Hello World', noindex: true });
|
||||
});
|
||||
|
||||
expect(ctx!.pages[0].seo).toEqual({ metaTitle: 'Hello World', noindex: true });
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
test('merges new fields onto existing seo without clobbering untouched fields', async () => {
|
||||
let ctx: ReturnType<typeof usePages> | null = null;
|
||||
const Consumer: React.FC = () => {
|
||||
ctx = usePages();
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<PageProvider>
|
||||
<Consumer />
|
||||
</PageProvider>,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
ctx!.updatePageSeo(ctx!.pages[0].id, { metaTitle: 'First', metaDescription: 'Desc' });
|
||||
});
|
||||
act(() => {
|
||||
ctx!.updatePageSeo(ctx!.pages[0].id, { metaTitle: 'Second' });
|
||||
});
|
||||
|
||||
expect(ctx!.pages[0].seo).toEqual({ metaTitle: 'Second', metaDescription: 'Desc' });
|
||||
|
||||
unmount();
|
||||
});
|
||||
|
||||
test('only touches the targeted page, leaving other pages untouched', async () => {
|
||||
let ctx: ReturnType<typeof usePages> | null = null;
|
||||
const Consumer: React.FC = () => {
|
||||
ctx = usePages();
|
||||
return null;
|
||||
};
|
||||
|
||||
render(
|
||||
<PageProvider>
|
||||
<Consumer />
|
||||
</PageProvider>,
|
||||
);
|
||||
|
||||
act(() => {
|
||||
ctx!.addPage('About', 'about');
|
||||
});
|
||||
|
||||
const homeId = ctx!.pages[0].id;
|
||||
const aboutId = ctx!.pages[1].id;
|
||||
|
||||
act(() => {
|
||||
ctx!.updatePageSeo(aboutId, { metaTitle: 'About Us' });
|
||||
});
|
||||
|
||||
expect(ctx!.pages.find((p) => p.id === aboutId)?.seo).toEqual({ metaTitle: 'About Us' });
|
||||
expect(ctx!.pages.find((p) => p.id === homeId)?.seo).toBeUndefined();
|
||||
|
||||
unmount();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user