@prenta/admin 0.98.1 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js +17 -28
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/seo-score-bands.test.js +0 -3
- package/dist/__tests__/components/seo-score-bands.test.js.map +1 -1
- package/dist/__tests__/layout/sidebar-posts.test.js +6 -11
- package/dist/__tests__/layout/sidebar-posts.test.js.map +1 -1
- package/dist/__tests__/layout/sidebar-submenu.render.test.js +3 -2
- package/dist/__tests__/layout/sidebar-submenu.render.test.js.map +1 -1
- package/dist/__tests__/lib/page-editor-service-api.test.js +26 -23
- package/dist/__tests__/lib/page-editor-service-api.test.js.map +1 -1
- package/dist/__tests__/views/page-section-editor.test.js +0 -1
- package/dist/__tests__/views/page-section-editor.test.js.map +1 -1
- package/dist/__tests__/views/page-tab.render.test.js +0 -1
- package/dist/__tests__/views/page-tab.render.test.js.map +1 -1
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -3
- package/dist/index.js.map +1 -1
- package/dist/layout/Sidebar.d.ts +3 -4
- package/dist/layout/Sidebar.d.ts.map +1 -1
- package/dist/layout/Sidebar.js +7 -38
- package/dist/layout/Sidebar.js.map +1 -1
- package/dist/lib/page-editor-service.d.ts +0 -7
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +9 -9
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/prenta-admin.css +1 -1
- package/dist/views/PageEditor.d.ts +2 -2
- package/dist/views/PageEditor.js +2 -2
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +9 -15
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/package.json +4 -4
- package/src/AdminRoot.tsx +18 -68
- package/src/__tests__/components/seo-score-bands.test.ts +0 -3
- package/src/__tests__/layout/sidebar-posts.test.ts +6 -11
- package/src/__tests__/layout/sidebar-submenu.render.test.tsx +3 -2
- package/src/__tests__/lib/page-editor-service-api.test.ts +27 -25
- package/src/__tests__/views/page-section-editor.test.tsx +0 -1
- package/src/__tests__/views/page-tab.render.test.tsx +0 -1
- package/src/index.ts +1 -11
- package/src/layout/Sidebar.tsx +6 -41
- package/src/lib/page-editor-service.ts +9 -16
- package/src/views/PageEditor.tsx +2 -2
- package/src/views/page-editor/PageSectionEditor.tsx +8 -37
- package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-block-renderer.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-block-renderer.render.test.js +0 -48
- package/dist/__tests__/views/page-builder-block-renderer.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-form-picker.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-form-picker.render.test.js +0 -44
- package/dist/__tests__/views/page-builder-form-picker.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-sections-panel.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-sections-panel.render.test.js +0 -121
- package/dist/__tests__/views/page-builder-sections-panel.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-seo-panel.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-seo-panel.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-seo-panel.render.test.js +0 -65
- package/dist/__tests__/views/page-builder-seo-panel.render.test.js.map +0 -1
- package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-builder-template-seed.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-builder-template-seed.render.test.js +0 -48
- package/dist/__tests__/views/page-builder-template-seed.render.test.js.map +0 -1
- package/dist/__tests__/views/page-templates.render.test.d.ts +0 -2
- package/dist/__tests__/views/page-templates.render.test.d.ts.map +0 -1
- package/dist/__tests__/views/page-templates.render.test.js +0 -126
- package/dist/__tests__/views/page-templates.render.test.js.map +0 -1
- package/dist/hooks/useBuilderState.d.ts +0 -49
- package/dist/hooks/useBuilderState.d.ts.map +0 -1
- package/dist/hooks/useBuilderState.js +0 -238
- package/dist/hooks/useBuilderState.js.map +0 -1
- package/dist/views/page-builder/AIBlockAssist.d.ts +0 -9
- package/dist/views/page-builder/AIBlockAssist.d.ts.map +0 -1
- package/dist/views/page-builder/AIBlockAssist.js +0 -40
- package/dist/views/page-builder/AIBlockAssist.js.map +0 -1
- package/dist/views/page-builder/AIGenerateDialog.d.ts +0 -8
- package/dist/views/page-builder/AIGenerateDialog.d.ts.map +0 -1
- package/dist/views/page-builder/AIGenerateDialog.js +0 -173
- package/dist/views/page-builder/AIGenerateDialog.js.map +0 -1
- package/dist/views/page-builder/BlockEditor.d.ts +0 -11
- package/dist/views/page-builder/BlockEditor.d.ts.map +0 -1
- package/dist/views/page-builder/BlockEditor.js +0 -159
- package/dist/views/page-builder/BlockEditor.js.map +0 -1
- package/dist/views/page-builder/BlockPicker.d.ts +0 -7
- package/dist/views/page-builder/BlockPicker.d.ts.map +0 -1
- package/dist/views/page-builder/BlockPicker.js +0 -103
- package/dist/views/page-builder/BlockPicker.js.map +0 -1
- package/dist/views/page-builder/BottomBar.d.ts +0 -9
- package/dist/views/page-builder/BottomBar.d.ts.map +0 -1
- package/dist/views/page-builder/BottomBar.js +0 -13
- package/dist/views/page-builder/BottomBar.js.map +0 -1
- package/dist/views/page-builder/BuilderSectionsPanel.d.ts +0 -20
- package/dist/views/page-builder/BuilderSectionsPanel.d.ts.map +0 -1
- package/dist/views/page-builder/BuilderSectionsPanel.js +0 -105
- package/dist/views/page-builder/BuilderSectionsPanel.js.map +0 -1
- package/dist/views/page-builder/BuilderToolbar.d.ts +0 -21
- package/dist/views/page-builder/BuilderToolbar.d.ts.map +0 -1
- package/dist/views/page-builder/BuilderToolbar.js +0 -18
- package/dist/views/page-builder/BuilderToolbar.js.map +0 -1
- package/dist/views/page-builder/ContextPanel.d.ts +0 -20
- package/dist/views/page-builder/ContextPanel.d.ts.map +0 -1
- package/dist/views/page-builder/ContextPanel.js +0 -43
- package/dist/views/page-builder/ContextPanel.js.map +0 -1
- package/dist/views/page-builder/DesignScore.d.ts +0 -6
- package/dist/views/page-builder/DesignScore.d.ts.map +0 -1
- package/dist/views/page-builder/DesignScore.js +0 -93
- package/dist/views/page-builder/DesignScore.js.map +0 -1
- package/dist/views/page-builder/NodeSettings.d.ts +0 -12
- package/dist/views/page-builder/NodeSettings.d.ts.map +0 -1
- package/dist/views/page-builder/NodeSettings.js +0 -80
- package/dist/views/page-builder/NodeSettings.js.map +0 -1
- package/dist/views/page-builder/PageBuilder.d.ts +0 -10
- package/dist/views/page-builder/PageBuilder.d.ts.map +0 -1
- package/dist/views/page-builder/PageBuilder.js +0 -222
- package/dist/views/page-builder/PageBuilder.js.map +0 -1
- package/dist/views/page-builder/PageSettings.d.ts +0 -7
- package/dist/views/page-builder/PageSettings.d.ts.map +0 -1
- package/dist/views/page-builder/PageSettings.js +0 -27
- package/dist/views/page-builder/PageSettings.js.map +0 -1
- package/dist/views/page-builder/PageTemplates.d.ts +0 -5
- package/dist/views/page-builder/PageTemplates.d.ts.map +0 -1
- package/dist/views/page-builder/PageTemplates.js +0 -146
- package/dist/views/page-builder/PageTemplates.js.map +0 -1
- package/dist/views/page-builder/RelationshipField.d.ts +0 -15
- package/dist/views/page-builder/RelationshipField.d.ts.map +0 -1
- package/dist/views/page-builder/RelationshipField.js +0 -65
- package/dist/views/page-builder/RelationshipField.js.map +0 -1
- package/dist/views/page-builder/SEOPanel.d.ts +0 -9
- package/dist/views/page-builder/SEOPanel.d.ts.map +0 -1
- package/dist/views/page-builder/SEOPanel.js +0 -38
- package/dist/views/page-builder/SEOPanel.js.map +0 -1
- package/dist/views/page-builder/SavedSections.d.ts +0 -6
- package/dist/views/page-builder/SavedSections.d.ts.map +0 -1
- package/dist/views/page-builder/SavedSections.js +0 -141
- package/dist/views/page-builder/SavedSections.js.map +0 -1
- package/dist/views/page-builder/TemplatePicker.d.ts +0 -7
- package/dist/views/page-builder/TemplatePicker.d.ts.map +0 -1
- package/dist/views/page-builder/TemplatePicker.js +0 -68
- package/dist/views/page-builder/TemplatePicker.js.map +0 -1
- package/dist/views/page-builder/Widgets.d.ts +0 -13
- package/dist/views/page-builder/Widgets.d.ts.map +0 -1
- package/dist/views/page-builder/Widgets.js +0 -15
- package/dist/views/page-builder/Widgets.js.map +0 -1
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/CTAPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/CTAPreview.js +0 -19
- package/dist/views/page-builder/block-renderers/CTAPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/CardsPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/CardsPreview.js +0 -22
- package/dist/views/page-builder/block-renderers/CardsPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/CodePreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/CodePreview.js +0 -12
- package/dist/views/page-builder/block-renderers/CodePreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/FAQPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/FAQPreview.js +0 -27
- package/dist/views/page-builder/block-renderers/FAQPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts +0 -6
- package/dist/views/page-builder/block-renderers/FallbackPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/FallbackPreview.js +0 -7
- package/dist/views/page-builder/block-renderers/FallbackPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts +0 -9
- package/dist/views/page-builder/block-renderers/FormPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/FormPreview.js +0 -52
- package/dist/views/page-builder/block-renderers/FormPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/GalleryPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/GalleryPreview.js +0 -19
- package/dist/views/page-builder/block-renderers/GalleryPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/HeroPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/HeroPreview.js +0 -19
- package/dist/views/page-builder/block-renderers/HeroPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/ImagePreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/ImagePreview.js +0 -17
- package/dist/views/page-builder/block-renderers/ImagePreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/TextPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/TextPreview.js +0 -22
- package/dist/views/page-builder/block-renderers/TextPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts +0 -3
- package/dist/views/page-builder/block-renderers/VideoPreview.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/VideoPreview.js +0 -18
- package/dist/views/page-builder/block-renderers/VideoPreview.js.map +0 -1
- package/dist/views/page-builder/block-renderers/index.d.ts +0 -9
- package/dist/views/page-builder/block-renderers/index.d.ts.map +0 -1
- package/dist/views/page-builder/block-renderers/index.js +0 -25
- package/dist/views/page-builder/block-renderers/index.js.map +0 -1
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/BlockRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/BlockRenderer.js +0 -20
- package/dist/views/page-builder/canvas/BlockRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts +0 -10
- package/dist/views/page-builder/canvas/BuilderCanvas.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/BuilderCanvas.js +0 -26
- package/dist/views/page-builder/canvas/BuilderCanvas.js.map +0 -1
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/ColumnRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/ColumnRenderer.js +0 -32
- package/dist/views/page-builder/canvas/ColumnRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/ContainerRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/ContainerRenderer.js +0 -29
- package/dist/views/page-builder/canvas/ContainerRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/RowRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/RowRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/RowRenderer.js +0 -28
- package/dist/views/page-builder/canvas/RowRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts +0 -8
- package/dist/views/page-builder/canvas/SectionRenderer.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/SectionRenderer.js +0 -50
- package/dist/views/page-builder/canvas/SectionRenderer.js.map +0 -1
- package/dist/views/page-builder/canvas/index.d.ts +0 -3
- package/dist/views/page-builder/canvas/index.d.ts.map +0 -1
- package/dist/views/page-builder/canvas/index.js +0 -2
- package/dist/views/page-builder/canvas/index.js.map +0 -1
- package/src/__tests__/views/page-builder-block-renderer.render.test.tsx +0 -66
- package/src/__tests__/views/page-builder-form-picker.render.test.tsx +0 -54
- package/src/__tests__/views/page-builder-sections-panel.render.test.tsx +0 -138
- package/src/__tests__/views/page-builder-seo-panel.render.test.tsx +0 -77
- package/src/__tests__/views/page-builder-template-seed.render.test.tsx +0 -59
- package/src/__tests__/views/page-templates.render.test.tsx +0 -182
- package/src/hooks/useBuilderState.ts +0 -361
- package/src/views/page-builder/AIBlockAssist.tsx +0 -64
- package/src/views/page-builder/AIGenerateDialog.tsx +0 -593
- package/src/views/page-builder/BlockEditor.tsx +0 -523
- package/src/views/page-builder/BlockPicker.tsx +0 -324
- package/src/views/page-builder/BottomBar.tsx +0 -65
- package/src/views/page-builder/BuilderSectionsPanel.tsx +0 -304
- package/src/views/page-builder/BuilderToolbar.tsx +0 -221
- package/src/views/page-builder/ContextPanel.tsx +0 -144
- package/src/views/page-builder/DesignScore.tsx +0 -251
- package/src/views/page-builder/NodeSettings.tsx +0 -500
- package/src/views/page-builder/PageBuilder.tsx +0 -405
- package/src/views/page-builder/PageSettings.tsx +0 -162
- package/src/views/page-builder/PageTemplates.tsx +0 -435
- package/src/views/page-builder/RelationshipField.tsx +0 -113
- package/src/views/page-builder/SEOPanel.tsx +0 -222
- package/src/views/page-builder/SavedSections.tsx +0 -480
- package/src/views/page-builder/TemplatePicker.tsx +0 -197
- package/src/views/page-builder/Widgets.tsx +0 -44
- package/src/views/page-builder/block-renderers/CTAPreview.tsx +0 -79
- package/src/views/page-builder/block-renderers/CardsPreview.tsx +0 -69
- package/src/views/page-builder/block-renderers/CodePreview.tsx +0 -46
- package/src/views/page-builder/block-renderers/FAQPreview.tsx +0 -87
- package/src/views/page-builder/block-renderers/FallbackPreview.tsx +0 -18
- package/src/views/page-builder/block-renderers/FormPreview.tsx +0 -117
- package/src/views/page-builder/block-renderers/GalleryPreview.tsx +0 -87
- package/src/views/page-builder/block-renderers/HeroPreview.tsx +0 -90
- package/src/views/page-builder/block-renderers/ImagePreview.tsx +0 -54
- package/src/views/page-builder/block-renderers/TextPreview.tsx +0 -81
- package/src/views/page-builder/block-renderers/VideoPreview.tsx +0 -73
- package/src/views/page-builder/block-renderers/index.ts +0 -34
- package/src/views/page-builder/canvas/BlockRenderer.tsx +0 -56
- package/src/views/page-builder/canvas/BuilderCanvas.tsx +0 -87
- package/src/views/page-builder/canvas/ColumnRenderer.tsx +0 -82
- package/src/views/page-builder/canvas/ContainerRenderer.tsx +0 -67
- package/src/views/page-builder/canvas/RowRenderer.tsx +0 -68
- package/src/views/page-builder/canvas/SectionRenderer.tsx +0 -93
- package/src/views/page-builder/canvas/index.ts +0 -2
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
'use client';
|
|
2
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
-
import { useState } from 'react';
|
|
4
|
-
import { ContainerRenderer } from './ContainerRenderer.js';
|
|
5
|
-
import { RowRenderer } from './RowRenderer.js';
|
|
6
|
-
export function SectionRenderer({ node, selectedNodeId, onSelectNode }) {
|
|
7
|
-
const [hovered, setHovered] = useState(false);
|
|
8
|
-
const isSelected = selectedNodeId === node.id;
|
|
9
|
-
const handleClick = (e) => {
|
|
10
|
-
e.stopPropagation();
|
|
11
|
-
onSelectNode(node.id);
|
|
12
|
-
};
|
|
13
|
-
const backgroundStyle = {};
|
|
14
|
-
if (node.settings.background) {
|
|
15
|
-
backgroundStyle.backgroundColor = node.settings.background;
|
|
16
|
-
}
|
|
17
|
-
if (node.settings.backgroundImage) {
|
|
18
|
-
backgroundStyle.backgroundImage = `url(${node.settings.backgroundImage})`;
|
|
19
|
-
backgroundStyle.backgroundSize = 'cover';
|
|
20
|
-
backgroundStyle.backgroundPosition = 'center';
|
|
21
|
-
}
|
|
22
|
-
if (node.settings.backgroundGradient) {
|
|
23
|
-
backgroundStyle.backgroundImage = node.settings.backgroundGradient;
|
|
24
|
-
}
|
|
25
|
-
if (node.settings.paddingTop) {
|
|
26
|
-
backgroundStyle.paddingTop = node.settings.paddingTop;
|
|
27
|
-
}
|
|
28
|
-
if (node.settings.paddingBottom) {
|
|
29
|
-
backgroundStyle.paddingBottom = node.settings.paddingBottom;
|
|
30
|
-
}
|
|
31
|
-
if (node.settings.marginTop) {
|
|
32
|
-
backgroundStyle.marginTop = node.settings.marginTop;
|
|
33
|
-
}
|
|
34
|
-
if (node.settings.marginBottom) {
|
|
35
|
-
backgroundStyle.marginBottom = node.settings.marginBottom;
|
|
36
|
-
}
|
|
37
|
-
return (_jsxs("div", { "data-node-id": node.id, className: `relative w-full transition-shadow ${isSelected ? 'ring-primary ring-2 ring-offset-2' : hovered ? 'ring-primary/50 ring-1' : ''}`, style: backgroundStyle, onClick: handleClick, onMouseEnter: (e) => {
|
|
38
|
-
e.stopPropagation();
|
|
39
|
-
setHovered(true);
|
|
40
|
-
}, onMouseLeave: () => setHovered(false), children: [(hovered || isSelected) && (_jsx("span", { className: "bg-primary text-primary-foreground absolute -top-2 -left-1 z-10 rounded px-1.5 py-0.5 text-xs font-medium", children: "Section" })), node.children.map((child) => {
|
|
41
|
-
if (child.type === 'container') {
|
|
42
|
-
return (_jsx(ContainerRenderer, { node: child, selectedNodeId: selectedNodeId, onSelectNode: onSelectNode }, child.id));
|
|
43
|
-
}
|
|
44
|
-
if (child.type === 'row') {
|
|
45
|
-
return (_jsx(RowRenderer, { node: child, selectedNodeId: selectedNodeId, onSelectNode: onSelectNode }, child.id));
|
|
46
|
-
}
|
|
47
|
-
return null;
|
|
48
|
-
})] }));
|
|
49
|
-
}
|
|
50
|
-
//# sourceMappingURL=SectionRenderer.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"SectionRenderer.js","sourceRoot":"","sources":["../../../../src/views/page-builder/canvas/SectionRenderer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/views/page-builder/canvas/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,YAAY,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/views/page-builder/canvas/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA"}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
4
|
-
import type { BlockNode } from '@prenta/core'
|
|
5
|
-
|
|
6
|
-
const cmsApi = vi.fn()
|
|
7
|
-
vi.mock('../../lib/api.js', () => ({ cmsApi: (...args: unknown[]) => cmsApi(...args) }))
|
|
8
|
-
|
|
9
|
-
const { BlockRenderer } = await import('../../views/page-builder/canvas/BlockRenderer.js')
|
|
10
|
-
|
|
11
|
-
function formNode(over: Partial<BlockNode> = {}): BlockNode {
|
|
12
|
-
return {
|
|
13
|
-
id: 'node-1',
|
|
14
|
-
type: 'block',
|
|
15
|
-
settings: { blockType: 'form', variant: 'contact' },
|
|
16
|
-
data: { formId: 'f1' },
|
|
17
|
-
...over,
|
|
18
|
-
} as BlockNode
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
beforeEach(() => {
|
|
22
|
-
cmsApi.mockReset()
|
|
23
|
-
cmsApi.mockResolvedValue({
|
|
24
|
-
data: {
|
|
25
|
-
name: 'Contact Us',
|
|
26
|
-
fields: [
|
|
27
|
-
{ key: 'name', label: 'Full Name', type: 'text' },
|
|
28
|
-
{ key: 'message', label: 'Message', type: 'textarea' },
|
|
29
|
-
],
|
|
30
|
-
},
|
|
31
|
-
status: 200,
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
describe('canvas BlockRenderer', () => {
|
|
36
|
-
it('renders the live form preview with the linked form name and fields', async () => {
|
|
37
|
-
render(<BlockRenderer node={formNode()} selectedNodeId={null} onSelectNode={vi.fn()} />)
|
|
38
|
-
await waitFor(() => expect(screen.getByText('Contact Us')).toBeTruthy())
|
|
39
|
-
expect(cmsApi).toHaveBeenCalledWith('/forms/f1')
|
|
40
|
-
expect(screen.getByText('Full Name')).toBeTruthy()
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('selects the node on click', () => {
|
|
44
|
-
const onSelectNode = vi.fn()
|
|
45
|
-
const { container } = render(
|
|
46
|
-
<BlockRenderer
|
|
47
|
-
node={formNode({ data: {} })}
|
|
48
|
-
selectedNodeId={null}
|
|
49
|
-
onSelectNode={onSelectNode}
|
|
50
|
-
/>,
|
|
51
|
-
)
|
|
52
|
-
fireEvent.click(container.querySelector('[data-node-id="node-1"]')!)
|
|
53
|
-
expect(onSelectNode).toHaveBeenCalledWith('node-1')
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('falls back to a placeholder for unknown block types', () => {
|
|
57
|
-
render(
|
|
58
|
-
<BlockRenderer
|
|
59
|
-
node={formNode({ settings: { blockType: 'widget' }, data: {} })}
|
|
60
|
-
selectedNodeId={null}
|
|
61
|
-
onSelectNode={vi.fn()}
|
|
62
|
-
/>,
|
|
63
|
-
)
|
|
64
|
-
expect(screen.getByText('No preview available')).toBeTruthy()
|
|
65
|
-
})
|
|
66
|
-
})
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
4
|
-
import type { FieldDefinition } from '@prenta/core'
|
|
5
|
-
|
|
6
|
-
const cmsApi = vi.fn()
|
|
7
|
-
vi.mock('../../lib/api.js', () => ({ cmsApi: (...args: unknown[]) => cmsApi(...args) }))
|
|
8
|
-
|
|
9
|
-
const { RelationshipField } = await import('../../views/page-builder/RelationshipField.js')
|
|
10
|
-
|
|
11
|
-
const formField: FieldDefinition = {
|
|
12
|
-
type: 'relationship',
|
|
13
|
-
label: 'Form',
|
|
14
|
-
relationTo: 'forms',
|
|
15
|
-
} as FieldDefinition
|
|
16
|
-
|
|
17
|
-
beforeEach(() => {
|
|
18
|
-
cmsApi.mockReset()
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
describe('RelationshipField (form block picker)', () => {
|
|
22
|
-
it('loads forms and lets the editor pick one by id', async () => {
|
|
23
|
-
cmsApi.mockResolvedValue({
|
|
24
|
-
data: [
|
|
25
|
-
{ id: 'f1', name: 'Contact Us', slug: 'contact-us' },
|
|
26
|
-
{ id: 'f2', name: 'Event RSVP', slug: 'event-rsvp' },
|
|
27
|
-
],
|
|
28
|
-
status: 200,
|
|
29
|
-
})
|
|
30
|
-
const onChange = vi.fn()
|
|
31
|
-
render(<RelationshipField definition={formField} value="" onChange={onChange} label="Form" />)
|
|
32
|
-
|
|
33
|
-
await waitFor(() => expect(screen.getByText('Contact Us')).toBeTruthy())
|
|
34
|
-
expect(cmsApi).toHaveBeenCalledWith('/forms')
|
|
35
|
-
expect(screen.getByText('Event RSVP')).toBeTruthy()
|
|
36
|
-
|
|
37
|
-
fireEvent.change(screen.getByLabelText('Form'), { target: { value: 'f2' } })
|
|
38
|
-
expect(onChange).toHaveBeenCalledWith('f2')
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
it('shows an empty hint when the collection has no items', async () => {
|
|
42
|
-
cmsApi.mockResolvedValue({ data: [], status: 200 })
|
|
43
|
-
render(<RelationshipField definition={formField} value="" onChange={vi.fn()} label="Form" />)
|
|
44
|
-
await waitFor(() => expect(screen.getByText('No forms found.')).toBeTruthy())
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('degrades to a plain id input when the collection cannot be loaded', async () => {
|
|
48
|
-
cmsApi.mockResolvedValue({ error: 'boom', status: 500 })
|
|
49
|
-
render(<RelationshipField definition={formField} value="f9" onChange={vi.fn()} label="Form" />)
|
|
50
|
-
await waitFor(() =>
|
|
51
|
-
expect((screen.getByPlaceholderText('Enter forms id…') as HTMLInputElement).value).toBe('f9'),
|
|
52
|
-
)
|
|
53
|
-
})
|
|
54
|
-
})
|
|
@@ -1,138 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { fireEvent, render, screen } from '@testing-library/react'
|
|
4
|
-
import type { PageNode } from '@prenta/core'
|
|
5
|
-
|
|
6
|
-
const { BuilderSectionsPanel } = await import('../../views/page-builder/BuilderSectionsPanel.js')
|
|
7
|
-
|
|
8
|
-
/** Minimal builder tree: a hero section plus an empty, hidden section. */
|
|
9
|
-
function makeTree(): PageNode {
|
|
10
|
-
return {
|
|
11
|
-
id: 'page1',
|
|
12
|
-
type: 'page',
|
|
13
|
-
children: [
|
|
14
|
-
{
|
|
15
|
-
id: 'sec1',
|
|
16
|
-
type: 'section',
|
|
17
|
-
settings: {},
|
|
18
|
-
children: [
|
|
19
|
-
{
|
|
20
|
-
id: 'cont1',
|
|
21
|
-
type: 'container',
|
|
22
|
-
settings: {},
|
|
23
|
-
children: [
|
|
24
|
-
{
|
|
25
|
-
id: 'row1',
|
|
26
|
-
type: 'row',
|
|
27
|
-
settings: {},
|
|
28
|
-
children: [
|
|
29
|
-
{
|
|
30
|
-
id: 'col1',
|
|
31
|
-
type: 'column',
|
|
32
|
-
settings: { width: 12 },
|
|
33
|
-
children: [
|
|
34
|
-
{
|
|
35
|
-
id: 'blk1',
|
|
36
|
-
type: 'block',
|
|
37
|
-
settings: { blockType: 'hero', variant: 'centered' },
|
|
38
|
-
data: {},
|
|
39
|
-
},
|
|
40
|
-
],
|
|
41
|
-
},
|
|
42
|
-
],
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
},
|
|
46
|
-
],
|
|
47
|
-
},
|
|
48
|
-
{ id: 'sec2', type: 'section', settings: { visibility: 'hidden' }, children: [] },
|
|
49
|
-
],
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function renderPanel(over: Partial<Parameters<typeof BuilderSectionsPanel>[0]> = {}) {
|
|
54
|
-
const props = {
|
|
55
|
-
tree: makeTree(),
|
|
56
|
-
selectedNodeId: null,
|
|
57
|
-
collapsed: false,
|
|
58
|
-
onToggleCollapsed: vi.fn(),
|
|
59
|
-
onSelect: vi.fn(),
|
|
60
|
-
onReorder: vi.fn(),
|
|
61
|
-
onAddSection: vi.fn(),
|
|
62
|
-
...over,
|
|
63
|
-
}
|
|
64
|
-
render(<BuilderSectionsPanel {...props} />)
|
|
65
|
-
return props
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
beforeEach(() => {
|
|
69
|
-
vi.clearAllMocks()
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
describe('BuilderSectionsPanel', () => {
|
|
73
|
-
it('lists the page sections in order with derived names and block counts', () => {
|
|
74
|
-
renderPanel()
|
|
75
|
-
|
|
76
|
-
// First section is named after its first block's catalog label.
|
|
77
|
-
expect(screen.getByText('Hero')).toBeTruthy()
|
|
78
|
-
expect(screen.getByText('1 block')).toBeTruthy()
|
|
79
|
-
// Empty section falls back to a positional name.
|
|
80
|
-
expect(screen.getByText('Section 2')).toBeTruthy()
|
|
81
|
-
expect(screen.getByText('Empty section')).toBeTruthy()
|
|
82
|
-
// Count chip in the header.
|
|
83
|
-
expect(screen.getByText('2')).toBeTruthy()
|
|
84
|
-
// Panel is an accessible nav landmark.
|
|
85
|
-
expect(screen.getByRole('navigation', { name: 'Page sections' })).toBeTruthy()
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('fires the selection callback when a section row is clicked', () => {
|
|
89
|
-
const props = renderPanel()
|
|
90
|
-
|
|
91
|
-
// `/^Hero/` excludes the drag handle ("Drag to reorder Hero").
|
|
92
|
-
fireEvent.click(screen.getByRole('button', { name: /^Hero/ }))
|
|
93
|
-
expect(props.onSelect).toHaveBeenCalledWith('sec1')
|
|
94
|
-
})
|
|
95
|
-
|
|
96
|
-
it('marks the selected section with aria-current', () => {
|
|
97
|
-
renderPanel({ selectedNodeId: 'sec1' })
|
|
98
|
-
|
|
99
|
-
const row = screen.getByRole('button', { name: /^Hero/ })
|
|
100
|
-
expect(row.getAttribute('aria-current')).toBe('true')
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
it('exposes keyboard-accessible drag handles per section', () => {
|
|
104
|
-
renderPanel()
|
|
105
|
-
expect(screen.getByRole('button', { name: 'Drag to reorder Hero' })).toBeTruthy()
|
|
106
|
-
expect(screen.getByRole('button', { name: 'Drag to reorder Section 2' })).toBeTruthy()
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
it('fires the add-section callback from the bottom affordance', () => {
|
|
110
|
-
const props = renderPanel()
|
|
111
|
-
|
|
112
|
-
fireEvent.click(screen.getByRole('button', { name: 'Add Section' }))
|
|
113
|
-
expect(props.onAddSection).toHaveBeenCalledTimes(1)
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
it('collapses to a rail with an expand toggle', () => {
|
|
117
|
-
const props = renderPanel({ collapsed: true })
|
|
118
|
-
|
|
119
|
-
// Section rows are gone; only the expand affordance remains.
|
|
120
|
-
expect(screen.queryByText('Hero')).toBeNull()
|
|
121
|
-
const toggle = screen.getByRole('button', { name: 'Expand sections panel' })
|
|
122
|
-
expect(toggle.getAttribute('aria-expanded')).toBe('false')
|
|
123
|
-
|
|
124
|
-
fireEvent.click(toggle)
|
|
125
|
-
expect(props.onToggleCollapsed).toHaveBeenCalledTimes(1)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('shows a collapse toggle with aria-expanded when open', () => {
|
|
129
|
-
renderPanel()
|
|
130
|
-
const toggle = screen.getByRole('button', { name: 'Collapse sections panel' })
|
|
131
|
-
expect(toggle.getAttribute('aria-expanded')).toBe('true')
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('shows an empty message when the page has no sections', () => {
|
|
135
|
-
renderPanel({ tree: { id: 'page1', type: 'page', children: [] } })
|
|
136
|
-
expect(screen.getByText(/No sections yet/)).toBeTruthy()
|
|
137
|
-
})
|
|
138
|
-
})
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
import { describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { render, screen } from '@testing-library/react'
|
|
4
|
-
import type { PageNode } from '@prenta/core'
|
|
5
|
-
import { BuilderSEOPanel } from '../../views/page-builder/SEOPanel.js'
|
|
6
|
-
|
|
7
|
-
const tree = {
|
|
8
|
-
id: 'page-1',
|
|
9
|
-
type: 'page',
|
|
10
|
-
children: [
|
|
11
|
-
{
|
|
12
|
-
id: 'sec-1',
|
|
13
|
-
type: 'section',
|
|
14
|
-
settings: {},
|
|
15
|
-
children: [
|
|
16
|
-
{
|
|
17
|
-
id: 'row-1',
|
|
18
|
-
type: 'row',
|
|
19
|
-
settings: {},
|
|
20
|
-
children: [
|
|
21
|
-
{
|
|
22
|
-
id: 'col-1',
|
|
23
|
-
type: 'column',
|
|
24
|
-
settings: { width: 12 },
|
|
25
|
-
children: [
|
|
26
|
-
{
|
|
27
|
-
id: 'blk-hero',
|
|
28
|
-
type: 'block',
|
|
29
|
-
settings: { blockType: 'hero' },
|
|
30
|
-
data: {
|
|
31
|
-
title: 'Welcome to Our Service',
|
|
32
|
-
image: { url: '/hero.webp', alt: 'Hero banner' },
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
},
|
|
37
|
-
],
|
|
38
|
-
},
|
|
39
|
-
],
|
|
40
|
-
},
|
|
41
|
-
],
|
|
42
|
-
} as PageNode
|
|
43
|
-
|
|
44
|
-
const pageSettings = {
|
|
45
|
-
title: 'Home',
|
|
46
|
-
slug: 'home',
|
|
47
|
-
metaTitle: 'Welcome to Our Service',
|
|
48
|
-
metaDescription: 'The public description.',
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
describe('BuilderSEOPanel', () => {
|
|
52
|
-
it('grades the live tree with the shared page audit panel and keeps meta fields', () => {
|
|
53
|
-
render(
|
|
54
|
-
<BuilderSEOPanel tree={tree} pageSettings={pageSettings} onPageSettingsChange={vi.fn()} />,
|
|
55
|
-
)
|
|
56
|
-
|
|
57
|
-
expect(screen.getByRole('img', { name: /SEO score:/i })).toBeTruthy()
|
|
58
|
-
expect(screen.getByRole('img', { name: /GEO score:/i })).toBeTruthy()
|
|
59
|
-
expect(screen.getByText('SEO checks')).toBeTruthy()
|
|
60
|
-
expect(screen.getByText('GEO checks')).toBeTruthy()
|
|
61
|
-
expect(screen.getByLabelText('Meta Title')).toBeTruthy()
|
|
62
|
-
expect(screen.queryByText('Readability')).toBeNull()
|
|
63
|
-
expect(screen.queryByText('Block Hints')).toBeNull()
|
|
64
|
-
})
|
|
65
|
-
|
|
66
|
-
it('keeps the empty-tree copy when the canvas has no children', () => {
|
|
67
|
-
render(
|
|
68
|
-
<BuilderSEOPanel
|
|
69
|
-
tree={{ id: 'page-1', type: 'page', children: [] } as PageNode}
|
|
70
|
-
pageSettings={pageSettings}
|
|
71
|
-
onPageSettingsChange={vi.fn()}
|
|
72
|
-
/>,
|
|
73
|
-
)
|
|
74
|
-
expect(screen.getByText('Add content to your page to see SEO analysis')).toBeTruthy()
|
|
75
|
-
expect(screen.queryByRole('img', { name: /SEO score:/i })).toBeNull()
|
|
76
|
-
})
|
|
77
|
-
})
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
import { beforeEach, describe, expect, it, vi, afterEach } from 'vitest'
|
|
3
|
-
import { cleanup, render, screen, waitFor } from '@testing-library/react'
|
|
4
|
-
|
|
5
|
-
const cmsApi = vi.fn()
|
|
6
|
-
vi.mock('../../lib/api.js', () => ({ cmsApi: (...args: unknown[]) => cmsApi(...args) }))
|
|
7
|
-
vi.mock('sonner', () => ({
|
|
8
|
-
toast: { success: vi.fn(), error: vi.fn(), loading: vi.fn() },
|
|
9
|
-
Toaster: () => null,
|
|
10
|
-
}))
|
|
11
|
-
|
|
12
|
-
const { PageBuilder } = await import('../../views/page-builder/PageBuilder.js')
|
|
13
|
-
|
|
14
|
-
const TEMPLATE_TREE = {
|
|
15
|
-
id: 'tpl-page',
|
|
16
|
-
type: 'page',
|
|
17
|
-
children: [
|
|
18
|
-
{
|
|
19
|
-
id: 'sec1',
|
|
20
|
-
type: 'section',
|
|
21
|
-
settings: { label: 'Hero From Template' },
|
|
22
|
-
children: [],
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
beforeEach(() => {
|
|
28
|
-
cmsApi.mockReset()
|
|
29
|
-
cmsApi.mockImplementation(async (endpoint: string) => {
|
|
30
|
-
if (endpoint === '/page-templates/tpl-1') {
|
|
31
|
-
return { data: { id: 'tpl-1', name: 'Landing', tree: TEMPLATE_TREE }, status: 200 }
|
|
32
|
-
}
|
|
33
|
-
return { data: {}, status: 200 }
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
afterEach(cleanup)
|
|
38
|
-
|
|
39
|
-
describe('PageBuilder template seeding (/page-builder/new?template=)', () => {
|
|
40
|
-
it('fetches the template and seeds the canvas tree for a new page', async () => {
|
|
41
|
-
render(
|
|
42
|
-
<PageBuilder templateId="tpl-1" collectionSlug="pages" config={{}} onNavigate={() => {}} />,
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
await waitFor(() => expect(cmsApi).toHaveBeenCalledWith('/page-templates/tpl-1'))
|
|
46
|
-
// replaceTree marks the builder dirty — the unsaved-changes dot is the
|
|
47
|
-
// observable proof the template tree was seeded into the canvas state.
|
|
48
|
-
expect(await screen.findByTitle('Unsaved changes')).toBeTruthy()
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('starts from a blank, clean tree when no template id is given', async () => {
|
|
52
|
-
render(<PageBuilder collectionSlug="pages" config={{}} onNavigate={() => {}} />)
|
|
53
|
-
|
|
54
|
-
await waitFor(() =>
|
|
55
|
-
expect(cmsApi).not.toHaveBeenCalledWith(expect.stringContaining('/page-templates/')),
|
|
56
|
-
)
|
|
57
|
-
expect(screen.queryByTitle('Unsaved changes')).toBeNull()
|
|
58
|
-
})
|
|
59
|
-
})
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
// @vitest-environment happy-dom
|
|
2
|
-
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
4
|
-
|
|
5
|
-
const cmsApi = vi.fn()
|
|
6
|
-
vi.mock('../../lib/api.js', () => ({ cmsApi: (...args: unknown[]) => cmsApi(...args) }))
|
|
7
|
-
vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), loading: vi.fn() } }))
|
|
8
|
-
|
|
9
|
-
const { PageTemplates } = await import('../../views/page-builder/PageTemplates.js')
|
|
10
|
-
|
|
11
|
-
function section(id: string) {
|
|
12
|
-
return { id, type: 'section', settings: {}, children: [] }
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const TEMPLATES = [
|
|
16
|
-
{
|
|
17
|
-
id: 'builtin-landing',
|
|
18
|
-
name: 'Landing Page',
|
|
19
|
-
description: 'Hero, features cards, CTA, testimonials, and final CTA',
|
|
20
|
-
category: 'landing',
|
|
21
|
-
builtIn: true,
|
|
22
|
-
updatedAt: '2026-05-29T00:00:00.000Z',
|
|
23
|
-
thumbnail: null,
|
|
24
|
-
tree: { id: 'p1', type: 'page', children: [section('a'), section('b'), section('c')] },
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
id: 'custom-1',
|
|
28
|
-
name: 'Team Member',
|
|
29
|
-
description: 'Avatar hero, full bio, and contact links',
|
|
30
|
-
category: 'content',
|
|
31
|
-
builtIn: false,
|
|
32
|
-
updatedAt: '2026-06-10T00:00:00.000Z',
|
|
33
|
-
thumbnail: null,
|
|
34
|
-
tree: { id: 'p2', type: 'page', children: [section('a')] },
|
|
35
|
-
},
|
|
36
|
-
]
|
|
37
|
-
|
|
38
|
-
beforeEach(() => {
|
|
39
|
-
cmsApi.mockReset()
|
|
40
|
-
cmsApi.mockImplementation(async (endpoint: string, options?: RequestInit) => {
|
|
41
|
-
if (endpoint === '/page-templates' && !options?.method) {
|
|
42
|
-
return { data: TEMPLATES, status: 200 }
|
|
43
|
-
}
|
|
44
|
-
return { data: {}, status: 200 }
|
|
45
|
-
})
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
describe('PageTemplates card grid', () => {
|
|
49
|
-
it('renders a card per template with name, category, and section-count meta', async () => {
|
|
50
|
-
render(<PageTemplates onNavigate={() => {}} />)
|
|
51
|
-
|
|
52
|
-
expect(await screen.findByText('Landing Page')).toBeTruthy()
|
|
53
|
-
expect(screen.getByText('Team Member')).toBeTruthy()
|
|
54
|
-
// Section counts derived from each template's tree.
|
|
55
|
-
expect(screen.getByText(/3 sections/)).toBeTruthy()
|
|
56
|
-
expect(screen.getByText(/1 section ·/)).toBeTruthy()
|
|
57
|
-
// Category labels from the design's palette.
|
|
58
|
-
expect(screen.getByText('Landing Page')).toBeTruthy()
|
|
59
|
-
expect(screen.getByText('Built-in')).toBeTruthy()
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
it('hides edit/delete actions on built-in templates (server rejects them)', async () => {
|
|
63
|
-
render(<PageTemplates onNavigate={() => {}} />)
|
|
64
|
-
await screen.findByText('Landing Page')
|
|
65
|
-
|
|
66
|
-
expect(screen.queryByRole('button', { name: 'Edit Landing Page' })).toBeNull()
|
|
67
|
-
expect(screen.queryByRole('button', { name: 'Delete Landing Page' })).toBeNull()
|
|
68
|
-
expect(screen.getByRole('button', { name: 'Edit Team Member' })).toBeTruthy()
|
|
69
|
-
expect(screen.getByRole('button', { name: 'Delete Team Member' })).toBeTruthy()
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('deletes a custom template only after the confirm dialog is accepted', async () => {
|
|
73
|
-
render(<PageTemplates onNavigate={() => {}} />)
|
|
74
|
-
await screen.findByText('Team Member')
|
|
75
|
-
|
|
76
|
-
fireEvent.click(screen.getByRole('button', { name: 'Delete Team Member' }))
|
|
77
|
-
|
|
78
|
-
// Confirm dialog is up; nothing deleted yet.
|
|
79
|
-
expect(await screen.findByText('Delete template?')).toBeTruthy()
|
|
80
|
-
expect(cmsApi).not.toHaveBeenCalledWith(
|
|
81
|
-
'/page-templates/custom-1',
|
|
82
|
-
expect.objectContaining({ method: 'DELETE' }),
|
|
83
|
-
)
|
|
84
|
-
|
|
85
|
-
fireEvent.click(screen.getByRole('button', { name: 'Delete' }))
|
|
86
|
-
|
|
87
|
-
await waitFor(() =>
|
|
88
|
-
expect(cmsApi).toHaveBeenCalledWith(
|
|
89
|
-
'/page-templates/custom-1',
|
|
90
|
-
expect.objectContaining({ method: 'DELETE' }),
|
|
91
|
-
),
|
|
92
|
-
)
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('opens the edit modal seeded with the template and saves via PUT', async () => {
|
|
96
|
-
render(<PageTemplates onNavigate={() => {}} />)
|
|
97
|
-
await screen.findByText('Team Member')
|
|
98
|
-
|
|
99
|
-
fireEvent.click(screen.getByRole('button', { name: 'Edit Team Member' }))
|
|
100
|
-
|
|
101
|
-
const nameInput = (await screen.findByPlaceholderText('Template name')) as HTMLInputElement
|
|
102
|
-
expect(nameInput.value).toBe('Team Member')
|
|
103
|
-
|
|
104
|
-
fireEvent.change(nameInput, { target: { value: 'Team Bio' } })
|
|
105
|
-
fireEvent.click(screen.getByRole('button', { name: 'Save changes' }))
|
|
106
|
-
|
|
107
|
-
await waitFor(() =>
|
|
108
|
-
expect(cmsApi).toHaveBeenCalledWith(
|
|
109
|
-
'/page-templates/custom-1',
|
|
110
|
-
expect.objectContaining({ method: 'PUT' }),
|
|
111
|
-
),
|
|
112
|
-
)
|
|
113
|
-
const putCall = cmsApi.mock.calls.find(
|
|
114
|
-
(c) => (c[1] as RequestInit | undefined)?.method === 'PUT',
|
|
115
|
-
)!
|
|
116
|
-
expect(JSON.parse((putCall[1] as RequestInit).body as string)).toMatchObject({
|
|
117
|
-
name: 'Team Bio',
|
|
118
|
-
category: 'content',
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('routes "Use template" into the builder with the template id', async () => {
|
|
123
|
-
const onNavigate = vi.fn()
|
|
124
|
-
render(<PageTemplates onNavigate={onNavigate} />)
|
|
125
|
-
await screen.findByText('Landing Page')
|
|
126
|
-
|
|
127
|
-
fireEvent.click(screen.getByRole('button', { name: 'Create page from Landing Page' }))
|
|
128
|
-
|
|
129
|
-
expect(onNavigate).toHaveBeenCalledWith('/page-builder/new?template=builtin-landing')
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
it('duplicates a template (built-ins included) via POST with a "(copy)" name', async () => {
|
|
133
|
-
render(<PageTemplates onNavigate={() => {}} />)
|
|
134
|
-
await screen.findByText('Landing Page')
|
|
135
|
-
|
|
136
|
-
fireEvent.click(screen.getByRole('button', { name: 'Duplicate Landing Page' }))
|
|
137
|
-
|
|
138
|
-
await waitFor(() =>
|
|
139
|
-
expect(cmsApi).toHaveBeenCalledWith(
|
|
140
|
-
'/page-templates',
|
|
141
|
-
expect.objectContaining({ method: 'POST' }),
|
|
142
|
-
),
|
|
143
|
-
)
|
|
144
|
-
const postCall = cmsApi.mock.calls.find(
|
|
145
|
-
(c) => (c[1] as RequestInit | undefined)?.method === 'POST',
|
|
146
|
-
)!
|
|
147
|
-
expect(JSON.parse((postCall[1] as RequestInit).body as string)).toMatchObject({
|
|
148
|
-
name: 'Landing Page (copy)',
|
|
149
|
-
category: 'landing',
|
|
150
|
-
tree: TEMPLATES[0]!.tree,
|
|
151
|
-
})
|
|
152
|
-
// The grid refetches so the copy appears.
|
|
153
|
-
await waitFor(() =>
|
|
154
|
-
expect(
|
|
155
|
-
cmsApi.mock.calls.filter(
|
|
156
|
-
(c) => c[0] === '/page-templates' && !(c[1] as RequestInit | undefined)?.method,
|
|
157
|
-
).length,
|
|
158
|
-
).toBeGreaterThanOrEqual(2),
|
|
159
|
-
)
|
|
160
|
-
})
|
|
161
|
-
|
|
162
|
-
it('filters cards by search text', async () => {
|
|
163
|
-
render(<PageTemplates onNavigate={() => {}} />)
|
|
164
|
-
await screen.findByText('Team Member')
|
|
165
|
-
|
|
166
|
-
fireEvent.change(screen.getByPlaceholderText('Search templates…'), {
|
|
167
|
-
target: { value: 'landing' },
|
|
168
|
-
})
|
|
169
|
-
|
|
170
|
-
await waitFor(() => {
|
|
171
|
-
expect(screen.queryByText('Team Member')).toBeNull()
|
|
172
|
-
expect(screen.getByText('Landing Page')).toBeTruthy()
|
|
173
|
-
})
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('shows the empty state when no templates exist', async () => {
|
|
177
|
-
cmsApi.mockResolvedValue({ data: [], status: 200 })
|
|
178
|
-
render(<PageTemplates onNavigate={() => {}} />)
|
|
179
|
-
|
|
180
|
-
expect(await screen.findByText('No page templates yet')).toBeTruthy()
|
|
181
|
-
})
|
|
182
|
-
})
|