@prenta/admin 0.92.0 → 0.94.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 +663 -0
- package/design-system/tokens.css +173 -71
- package/design-system/tokens.json +244 -63
- package/design-system/tokens.scss +203 -98
- package/design-system/tokens.tailwind.cjs +93 -38
- package/dist/AdminRoot.d.ts +27 -6
- package/dist/AdminRoot.d.ts.map +1 -1
- package/dist/AdminRoot.js.map +1 -1
- package/dist/__tests__/components/breakpoint-controls.render.test.d.ts +2 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.js +51 -0
- package/dist/__tests__/components/breakpoint-controls.render.test.js.map +1 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts +2 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.js +37 -0
- package/dist/__tests__/components/canvas-edit-tip.render.test.js.map +1 -0
- package/dist/__tests__/components/editor-rail.render.test.d.ts +2 -0
- package/dist/__tests__/components/editor-rail.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/editor-rail.render.test.js +138 -0
- package/dist/__tests__/components/editor-rail.render.test.js.map +1 -0
- package/dist/__tests__/components/media-block.render.test.d.ts +2 -0
- package/dist/__tests__/components/media-block.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/media-block.render.test.js +108 -0
- package/dist/__tests__/components/media-block.render.test.js.map +1 -0
- package/dist/__tests__/components/page-audit-panel.render.test.d.ts +2 -0
- package/dist/__tests__/components/page-audit-panel.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/page-audit-panel.render.test.js +59 -0
- package/dist/__tests__/components/page-audit-panel.render.test.js.map +1 -0
- package/dist/__tests__/components/reflow-meter.render.test.d.ts +2 -0
- package/dist/__tests__/components/reflow-meter.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/reflow-meter.render.test.js +94 -0
- package/dist/__tests__/components/reflow-meter.render.test.js.map +1 -0
- package/dist/__tests__/components/seo-char-count.test.js +24 -6
- package/dist/__tests__/components/seo-char-count.test.js.map +1 -1
- package/dist/__tests__/components/serp-preview.render.test.d.ts +2 -0
- package/dist/__tests__/components/serp-preview.render.test.d.ts.map +1 -0
- package/dist/__tests__/components/serp-preview.render.test.js +25 -0
- package/dist/__tests__/components/serp-preview.render.test.js.map +1 -0
- package/dist/__tests__/lib/breakpoints.test.d.ts +2 -0
- package/dist/__tests__/lib/breakpoints.test.d.ts.map +1 -0
- package/dist/__tests__/lib/breakpoints.test.js +67 -0
- package/dist/__tests__/lib/breakpoints.test.js.map +1 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts +2 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.d.ts.map +1 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.js +44 -0
- package/dist/__tests__/lib/canvas-edit-chrome.test.js.map +1 -0
- package/dist/__tests__/lib/media-format.test.d.ts +2 -0
- package/dist/__tests__/lib/media-format.test.d.ts.map +1 -0
- package/dist/__tests__/lib/media-format.test.js +70 -0
- package/dist/__tests__/lib/media-format.test.js.map +1 -0
- package/dist/__tests__/lib/page-audit-input.test.d.ts +2 -0
- package/dist/__tests__/lib/page-audit-input.test.d.ts.map +1 -0
- package/dist/__tests__/lib/page-audit-input.test.js +122 -0
- package/dist/__tests__/lib/page-audit-input.test.js.map +1 -0
- package/dist/__tests__/lib/page-editor-service.test.js +2 -0
- package/dist/__tests__/lib/page-editor-service.test.js.map +1 -1
- package/dist/__tests__/lib/page-redirect.test.d.ts +2 -0
- package/dist/__tests__/lib/page-redirect.test.d.ts.map +1 -0
- package/dist/__tests__/lib/page-redirect.test.js +26 -0
- package/dist/__tests__/lib/page-redirect.test.js.map +1 -0
- package/dist/__tests__/lib/rail-width.test.d.ts +2 -0
- package/dist/__tests__/lib/rail-width.test.d.ts.map +1 -0
- package/dist/__tests__/lib/rail-width.test.js +69 -0
- package/dist/__tests__/lib/rail-width.test.js.map +1 -0
- package/dist/__tests__/lib/reflow-baseline.test.d.ts +2 -0
- package/dist/__tests__/lib/reflow-baseline.test.d.ts.map +1 -0
- package/dist/__tests__/lib/reflow-baseline.test.js +56 -0
- package/dist/__tests__/lib/reflow-baseline.test.js.map +1 -0
- package/dist/__tests__/lib/reflow-probe.test.d.ts +2 -0
- package/dist/__tests__/lib/reflow-probe.test.d.ts.map +1 -0
- package/dist/__tests__/lib/reflow-probe.test.js +73 -0
- package/dist/__tests__/lib/reflow-probe.test.js.map +1 -0
- package/dist/__tests__/lib/version-diff.test.d.ts +2 -0
- package/dist/__tests__/lib/version-diff.test.d.ts.map +1 -0
- package/dist/__tests__/lib/version-diff.test.js +230 -0
- package/dist/__tests__/lib/version-diff.test.js.map +1 -0
- package/dist/__tests__/lib/version-history.test.d.ts +2 -0
- package/dist/__tests__/lib/version-history.test.d.ts.map +1 -0
- package/dist/__tests__/lib/version-history.test.js +125 -0
- package/dist/__tests__/lib/version-history.test.js.map +1 -0
- package/dist/__tests__/styles/canvas-container-queries.test.d.ts +2 -0
- package/dist/__tests__/styles/canvas-container-queries.test.d.ts.map +1 -0
- package/dist/__tests__/styles/canvas-container-queries.test.js +87 -0
- package/dist/__tests__/styles/canvas-container-queries.test.js.map +1 -0
- package/dist/__tests__/styles/packaging.test.d.ts +2 -0
- package/dist/__tests__/styles/packaging.test.d.ts.map +1 -0
- package/dist/__tests__/styles/packaging.test.js +31 -0
- package/dist/__tests__/styles/packaging.test.js.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.d.ts +2 -0
- package/dist/__tests__/styles/responsive-layer.test.d.ts.map +1 -0
- package/dist/__tests__/styles/responsive-layer.test.js +127 -0
- package/dist/__tests__/styles/responsive-layer.test.js.map +1 -0
- package/dist/__tests__/styles/tokens-drift.test.d.ts +2 -0
- package/dist/__tests__/styles/tokens-drift.test.d.ts.map +1 -0
- package/dist/__tests__/styles/tokens-drift.test.js +92 -0
- package/dist/__tests__/styles/tokens-drift.test.js.map +1 -0
- package/dist/__tests__/views/admin-renderer-compat.test.d.ts +2 -0
- package/dist/__tests__/views/admin-renderer-compat.test.d.ts.map +1 -0
- package/dist/__tests__/views/admin-renderer-compat.test.js +43 -0
- package/dist/__tests__/views/admin-renderer-compat.test.js.map +1 -0
- package/dist/__tests__/views/admin-section-renderers.test.js +52 -4
- package/dist/__tests__/views/admin-section-renderers.test.js.map +1 -1
- package/dist/__tests__/views/canvas-review-mode.render.test.d.ts +2 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.js +70 -0
- package/dist/__tests__/views/canvas-review-mode.render.test.js.map +1 -0
- package/dist/__tests__/views/editor-top-bar.render.test.js +30 -1
- package/dist/__tests__/views/editor-top-bar.render.test.js.map +1 -1
- package/dist/__tests__/views/page-tab.render.test.d.ts +2 -0
- package/dist/__tests__/views/page-tab.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/page-tab.render.test.js +151 -0
- package/dist/__tests__/views/page-tab.render.test.js.map +1 -0
- package/dist/__tests__/views/section-class-parity.test.d.ts +2 -0
- package/dist/__tests__/views/section-class-parity.test.d.ts.map +1 -0
- package/dist/__tests__/views/section-class-parity.test.js +81 -0
- package/dist/__tests__/views/section-class-parity.test.js.map +1 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts +2 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.js +86 -0
- package/dist/__tests__/views/sections-panel-design-owned.render.test.js.map +1 -0
- package/dist/__tests__/views/version-history-panel.render.test.d.ts +2 -0
- package/dist/__tests__/views/version-history-panel.render.test.d.ts.map +1 -0
- package/dist/__tests__/views/version-history-panel.render.test.js +153 -0
- package/dist/__tests__/views/version-history-panel.render.test.js.map +1 -0
- package/dist/components/BreakpointPicker.d.ts +18 -0
- package/dist/components/BreakpointPicker.d.ts.map +1 -0
- package/dist/components/BreakpointPicker.js +31 -0
- package/dist/components/BreakpointPicker.js.map +1 -0
- package/dist/components/CanvasEditTip.d.ts +6 -0
- package/dist/components/CanvasEditTip.d.ts.map +1 -0
- package/dist/components/CanvasEditTip.js +22 -0
- package/dist/components/CanvasEditTip.js.map +1 -0
- package/dist/components/EditorRail.d.ts +36 -0
- package/dist/components/EditorRail.d.ts.map +1 -0
- package/dist/components/EditorRail.js +104 -0
- package/dist/components/EditorRail.js.map +1 -0
- package/dist/components/EditorRailTabs.d.ts +32 -0
- package/dist/components/EditorRailTabs.d.ts.map +1 -0
- package/dist/components/EditorRailTabs.js +45 -0
- package/dist/components/EditorRailTabs.js.map +1 -0
- package/dist/components/MediaBlock.d.ts +49 -0
- package/dist/components/MediaBlock.d.ts.map +1 -0
- package/dist/components/MediaBlock.js +50 -0
- package/dist/components/MediaBlock.js.map +1 -0
- package/dist/components/ReflowMeter.d.ts +28 -0
- package/dist/components/ReflowMeter.d.ts.map +1 -0
- package/dist/components/ReflowMeter.js +42 -0
- package/dist/components/ReflowMeter.js.map +1 -0
- package/dist/components/ZoomChip.d.ts +17 -0
- package/dist/components/ZoomChip.d.ts.map +1 -0
- package/dist/components/ZoomChip.js +17 -0
- package/dist/components/ZoomChip.js.map +1 -0
- package/dist/components/breakpoint-icons.d.ts +3 -0
- package/dist/components/breakpoint-icons.d.ts.map +1 -0
- package/dist/components/breakpoint-icons.js +16 -0
- package/dist/components/breakpoint-icons.js.map +1 -0
- package/dist/components/page-tab/AuthorPicker.d.ts +31 -0
- package/dist/components/page-tab/AuthorPicker.d.ts.map +1 -0
- package/dist/components/page-tab/AuthorPicker.js +95 -0
- package/dist/components/page-tab/AuthorPicker.js.map +1 -0
- package/dist/components/page-tab/DirectiveSwitch.d.ts +38 -0
- package/dist/components/page-tab/DirectiveSwitch.d.ts.map +1 -0
- package/dist/components/page-tab/DirectiveSwitch.js +33 -0
- package/dist/components/page-tab/DirectiveSwitch.js.map +1 -0
- package/dist/components/seo/EditorSeoScoreBadge.d.ts +8 -1
- package/dist/components/seo/EditorSeoScoreBadge.d.ts.map +1 -1
- package/dist/components/seo/EditorSeoScoreBadge.js +2 -2
- package/dist/components/seo/EditorSeoScoreBadge.js.map +1 -1
- package/dist/components/seo/PageAuditPanel.d.ts +14 -0
- package/dist/components/seo/PageAuditPanel.d.ts.map +1 -0
- package/dist/components/seo/PageAuditPanel.js +59 -0
- package/dist/components/seo/PageAuditPanel.js.map +1 -0
- package/dist/components/seo/SeoCharCount.d.ts +19 -7
- package/dist/components/seo/SeoCharCount.d.ts.map +1 -1
- package/dist/components/seo/SeoCharCount.js +30 -16
- package/dist/components/seo/SeoCharCount.js.map +1 -1
- package/dist/components/seo/SeoEditorPane.d.ts +15 -1
- package/dist/components/seo/SeoEditorPane.d.ts.map +1 -1
- package/dist/components/seo/SeoEditorPane.js +118 -21
- package/dist/components/seo/SeoEditorPane.js.map +1 -1
- package/dist/components/seo/SerpPreview.d.ts +20 -0
- package/dist/components/seo/SerpPreview.d.ts.map +1 -0
- package/dist/components/seo/SerpPreview.js +6 -0
- package/dist/components/seo/SerpPreview.js.map +1 -0
- package/dist/hooks/usePageAudit.d.ts +41 -0
- package/dist/hooks/usePageAudit.d.ts.map +1 -0
- package/dist/hooks/usePageAudit.js +68 -0
- package/dist/hooks/usePageAudit.js.map +1 -0
- package/dist/hooks/useReflowGuard.d.ts +47 -0
- package/dist/hooks/useReflowGuard.d.ts.map +1 -0
- package/dist/hooks/useReflowGuard.js +52 -0
- package/dist/hooks/useReflowGuard.js.map +1 -0
- package/dist/layout/primitives/AdminShell.d.ts.map +1 -1
- package/dist/layout/primitives/AdminShell.js +2 -2
- package/dist/layout/primitives/AdminShell.js.map +1 -1
- package/dist/lib/breakpoints.d.ts +51 -0
- package/dist/lib/breakpoints.d.ts.map +1 -0
- package/dist/lib/breakpoints.js +84 -0
- package/dist/lib/breakpoints.js.map +1 -0
- package/dist/lib/canvas-edit-chrome.d.ts +22 -0
- package/dist/lib/canvas-edit-chrome.d.ts.map +1 -0
- package/dist/lib/canvas-edit-chrome.js +81 -0
- package/dist/lib/canvas-edit-chrome.js.map +1 -0
- package/dist/lib/media-format.d.ts +55 -0
- package/dist/lib/media-format.d.ts.map +1 -0
- package/dist/lib/media-format.js +93 -0
- package/dist/lib/media-format.js.map +1 -0
- package/dist/lib/page-audit-input.d.ts +45 -0
- package/dist/lib/page-audit-input.d.ts.map +1 -0
- package/dist/lib/page-audit-input.js +53 -0
- package/dist/lib/page-audit-input.js.map +1 -0
- package/dist/lib/page-editor-service.d.ts +12 -0
- package/dist/lib/page-editor-service.d.ts.map +1 -1
- package/dist/lib/page-editor-service.js +6 -0
- package/dist/lib/page-editor-service.js.map +1 -1
- package/dist/lib/page-redirect.d.ts +39 -0
- package/dist/lib/page-redirect.d.ts.map +1 -0
- package/dist/lib/page-redirect.js +38 -0
- package/dist/lib/page-redirect.js.map +1 -0
- package/dist/lib/rail-width.d.ts +23 -0
- package/dist/lib/rail-width.d.ts.map +1 -0
- package/dist/lib/rail-width.js +59 -0
- package/dist/lib/rail-width.js.map +1 -0
- package/dist/lib/reflow-baseline.d.ts +45 -0
- package/dist/lib/reflow-baseline.d.ts.map +1 -0
- package/dist/lib/reflow-baseline.js +38 -0
- package/dist/lib/reflow-baseline.js.map +1 -0
- package/dist/lib/reflow-probe.d.ts +159 -0
- package/dist/lib/reflow-probe.d.ts.map +1 -0
- package/dist/lib/reflow-probe.js +228 -0
- package/dist/lib/reflow-probe.js.map +1 -0
- package/dist/lib/seo-service.d.ts +10 -0
- package/dist/lib/seo-service.d.ts.map +1 -1
- package/dist/lib/seo-service.js.map +1 -1
- package/dist/lib/version-diff.d.ts +50 -0
- package/dist/lib/version-diff.d.ts.map +1 -0
- package/dist/lib/version-diff.js +294 -0
- package/dist/lib/version-diff.js.map +1 -0
- package/dist/lib/version-history.d.ts +73 -0
- package/dist/lib/version-history.d.ts.map +1 -0
- package/dist/lib/version-history.js +126 -0
- package/dist/lib/version-history.js.map +1 -0
- package/dist/prenta-admin.css +1 -1
- package/dist/views/page-editor/CanvasMetaLine.d.ts +19 -0
- package/dist/views/page-editor/CanvasMetaLine.d.ts.map +1 -0
- package/dist/views/page-editor/CanvasMetaLine.js +23 -0
- package/dist/views/page-editor/CanvasMetaLine.js.map +1 -0
- package/dist/views/page-editor/EditorCanvas.d.ts +47 -8
- package/dist/views/page-editor/EditorCanvas.d.ts.map +1 -1
- package/dist/views/page-editor/EditorCanvas.js +68 -34
- package/dist/views/page-editor/EditorCanvas.js.map +1 -1
- package/dist/views/page-editor/EditorTopBar.d.ts +21 -2
- package/dist/views/page-editor/EditorTopBar.d.ts.map +1 -1
- package/dist/views/page-editor/EditorTopBar.js +13 -20
- package/dist/views/page-editor/EditorTopBar.js.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.d.ts.map +1 -1
- package/dist/views/page-editor/PageSectionEditor.js +383 -78
- package/dist/views/page-editor/PageSectionEditor.js.map +1 -1
- package/dist/views/page-editor/PageTab.d.ts +36 -0
- package/dist/views/page-editor/PageTab.d.ts.map +1 -0
- package/dist/views/page-editor/PageTab.js +65 -0
- package/dist/views/page-editor/PageTab.js.map +1 -0
- package/dist/views/page-editor/ReviewBar.d.ts +21 -0
- package/dist/views/page-editor/ReviewBar.d.ts.map +1 -0
- package/dist/views/page-editor/ReviewBar.js +22 -0
- package/dist/views/page-editor/ReviewBar.js.map +1 -0
- package/dist/views/page-editor/SectionInspector.d.ts.map +1 -1
- package/dist/views/page-editor/SectionInspector.js +64 -20
- package/dist/views/page-editor/SectionInspector.js.map +1 -1
- package/dist/views/page-editor/SectionsPanel.d.ts +30 -5
- package/dist/views/page-editor/SectionsPanel.d.ts.map +1 -1
- package/dist/views/page-editor/SectionsPanel.js +38 -7
- package/dist/views/page-editor/SectionsPanel.js.map +1 -1
- package/dist/views/page-editor/SitePreviewFrame.d.ts.map +1 -1
- package/dist/views/page-editor/SitePreviewFrame.js +3 -0
- package/dist/views/page-editor/SitePreviewFrame.js.map +1 -1
- package/dist/views/page-editor/VersionHistoryPanel.d.ts +29 -0
- package/dist/views/page-editor/VersionHistoryPanel.d.ts.map +1 -0
- package/dist/views/page-editor/VersionHistoryPanel.js +81 -0
- package/dist/views/page-editor/VersionHistoryPanel.js.map +1 -0
- package/dist/views/page-editor/reflow-context.d.ts +28 -0
- package/dist/views/page-editor/reflow-context.d.ts.map +1 -0
- package/dist/views/page-editor/reflow-context.js +14 -0
- package/dist/views/page-editor/reflow-context.js.map +1 -0
- package/dist/views/page-editor/sections/ByTheNumbersSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js +1 -1
- package/dist/views/page-editor/sections/ByTheNumbersSection.js.map +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js +1 -1
- package/dist/views/page-editor/sections/CtaBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/FeatureHighlightSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/FeatureHighlightSection.js +2 -2
- package/dist/views/page-editor/sections/FeatureHighlightSection.js.map +1 -1
- package/dist/views/page-editor/sections/GallerySection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/GallerySection.js +7 -3
- package/dist/views/page-editor/sections/GallerySection.js.map +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js +1 -1
- package/dist/views/page-editor/sections/HeroBannerSection.js.map +1 -1
- package/dist/views/page-editor/sections/MissionSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/MissionSection.js +1 -1
- package/dist/views/page-editor/sections/MissionSection.js.map +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.js +1 -1
- package/dist/views/page-editor/sections/PullQuoteSection.js.map +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.d.ts.map +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.js +1 -1
- package/dist/views/page-editor/sections/RelatedPostsSection.js.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts +13 -1
- package/dist/views/page-editor/sections/SectionRenderer.d.ts.map +1 -1
- package/dist/views/page-editor/sections/SectionRenderer.js +66 -26
- package/dist/views/page-editor/sections/SectionRenderer.js.map +1 -1
- package/dist/views/page-editor/sections/admin-renderers.d.ts +47 -15
- package/dist/views/page-editor/sections/admin-renderers.d.ts.map +1 -1
- package/dist/views/page-editor/sections/admin-renderers.js +27 -15
- package/dist/views/page-editor/sections/admin-renderers.js.map +1 -1
- package/dist/views/page-editor/sections/parts.d.ts +0 -1
- package/dist/views/page-editor/sections/parts.d.ts.map +1 -1
- package/dist/views/page-editor/sections/parts.js +41 -16
- package/dist/views/page-editor/sections/parts.js.map +1 -1
- package/dist/views/page-editor/viewports.d.ts +7 -14
- package/dist/views/page-editor/viewports.d.ts.map +1 -1
- package/dist/views/page-editor/viewports.js +7 -12
- package/dist/views/page-editor/viewports.js.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts +2 -1
- package/dist/views/post-editor/PostEditorCanvas.d.ts.map +1 -1
- package/dist/views/post-editor/PostEditorCanvas.js +5 -2
- package/dist/views/post-editor/PostEditorCanvas.js.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostSectionEditor.js +2 -20
- package/dist/views/post-editor/PostSectionEditor.js.map +1 -1
- package/dist/views/post-editor/PostTemplateEditor.d.ts.map +1 -1
- package/dist/views/post-editor/PostTemplateEditor.js +4 -1
- package/dist/views/post-editor/PostTemplateEditor.js.map +1 -1
- package/package.json +5 -3
- package/src/AdminRoot.tsx +27 -6
- package/src/__tests__/components/breakpoint-controls.render.test.tsx +57 -0
- package/src/__tests__/components/canvas-edit-tip.render.test.tsx +41 -0
- package/src/__tests__/components/editor-rail.render.test.tsx +192 -0
- package/src/__tests__/components/media-block.render.test.tsx +126 -0
- package/src/__tests__/components/page-audit-panel.render.test.tsx +64 -0
- package/src/__tests__/components/reflow-meter.render.test.tsx +144 -0
- package/src/__tests__/components/seo-char-count.test.tsx +26 -6
- package/src/__tests__/components/serp-preview.render.test.tsx +32 -0
- package/src/__tests__/lib/breakpoints.test.ts +84 -0
- package/src/__tests__/lib/canvas-edit-chrome.test.ts +56 -0
- package/src/__tests__/lib/media-format.test.ts +89 -0
- package/src/__tests__/lib/page-audit-input.test.ts +144 -0
- package/src/__tests__/lib/page-editor-service.test.ts +2 -0
- package/src/__tests__/lib/page-redirect.test.ts +29 -0
- package/src/__tests__/lib/rail-width.test.ts +86 -0
- package/src/__tests__/lib/reflow-baseline.test.ts +64 -0
- package/src/__tests__/lib/reflow-probe.test.ts +95 -0
- package/src/__tests__/lib/version-diff.test.ts +278 -0
- package/src/__tests__/lib/version-history.test.ts +166 -0
- package/src/__tests__/styles/canvas-container-queries.test.ts +103 -0
- package/src/__tests__/styles/packaging.test.ts +38 -0
- package/src/__tests__/styles/responsive-layer.test.ts +152 -0
- package/src/__tests__/styles/tokens-drift.test.ts +110 -0
- package/src/__tests__/views/admin-renderer-compat.test.ts +48 -0
- package/src/__tests__/views/admin-section-renderers.test.tsx +70 -4
- package/src/__tests__/views/canvas-review-mode.render.test.tsx +94 -0
- package/src/__tests__/views/editor-top-bar.render.test.tsx +43 -1
- package/src/__tests__/views/page-tab.render.test.tsx +226 -0
- package/src/__tests__/views/section-class-parity.test.ts +98 -0
- package/src/__tests__/views/sections-panel-design-owned.render.test.tsx +104 -0
- package/src/__tests__/views/version-history-panel.render.test.tsx +195 -0
- package/src/components/BreakpointPicker.tsx +114 -0
- package/src/components/CanvasEditTip.tsx +46 -0
- package/src/components/EditorRail.tsx +219 -0
- package/src/components/EditorRailTabs.tsx +117 -0
- package/src/components/MediaBlock.tsx +223 -0
- package/src/components/ReflowMeter.tsx +125 -0
- package/src/components/ZoomChip.tsx +39 -0
- package/src/components/breakpoint-icons.ts +17 -0
- package/src/components/page-tab/AuthorPicker.tsx +240 -0
- package/src/components/page-tab/DirectiveSwitch.tsx +89 -0
- package/src/components/seo/EditorSeoScoreBadge.tsx +9 -1
- package/src/components/seo/PageAuditPanel.tsx +192 -0
- package/src/components/seo/SeoCharCount.tsx +55 -24
- package/src/components/seo/SeoEditorPane.tsx +213 -31
- package/src/components/seo/SerpPreview.tsx +41 -0
- package/src/hooks/usePageAudit.ts +98 -0
- package/src/hooks/useReflowGuard.ts +96 -0
- package/src/layout/primitives/AdminShell.tsx +13 -1
- package/src/lib/breakpoints.ts +107 -0
- package/src/lib/canvas-edit-chrome.ts +82 -0
- package/src/lib/media-format.ts +99 -0
- package/src/lib/page-audit-input.ts +104 -0
- package/src/lib/page-editor-service.ts +18 -0
- package/src/lib/page-redirect.ts +60 -0
- package/src/lib/rail-width.ts +58 -0
- package/src/lib/reflow-baseline.ts +70 -0
- package/src/lib/reflow-probe.ts +299 -0
- package/src/lib/seo-service.ts +10 -0
- package/src/lib/version-diff.ts +376 -0
- package/src/lib/version-history.ts +192 -0
- package/src/styles/prenta-tokens.css +120 -42
- package/src/styles/responsive.css +281 -0
- package/src/styles/theme.css +138 -0
- package/src/views/page-editor/CanvasMetaLine.tsx +50 -0
- package/src/views/page-editor/EditorCanvas.tsx +140 -54
- package/src/views/page-editor/EditorTopBar.tsx +55 -51
- package/src/views/page-editor/PageSectionEditor.tsx +575 -185
- package/src/views/page-editor/PageTab.tsx +433 -0
- package/src/views/page-editor/ReviewBar.tsx +76 -0
- package/src/views/page-editor/SectionInspector.tsx +84 -42
- package/src/views/page-editor/SectionsPanel.tsx +282 -141
- package/src/views/page-editor/SitePreviewFrame.tsx +3 -0
- package/src/views/page-editor/VersionHistoryPanel.tsx +302 -0
- package/src/views/page-editor/reflow-context.ts +43 -0
- package/src/views/page-editor/sections/ByTheNumbersSection.tsx +3 -2
- package/src/views/page-editor/sections/CtaBannerSection.tsx +3 -1
- package/src/views/page-editor/sections/FeatureHighlightSection.tsx +3 -2
- package/src/views/page-editor/sections/GallerySection.tsx +7 -3
- package/src/views/page-editor/sections/HeroBannerSection.tsx +2 -2
- package/src/views/page-editor/sections/MissionSection.tsx +4 -2
- package/src/views/page-editor/sections/PullQuoteSection.tsx +3 -1
- package/src/views/page-editor/sections/RelatedPostsSection.tsx +2 -1
- package/src/views/page-editor/sections/SectionRenderer.tsx +115 -34
- package/src/views/page-editor/sections/admin-renderers.tsx +54 -15
- package/src/views/page-editor/sections/parts.tsx +46 -18
- package/src/views/page-editor/viewports.ts +13 -26
- package/src/views/post-editor/PostEditorCanvas.tsx +18 -11
- package/src/views/post-editor/PostSectionEditor.tsx +1 -56
- package/src/views/post-editor/PostTemplateEditor.tsx +3 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
changedSectionIds,
|
|
4
|
+
diffVersions,
|
|
5
|
+
formatValue,
|
|
6
|
+
groupChangesBySection,
|
|
7
|
+
} from '../../lib/version-diff.js'
|
|
8
|
+
import type { PageSection } from '../../lib/page-editor-service.js'
|
|
9
|
+
|
|
10
|
+
function section(overrides: Partial<PageSection> & { id: string }): PageSection {
|
|
11
|
+
return {
|
|
12
|
+
sectionType: 'hero',
|
|
13
|
+
name: 'Hero Banner',
|
|
14
|
+
visible: true,
|
|
15
|
+
content: {},
|
|
16
|
+
settings: {},
|
|
17
|
+
...overrides,
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('formatValue', () => {
|
|
22
|
+
it('renders absent and blank values as an em dash so an empty diff cell is never blank', () => {
|
|
23
|
+
expect(formatValue(null)).toBe('—')
|
|
24
|
+
expect(formatValue(undefined)).toBe('—')
|
|
25
|
+
expect(formatValue('')).toBe('—')
|
|
26
|
+
expect(formatValue(' ')).toBe('—')
|
|
27
|
+
expect(formatValue([])).toBe('—')
|
|
28
|
+
expect(formatValue({})).toBe('—')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('summarises structured values rather than dumping them', () => {
|
|
32
|
+
expect(formatValue([{ value: '1' }, { value: '2' }])).toBe('2 items')
|
|
33
|
+
expect(formatValue([{ value: '1' }])).toBe('1 item')
|
|
34
|
+
expect(formatValue({ name: 'hero.jpg', url: '/m/hero.jpg' })).toBe('hero.jpg')
|
|
35
|
+
// No name-ish key: fall back to a shape description, not "[object Object]".
|
|
36
|
+
expect(formatValue({ x: 1, y: 2 })).toBe('2 fields')
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('reads booleans and numbers as words the panel can show', () => {
|
|
40
|
+
expect(formatValue(true)).toBe('Yes')
|
|
41
|
+
expect(formatValue(false)).toBe('No')
|
|
42
|
+
expect(formatValue(0)).toBe('0')
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
describe('diffVersions', () => {
|
|
47
|
+
it('labels content fields through the section registry, not by raw key', () => {
|
|
48
|
+
const before = [section({ id: 's1', content: { heading: 'Old headline' } })]
|
|
49
|
+
const after = [section({ id: 's1', content: { heading: 'New headline' } })]
|
|
50
|
+
|
|
51
|
+
const changes = diffVersions(after, before)
|
|
52
|
+
|
|
53
|
+
expect(changes).toEqual([
|
|
54
|
+
{
|
|
55
|
+
sectionId: 's1',
|
|
56
|
+
sectionName: 'Hero Banner',
|
|
57
|
+
// 'heading' is `label: 'Heading'` on the hero type in the registry.
|
|
58
|
+
field: 'Heading',
|
|
59
|
+
from: 'Old headline',
|
|
60
|
+
to: 'New headline',
|
|
61
|
+
},
|
|
62
|
+
])
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('falls back to the raw key for a type the registry does not describe', () => {
|
|
66
|
+
const before = [section({ id: 's1', sectionType: 'opal-pricing', content: { tiers: 'a' } })]
|
|
67
|
+
const after = [section({ id: 's1', sectionType: 'opal-pricing', content: { tiers: 'b' } })]
|
|
68
|
+
|
|
69
|
+
expect(diffVersions(after, before)[0]?.field).toBe('tiers')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('reports a field cleared to nothing — the key exists only in the older snapshot', () => {
|
|
73
|
+
const before = [section({ id: 's1', content: { heading: 'Something' } })]
|
|
74
|
+
const after = [section({ id: 's1', content: {} })]
|
|
75
|
+
|
|
76
|
+
expect(diffVersions(after, before)).toEqual([
|
|
77
|
+
{ sectionId: 's1', sectionName: 'Hero Banner', field: 'Heading', from: 'Something', to: '—' },
|
|
78
|
+
])
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('reports visibility as a state, because hiding a section is the one structural edit there is', () => {
|
|
82
|
+
const before = [section({ id: 's1', visible: true })]
|
|
83
|
+
const after = [section({ id: 's1', visible: false })]
|
|
84
|
+
|
|
85
|
+
expect(diffVersions(after, before)).toEqual([
|
|
86
|
+
{
|
|
87
|
+
sectionId: 's1',
|
|
88
|
+
sectionName: 'Hero Banner',
|
|
89
|
+
field: 'Visibility',
|
|
90
|
+
from: 'Shown',
|
|
91
|
+
to: 'Hidden',
|
|
92
|
+
},
|
|
93
|
+
])
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('reports added and removed sections', () => {
|
|
97
|
+
const before = [section({ id: 's1' })]
|
|
98
|
+
const after = [section({ id: 's1' }), section({ id: 's2', name: 'CTA', sectionType: 'cta' })]
|
|
99
|
+
|
|
100
|
+
expect(diffVersions(after, before)).toEqual([
|
|
101
|
+
{ sectionId: 's2', sectionName: 'CTA', field: 'Section', from: '—', to: 'Added' },
|
|
102
|
+
])
|
|
103
|
+
|
|
104
|
+
expect(diffVersions(before, after)).toEqual([
|
|
105
|
+
{ sectionId: 's2', sectionName: 'CTA', field: 'Section', from: 'Present', to: 'Removed' },
|
|
106
|
+
])
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
it('reports nothing when the snapshots match', () => {
|
|
110
|
+
const same = [section({ id: 's1', content: { heading: 'Same' } })]
|
|
111
|
+
expect(diffVersions(same, [section({ id: 's1', content: { heading: 'Same' } })])).toEqual([])
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('reports nothing against a missing predecessor', () => {
|
|
115
|
+
// The oldest version we hold is not the moment the page was created, so
|
|
116
|
+
// reporting every field as "added" would state something false.
|
|
117
|
+
expect(diffVersions([section({ id: 's1', content: { heading: 'x' } })], undefined)).toEqual([])
|
|
118
|
+
expect(diffVersions([section({ id: 's1' })], null)).toEqual([])
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('does not report a settings key that was merely absent versus explicitly default', () => {
|
|
122
|
+
// `{}` and `{ backgroundType: undefined }` format identically, so neither
|
|
123
|
+
// produces a row. Without this the first save after a schema addition
|
|
124
|
+
// would light up every section on the canvas.
|
|
125
|
+
const before = [section({ id: 's1', settings: {} })]
|
|
126
|
+
const after = [section({ id: 's1', settings: { backgroundType: undefined } as never })]
|
|
127
|
+
expect(diffVersions(after, before)).toEqual([])
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('labels known settings and reports a real background change', () => {
|
|
131
|
+
const before = [section({ id: 's1', settings: { backgroundType: 'none' } })]
|
|
132
|
+
const after = [section({ id: 's1', settings: { backgroundType: 'solid' } })]
|
|
133
|
+
|
|
134
|
+
expect(diffVersions(after, before)).toEqual([
|
|
135
|
+
{
|
|
136
|
+
sectionId: 's1',
|
|
137
|
+
sectionName: 'Hero Banner',
|
|
138
|
+
field: 'Background type',
|
|
139
|
+
from: 'none',
|
|
140
|
+
to: 'solid',
|
|
141
|
+
},
|
|
142
|
+
])
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('diffVersions — structured fields', () => {
|
|
147
|
+
const stats = (rows: Array<{ value: string; label: string }>) =>
|
|
148
|
+
section({ id: 's1', sectionType: 'stats', name: 'By The Numbers', content: { stats: rows } })
|
|
149
|
+
|
|
150
|
+
it('catches an edit that keeps the row count', () => {
|
|
151
|
+
// The bug this exists for: deciding "did it change" from the DISPLAY
|
|
152
|
+
// summary collapses an array to "2 items", so retyping a row while keeping
|
|
153
|
+
// the count formats identically on both sides and the field reports no
|
|
154
|
+
// change — no panel row AND no outline on the canvas, which is exactly the
|
|
155
|
+
// edit a reviewer opened history to find.
|
|
156
|
+
const before = [
|
|
157
|
+
stats([
|
|
158
|
+
{ value: '10k', label: 'Users' },
|
|
159
|
+
{ value: '99%', label: 'Uptime' },
|
|
160
|
+
]),
|
|
161
|
+
]
|
|
162
|
+
const after = [
|
|
163
|
+
stats([
|
|
164
|
+
{ value: '20k', label: 'Users' },
|
|
165
|
+
{ value: '99%', label: 'Uptime' },
|
|
166
|
+
]),
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
const changes = diffVersions(after, before)
|
|
170
|
+
|
|
171
|
+
expect(changes).toHaveLength(1)
|
|
172
|
+
expect(changes[0]?.from).toBe('10k')
|
|
173
|
+
expect(changes[0]?.to).toBe('20k')
|
|
174
|
+
// Named down to the row and the sub-field, so "which one" has an answer.
|
|
175
|
+
expect(changes[0]?.field).toBe('Stats · Stat 1 · Value')
|
|
176
|
+
expect(changedSectionIds(changes).has('s1')).toBe(true)
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
it('never emits a row whose before and after both read as a bare count', () => {
|
|
180
|
+
const before = [
|
|
181
|
+
stats([
|
|
182
|
+
{ value: 'a', label: 'x' },
|
|
183
|
+
{ value: 'b', label: 'y' },
|
|
184
|
+
]),
|
|
185
|
+
]
|
|
186
|
+
const after = [
|
|
187
|
+
stats([
|
|
188
|
+
{ value: 'c', label: 'x' },
|
|
189
|
+
{ value: 'd', label: 'y' },
|
|
190
|
+
]),
|
|
191
|
+
]
|
|
192
|
+
|
|
193
|
+
for (const change of diffVersions(after, before)) {
|
|
194
|
+
expect(change.from).not.toBe(change.to)
|
|
195
|
+
expect(change.from).not.toMatch(/^\d+ items?$/)
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it('reports added and removed rows', () => {
|
|
200
|
+
const before = [stats([{ value: '10k', label: 'Users' }])]
|
|
201
|
+
const after = [
|
|
202
|
+
stats([
|
|
203
|
+
{ value: '10k', label: 'Users' },
|
|
204
|
+
{ value: '99%', label: 'Uptime' },
|
|
205
|
+
]),
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
expect(diffVersions(after, before)).toEqual([
|
|
209
|
+
expect.objectContaining({ field: 'Stats · Stat 2', from: '—', to: 'Added' }),
|
|
210
|
+
])
|
|
211
|
+
expect(diffVersions(before, after)).toEqual([
|
|
212
|
+
expect.objectContaining({ field: 'Stats · Stat 2', from: 'Present', to: 'Removed' }),
|
|
213
|
+
])
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('catches a nested change inside a media object', () => {
|
|
217
|
+
// Same failure one level down: two media objects with the same `name` used
|
|
218
|
+
// to format identically while pointing at different files.
|
|
219
|
+
const media = (url: string) => ({ name: 'hero.jpg', url })
|
|
220
|
+
const before = [section({ id: 's1', content: { imageUrl: media('/a.jpg') } })]
|
|
221
|
+
const after = [section({ id: 's1', content: { imageUrl: media('/b.jpg') } })]
|
|
222
|
+
|
|
223
|
+
expect(diffVersions(after, before)).toHaveLength(1)
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
it('treats every spelling of blank as unchanged', () => {
|
|
227
|
+
// Tightening the comparison must not start reporting "— → —".
|
|
228
|
+
for (const [before, after] of [
|
|
229
|
+
['', null],
|
|
230
|
+
[null, undefined],
|
|
231
|
+
[undefined, ' '],
|
|
232
|
+
[[], undefined],
|
|
233
|
+
[{}, null],
|
|
234
|
+
] as Array<[unknown, unknown]>) {
|
|
235
|
+
const changes = diffVersions(
|
|
236
|
+
[section({ id: 's1', content: { heading: after } })],
|
|
237
|
+
[section({ id: 's1', content: { heading: before } })],
|
|
238
|
+
)
|
|
239
|
+
expect(changes, `${JSON.stringify(before)} → ${JSON.stringify(after)}`).toEqual([])
|
|
240
|
+
}
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
it('reports an unchanged repeater as no change at all', () => {
|
|
244
|
+
const rows = [{ value: '10k', label: 'Users' }]
|
|
245
|
+
expect(diffVersions([stats(rows)], [stats([...rows.map((r) => ({ ...r }))])])).toEqual([])
|
|
246
|
+
})
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
describe('groupChangesBySection', () => {
|
|
250
|
+
it('groups by section in first-seen order', () => {
|
|
251
|
+
const before = [
|
|
252
|
+
section({ id: 's1', content: { heading: 'a', body: 'a' } }),
|
|
253
|
+
section({ id: 's2', name: 'CTA', sectionType: 'cta', content: { heading: 'a' } }),
|
|
254
|
+
]
|
|
255
|
+
const after = [
|
|
256
|
+
section({ id: 's1', content: { heading: 'b', body: 'b' } }),
|
|
257
|
+
section({ id: 's2', name: 'CTA', sectionType: 'cta', content: { heading: 'b' } }),
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
const groups = groupChangesBySection(diffVersions(after, before))
|
|
261
|
+
|
|
262
|
+
expect(groups.map((g) => g.sectionId)).toEqual(['s1', 's2'])
|
|
263
|
+
expect(groups[0]?.changes).toHaveLength(2)
|
|
264
|
+
expect(groups[1]?.changes).toHaveLength(1)
|
|
265
|
+
})
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
describe('changedSectionIds', () => {
|
|
269
|
+
it('collapses several changes on one section to a single outline', () => {
|
|
270
|
+
const before = [section({ id: 's1', content: { heading: 'a', body: 'a' } })]
|
|
271
|
+
const after = [section({ id: 's1', content: { heading: 'b', body: 'b' } })]
|
|
272
|
+
|
|
273
|
+
const ids = changedSectionIds(diffVersions(after, before))
|
|
274
|
+
|
|
275
|
+
expect(ids.size).toBe(1)
|
|
276
|
+
expect(ids.has('s1')).toBe(true)
|
|
277
|
+
})
|
|
278
|
+
})
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import {
|
|
3
|
+
dayLabel,
|
|
4
|
+
groupVersionsByDay,
|
|
5
|
+
initials,
|
|
6
|
+
toPageVersion,
|
|
7
|
+
versionKind,
|
|
8
|
+
type PageVersion,
|
|
9
|
+
} from '../../lib/version-history.js'
|
|
10
|
+
|
|
11
|
+
const NOW = new Date('2026-08-02T17:00:00Z')
|
|
12
|
+
|
|
13
|
+
function version(id: string, createdAt: string): PageVersion {
|
|
14
|
+
return {
|
|
15
|
+
id,
|
|
16
|
+
changeType: 'UPDATE',
|
|
17
|
+
createdAt,
|
|
18
|
+
author: { id: 'u1', name: 'Sarah Mills' },
|
|
19
|
+
sections: [],
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('toPageVersion', () => {
|
|
24
|
+
it('lifts the section snapshot out of the version payload', () => {
|
|
25
|
+
const mapped = toPageVersion({
|
|
26
|
+
id: 'v1',
|
|
27
|
+
changeType: 'PUBLISH',
|
|
28
|
+
createdAt: '2026-08-02T12:00:00Z',
|
|
29
|
+
data: {
|
|
30
|
+
sections: [
|
|
31
|
+
{ id: 's1', sectionType: 'hero', name: 'Hero', visible: true, content: {}, settings: {} },
|
|
32
|
+
],
|
|
33
|
+
},
|
|
34
|
+
changedBy: { id: 'u1', name: 'Sarah Mills', email: 's@example.com' },
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
expect(mapped.sections).toHaveLength(1)
|
|
38
|
+
expect(mapped.sections[0]?.id).toBe('s1')
|
|
39
|
+
expect(mapped.changeType).toBe('PUBLISH')
|
|
40
|
+
expect(mapped.author.name).toBe('Sarah Mills')
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('preserves an unmanaged section rather than dropping it from the snapshot', () => {
|
|
44
|
+
// Coercing a snapshot more strictly than the live document reads would
|
|
45
|
+
// make an externally-managed section look deleted in every version.
|
|
46
|
+
const mapped = toPageVersion({
|
|
47
|
+
id: 'v1',
|
|
48
|
+
changeType: 'UPDATE',
|
|
49
|
+
createdAt: '2026-08-02T12:00:00Z',
|
|
50
|
+
data: {
|
|
51
|
+
sections: [
|
|
52
|
+
{ id: 's1', sectionType: 'opal-pricing', name: 'Pricing', visible: true, content: {} },
|
|
53
|
+
],
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
expect(mapped.sections).toHaveLength(1)
|
|
58
|
+
expect(mapped.sections[0]?.sectionType).toBe('opal-pricing')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('falls back through name → email → a real word for the avatar', () => {
|
|
62
|
+
expect(
|
|
63
|
+
toPageVersion({ id: 'v', changeType: 'UPDATE', createdAt: '', data: null }).author.name,
|
|
64
|
+
).toBe('Unknown')
|
|
65
|
+
|
|
66
|
+
expect(
|
|
67
|
+
toPageVersion({
|
|
68
|
+
id: 'v',
|
|
69
|
+
changeType: 'UPDATE',
|
|
70
|
+
createdAt: '',
|
|
71
|
+
data: null,
|
|
72
|
+
changedBy: { id: 'u', name: ' ', email: 'a@b.com' },
|
|
73
|
+
}).author.name,
|
|
74
|
+
).toBe('a@b.com')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('yields no sections for a version whose payload has none', () => {
|
|
78
|
+
expect(
|
|
79
|
+
toPageVersion({ id: 'v', changeType: 'DELETE', createdAt: '', data: {} }).sections,
|
|
80
|
+
).toEqual([])
|
|
81
|
+
})
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
describe('versionKind', () => {
|
|
85
|
+
it('marks a published snapshot as the one a visitor could have seen', () => {
|
|
86
|
+
expect(versionKind('PUBLISH')).toEqual({ label: 'Published', tone: 'live' })
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
it('calls UPDATE "Saved", never "Draft"', () => {
|
|
90
|
+
// Every row written before cms-core started recording PUBLISH is an UPDATE
|
|
91
|
+
// whether or not it went live, so "Draft" would be false for those.
|
|
92
|
+
expect(versionKind('UPDATE').label).toBe('Saved')
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('distinguishes a rollback from authored content', () => {
|
|
96
|
+
expect(versionKind('RESTORE').label).toBe('Restored')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
it('degrades to a neutral label for an unrecognised type', () => {
|
|
100
|
+
expect(versionKind('SOMETHING_NEW').label).toBe('Saved')
|
|
101
|
+
})
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
describe('dayLabel', () => {
|
|
105
|
+
it('names today and yesterday', () => {
|
|
106
|
+
expect(dayLabel('2026-08-02T09:00:00', NOW)).toBe('Today')
|
|
107
|
+
expect(dayLabel('2026-08-01T23:59:00', NOW)).toBe('Yesterday')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('drops the year within the current one and keeps it outside', () => {
|
|
111
|
+
expect(dayLabel('2026-07-27T09:00:00', NOW)).not.toMatch(/2026/)
|
|
112
|
+
expect(dayLabel('2025-07-27T09:00:00', NOW)).toMatch(/2025/)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('does not throw on an unparseable timestamp', () => {
|
|
116
|
+
expect(dayLabel('not-a-date', NOW)).toBe('Unknown date')
|
|
117
|
+
})
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
describe('groupVersionsByDay', () => {
|
|
121
|
+
it('groups consecutive runs and keeps newest-first order', () => {
|
|
122
|
+
const groups = groupVersionsByDay(
|
|
123
|
+
[
|
|
124
|
+
version('v5', '2026-08-02T16:12:00'),
|
|
125
|
+
version('v4', '2026-08-02T11:03:00'),
|
|
126
|
+
version('v3', '2026-08-01T17:47:00'),
|
|
127
|
+
version('v1', '2026-07-27T09:30:00'),
|
|
128
|
+
],
|
|
129
|
+
NOW,
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
expect(groups.map((g) => g.label)).toEqual(['Today', 'Yesterday', expect.stringMatching(/Jul/)])
|
|
133
|
+
expect(groups[0]?.versions.map((v) => v.id)).toEqual(['v5', 'v4'])
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('does not merge two non-adjacent runs that share a label', () => {
|
|
137
|
+
// A Map keyed on the label would silently reorder an unsorted page into
|
|
138
|
+
// something that reads as chronological but is not.
|
|
139
|
+
const groups = groupVersionsByDay(
|
|
140
|
+
[
|
|
141
|
+
version('a', '2026-08-02T16:00:00'),
|
|
142
|
+
version('b', '2026-08-01T16:00:00'),
|
|
143
|
+
version('c', '2026-08-02T09:00:00'),
|
|
144
|
+
],
|
|
145
|
+
NOW,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
expect(groups.map((g) => g.label)).toEqual(['Today', 'Yesterday', 'Today'])
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
it('returns nothing for an empty list', () => {
|
|
152
|
+
expect(groupVersionsByDay([], NOW)).toEqual([])
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
describe('initials', () => {
|
|
157
|
+
it('takes first and last for a full name, two letters for one word', () => {
|
|
158
|
+
expect(initials('Sarah Mills')).toBe('SM')
|
|
159
|
+
expect(initials('Sarah Jane Mills')).toBe('SM')
|
|
160
|
+
expect(initials('sarah')).toBe('SA')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
it('never renders an empty monogram', () => {
|
|
164
|
+
expect(initials(' ')).toBe('?')
|
|
165
|
+
})
|
|
166
|
+
})
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { readFileSync, readdirSync } from 'node:fs'
|
|
2
|
+
import { fileURLToPath } from 'node:url'
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The editor canvas renders a page at a device's real CSS width and scales it
|
|
7
|
+
* with a transform, so the page must reflow against the CANVAS width, not the
|
|
8
|
+
* browser viewport. Two invariants make that work, and both fail silently:
|
|
9
|
+
*
|
|
10
|
+
* 1. `.prenta-canvas` must establish an inline-size query container. Without
|
|
11
|
+
* it, `@pv-*` variants never match and every breakpoint renders the
|
|
12
|
+
* narrowest layout — which looks plausible enough to ship.
|
|
13
|
+
* 2. Section renderers must not use viewport variants (`sm:`, `md:`, `lg:`).
|
|
14
|
+
* Those resolve against the editor window, so picking "Mobile" on a wide
|
|
15
|
+
* monitor yields a 390px canvas still laid out for desktop — the exact
|
|
16
|
+
* bug that made the old preview decorative.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const SECTIONS_DIR = fileURLToPath(new URL('../../views/page-editor/sections/', import.meta.url))
|
|
20
|
+
const THEME_CSS = fileURLToPath(new URL('../../styles/theme.css', import.meta.url))
|
|
21
|
+
|
|
22
|
+
/** Viewport variants in a className position. Matches `sm:`…`2xl:` prefixes. */
|
|
23
|
+
const VIEWPORT_VARIANT = /(?<![\w@[-])(?:sm|md|lg|xl|2xl):[a-z0-9[]/g
|
|
24
|
+
|
|
25
|
+
function sectionFiles(): string[] {
|
|
26
|
+
return readdirSync(SECTIONS_DIR).filter((f) => f.endsWith('.tsx') || f.endsWith('.ts'))
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('canvas container queries', () => {
|
|
30
|
+
it('makes .prenta-canvas an inline-size query container', () => {
|
|
31
|
+
const css = readFileSync(THEME_CSS, 'utf8').replace(/\/\*[\s\S]*?\*\//g, '')
|
|
32
|
+
expect(css).toMatch(/\.prenta-canvas\s*\{[^}]*container-type:\s*inline-size/)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('defines every reflow step the handoff specifies', () => {
|
|
36
|
+
const css = readFileSync(THEME_CSS, 'utf8')
|
|
37
|
+
for (const [name, value] of [
|
|
38
|
+
['--container-pv-sm', '460px'],
|
|
39
|
+
['--container-pv-md', '700px'],
|
|
40
|
+
['--container-pv-lg', '900px'],
|
|
41
|
+
['--container-pv-xl', '1100px'],
|
|
42
|
+
['--container-pv-2xl', '2000px'],
|
|
43
|
+
]) {
|
|
44
|
+
expect(css, `${name} is missing or has moved`).toContain(`${name}: ${value}`)
|
|
45
|
+
}
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('uses no viewport breakpoints in any section renderer', () => {
|
|
49
|
+
const offenders: string[] = []
|
|
50
|
+
for (const file of sectionFiles()) {
|
|
51
|
+
const source = readFileSync(SECTIONS_DIR + file, 'utf8')
|
|
52
|
+
for (const match of source.matchAll(VIEWPORT_VARIANT)) {
|
|
53
|
+
offenders.push(`${file}: ${match[0]}`)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
expect(
|
|
57
|
+
offenders,
|
|
58
|
+
'Section renderers must reflow on the canvas width. Use the @pv-* container ' +
|
|
59
|
+
'steps (theme.css) instead of sm:/md:/lg:.',
|
|
60
|
+
).toEqual([])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
it('restores the stock type scale inside the canvas, in the utilities layer', () => {
|
|
64
|
+
// The admin remaps six Tailwind size keys onto its own tokens (text-base =
|
|
65
|
+
// 13px). The canvas renders the consumer's website, where text-base must
|
|
66
|
+
// be 16px — and the reflow guard measures wrapping against these sizes, so
|
|
67
|
+
// a canvas on the admin scale makes every line count wrong.
|
|
68
|
+
const css = readFileSync(THEME_CSS, 'utf8')
|
|
69
|
+
|
|
70
|
+
// Layer order beats specificity: in @layer base this override would lose
|
|
71
|
+
// to the very utility it corrects.
|
|
72
|
+
const utilities = css.slice(css.indexOf('@layer utilities'))
|
|
73
|
+
expect(utilities, 'canvas type restoration must be in @layer utilities').toContain(
|
|
74
|
+
'.prenta-admin .prenta-canvas .text-base',
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
for (const [utility, size] of [
|
|
78
|
+
['text-xs', '0.75rem'],
|
|
79
|
+
['text-sm', '0.875rem'],
|
|
80
|
+
['text-base', '1rem'],
|
|
81
|
+
['text-lg', '1.125rem'],
|
|
82
|
+
['text-xl', '1.25rem'],
|
|
83
|
+
['text-2xl', '1.5rem'],
|
|
84
|
+
]) {
|
|
85
|
+
const rule = new RegExp(
|
|
86
|
+
`\\.prenta-canvas \\.${utility}\\s*\\{[^}]*font-size:\\s*${size!.replace('.', '\\.')}`,
|
|
87
|
+
)
|
|
88
|
+
expect(utilities, `${utility} is not restored to its stock size`).toMatch(rule)
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('only references container steps that exist', () => {
|
|
93
|
+
const declared = new Set(['@pv-sm', '@pv-md', '@pv-lg', '@pv-xl', '@pv-2xl'])
|
|
94
|
+
const used = new Set<string>()
|
|
95
|
+
for (const file of sectionFiles()) {
|
|
96
|
+
const source = readFileSync(SECTIONS_DIR + file, 'utf8')
|
|
97
|
+
for (const match of source.matchAll(/@pv-[\w-]+?(?=:)/g)) used.add(match[0])
|
|
98
|
+
}
|
|
99
|
+
// A typo'd step (`@pv-xxl:`) compiles to nothing and silently never applies.
|
|
100
|
+
expect([...used].filter((u) => !declared.has(u))).toEqual([])
|
|
101
|
+
expect(used.size).toBeGreaterThan(0)
|
|
102
|
+
})
|
|
103
|
+
})
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest'
|
|
2
|
+
import pkg from '../../../package.json' with { type: 'json' }
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* `@prenta/admin` exports a precompiled stylesheet
|
|
6
|
+
* (`@prenta/admin/styles/precompiled.css`) that consumers import purely for its
|
|
7
|
+
* side effects. `sideEffects: false` tells a bundler such an import is safe to
|
|
8
|
+
* eliminate — the admin would then render unstyled, with no error anywhere.
|
|
9
|
+
*
|
|
10
|
+
* The field is currently ABSENT, which is the safe default: bundlers assume
|
|
11
|
+
* side effects. This test exists so it stays safe. `@prenta/sections-react` hit
|
|
12
|
+
* exactly this problem and had to be corrected before publish.
|
|
13
|
+
*
|
|
14
|
+
* If someone does add the field to enable tree-shaking, it must be an array
|
|
15
|
+
* that keeps CSS side-effectful, not `false`.
|
|
16
|
+
*/
|
|
17
|
+
describe('@prenta/admin packaging', () => {
|
|
18
|
+
it('never declares itself free of side effects', () => {
|
|
19
|
+
const sideEffects = (pkg as { sideEffects?: unknown }).sideEffects
|
|
20
|
+
expect(
|
|
21
|
+
sideEffects,
|
|
22
|
+
'sideEffects: false lets bundlers drop the precompiled CSS import',
|
|
23
|
+
).not.toBe(false)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('keeps CSS side-effectful if the field is ever narrowed to an array', () => {
|
|
27
|
+
const sideEffects = (pkg as { sideEffects?: unknown }).sideEffects
|
|
28
|
+
if (!Array.isArray(sideEffects)) return
|
|
29
|
+
expect(
|
|
30
|
+
sideEffects.some((p) => String(p).endsWith('.css')),
|
|
31
|
+
'an array form must still list CSS',
|
|
32
|
+
).toBe(true)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('still exports the stylesheet consumers import', () => {
|
|
36
|
+
expect(pkg.exports['./styles/precompiled.css']).toBe('./dist/prenta-admin.css')
|
|
37
|
+
})
|
|
38
|
+
})
|