Files
site-builder/craft/src/panels/left/LayersPanel.test.tsx
T

103 lines
4.7 KiB
TypeScript

import { describe, test, expect } from 'vitest';
import React from 'react';
import { renderEditorHarness } from '../../test-utils/editorHarness';
import { LayersPanel } from './LayersPanel';
import { LayerFocusProvider } from './LayerFocusContext';
function stateWith(extra: Record<string, any>, rootChildren: string[]) {
return JSON.stringify({
ROOT: {
type: { resolvedName: 'Container' }, isCanvas: true,
props: { style: {}, tag: 'div' }, displayName: 'Container',
custom: {}, hidden: false, nodes: rootChildren, linkedNodes: {}, parent: null,
},
...extra,
});
}
const featuresNode = {
type: { resolvedName: 'FeaturesGrid' }, isCanvas: false,
props: { features: [{ title: 'Fast' }, { title: 'Secure' }] },
displayName: 'Features Grid',
custom: {}, hidden: false, nodes: [], linkedNodes: {}, parent: 'ROOT',
};
describe('LayersPanel', () => {
test('shows virtual rows for a Features Grid array prop', () => {
const harness = renderEditorHarness({ initialState: stateWith({ f1: featuresNode }, ['f1']) });
harness.mountChild(<LayerFocusProvider><LayersPanel /></LayerFocusProvider>);
// Scoped to `.layer-virtual-row` rather than the whole container:
// `FeaturesGrid` itself renders `feat.title` on the live canvas (which
// is also mounted inside `harness.container`, alongside the panel), so
// asserting on `container.textContent` alone would pass even if
// LayersPanel never rendered a single virtual row -- the "Fast"/"Secure"
// text would already be there from the canvas. Scoping to the virtual
// row elements themselves makes the assertion actually exercise
// LayersPanel's own rendering.
const virtualRows = harness.container.querySelectorAll('.layer-virtual-row');
expect(virtualRows).toHaveLength(2);
const virtualText = Array.from(virtualRows).map((el) => el.textContent).join(' | ');
expect(virtualText).toContain('Fast');
expect(virtualText).toContain('Secure');
// The real node row for the parent is still shown too.
const nodeRows = harness.container.querySelectorAll('.layer-node-row');
const nodeText = Array.from(nodeRows).map((el) => el.textContent).join(' | ');
expect(nodeText).toContain('Features Grid');
harness.unmount();
});
test('virtual rows are not rendered for a component with no registry entry', () => {
const heading = {
type: { resolvedName: 'Heading' }, isCanvas: false,
props: { text: 'Title', level: 2 }, displayName: 'Heading',
custom: {}, hidden: false, nodes: [], linkedNodes: {}, parent: 'ROOT',
};
const harness = renderEditorHarness({ initialState: stateWith({ h1: heading }, ['h1']) });
harness.mountChild(<LayerFocusProvider><LayersPanel /></LayerFocusProvider>);
expect(harness.container.querySelectorAll('.layer-virtual-row')).toHaveLength(0);
harness.unmount();
});
test('the Unplaced group does not render for a healthy tree', () => {
const harness = renderEditorHarness({ initialState: stateWith({ f1: featuresNode }, ['f1']) });
harness.mountChild(<LayerFocusProvider><LayersPanel /></LayerFocusProvider>);
expect(harness.container.textContent).not.toContain('Unplaced');
harness.unmount();
});
test('the Unplaced group appears and lists a node unreachable from ROOT', () => {
// 'stray' is a real node in the deserialized state, but no node's
// `nodes`/`linkedNodes` list references it -- exactly the "dropped
// outside the page" scenario findUnreachableNodeIds exists to catch.
const heading = {
type: { resolvedName: 'Heading' }, isCanvas: false,
props: { text: 'Title', level: 2 }, displayName: 'Heading',
custom: {}, hidden: false, nodes: [], linkedNodes: {}, parent: 'ROOT',
};
const stray = {
type: { resolvedName: 'HtmlBlock' }, isCanvas: false,
props: { code: '<p>stranded</p>', style: {} }, displayName: 'HTML',
custom: {}, hidden: false, nodes: [], linkedNodes: {}, parent: 'ghost',
};
const harness = renderEditorHarness({
initialState: stateWith({ h1: heading, stray }, ['h1']),
});
harness.mountChild(<LayerFocusProvider><LayersPanel /></LayerFocusProvider>);
expect(harness.container.textContent).toContain('Unplaced');
expect(harness.container.textContent).toContain('Unplaced (1)');
// The orphan itself is rendered as a selectable/deletable LayerNode row
// (displayName 'HTML') underneath the Unplaced heading, not silently
// dropped from the tree.
const nodeRows = harness.container.querySelectorAll('.layer-node-row');
const nodeText = Array.from(nodeRows).map((el) => el.textContent).join(' | ');
expect(nodeText).toContain('HTML');
harness.unmount();
});
});