canopycms 0.0.0 → 0.0.2
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/dist/auth/plugin.d.ts +8 -0
- package/dist/auth/plugin.d.ts.map +1 -1
- package/dist/build-mode.d.ts +15 -5
- package/dist/build-mode.d.ts.map +1 -1
- package/dist/build-mode.js +18 -8
- package/dist/build-mode.js.map +1 -1
- package/dist/cli/init.d.ts +2 -2
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +37 -36
- package/dist/cli/init.js.map +1 -1
- package/dist/cli/template-files/ai-config.ts.template +21 -0
- package/dist/cli/template-files/ai-route.ts.template +10 -0
- package/dist/cli/template-files/canopy.ts.template +24 -0
- package/dist/cli/templates.d.ts +5 -1
- package/dist/cli/templates.d.ts.map +1 -1
- package/dist/cli/templates.js +9 -2
- package/dist/cli/templates.js.map +1 -1
- package/dist/config/schemas/config.d.ts +4 -0
- package/dist/config/schemas/config.d.ts.map +1 -1
- package/dist/config/schemas/config.js +2 -0
- package/dist/config/schemas/config.js.map +1 -1
- package/dist/config/types.d.ts +5 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/content-reader.js +2 -2
- package/dist/content-reader.js.map +1 -1
- package/dist/context.js +5 -5
- package/dist/context.js.map +1 -1
- package/dist/operating-mode/client-unsafe-strategy.d.ts.map +1 -1
- package/dist/operating-mode/client-unsafe-strategy.js +15 -18
- package/dist/operating-mode/client-unsafe-strategy.js.map +1 -1
- package/dist/operating-mode/types.d.ts +8 -0
- package/dist/operating-mode/types.d.ts.map +1 -1
- package/dist/server.d.ts +2 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +2 -0
- package/dist/server.js.map +1 -1
- package/package.json +5 -4
- package/src/cli/init.ts +43 -38
- package/dist/__integration__/fixtures/content-seeds.d.ts +0 -43
- package/dist/__integration__/fixtures/content-seeds.d.ts.map +0 -1
- package/dist/__integration__/fixtures/content-seeds.js +0 -99
- package/dist/__integration__/fixtures/content-seeds.js.map +0 -1
- package/dist/__integration__/fixtures/schemas.d.ts +0 -12
- package/dist/__integration__/fixtures/schemas.d.ts.map +0 -1
- package/dist/__integration__/fixtures/schemas.js +0 -65
- package/dist/__integration__/fixtures/schemas.js.map +0 -1
- package/dist/__integration__/test-utils/api-client.d.ts +0 -123
- package/dist/__integration__/test-utils/api-client.d.ts.map +0 -1
- package/dist/__integration__/test-utils/api-client.js +0 -118
- package/dist/__integration__/test-utils/api-client.js.map +0 -1
- package/dist/__integration__/test-utils/multi-user.d.ts +0 -25
- package/dist/__integration__/test-utils/multi-user.d.ts.map +0 -1
- package/dist/__integration__/test-utils/multi-user.js +0 -105
- package/dist/__integration__/test-utils/multi-user.js.map +0 -1
- package/dist/__integration__/test-utils/test-workspace.d.ts +0 -25
- package/dist/__integration__/test-utils/test-workspace.d.ts.map +0 -1
- package/dist/__integration__/test-utils/test-workspace.js +0 -102
- package/dist/__integration__/test-utils/test-workspace.js.map +0 -1
- package/dist/editor/BranchManager.stories.d.ts +0 -8
- package/dist/editor/BranchManager.stories.d.ts.map +0 -1
- package/dist/editor/BranchManager.stories.js +0 -74
- package/dist/editor/BranchManager.stories.js.map +0 -1
- package/dist/editor/CanopyEditor.stories.d.ts +0 -7
- package/dist/editor/CanopyEditor.stories.d.ts.map +0 -1
- package/dist/editor/CanopyEditor.stories.js +0 -99
- package/dist/editor/CanopyEditor.stories.js.map +0 -1
- package/dist/editor/CommentsPanel.stories.d.ts +0 -10
- package/dist/editor/CommentsPanel.stories.d.ts.map +0 -1
- package/dist/editor/CommentsPanel.stories.js +0 -175
- package/dist/editor/CommentsPanel.stories.js.map +0 -1
- package/dist/editor/Editor.stories.d.ts +0 -7
- package/dist/editor/Editor.stories.d.ts.map +0 -1
- package/dist/editor/Editor.stories.js +0 -95
- package/dist/editor/Editor.stories.js.map +0 -1
- package/dist/editor/EditorPanes.stories.d.ts +0 -7
- package/dist/editor/EditorPanes.stories.d.ts.map +0 -1
- package/dist/editor/EditorPanes.stories.js +0 -116
- package/dist/editor/EditorPanes.stories.js.map +0 -1
- package/dist/editor/EntryNavigator.stories.d.ts +0 -8
- package/dist/editor/EntryNavigator.stories.d.ts.map +0 -1
- package/dist/editor/EntryNavigator.stories.js +0 -42
- package/dist/editor/EntryNavigator.stories.js.map +0 -1
- package/dist/editor/FormRenderer.stories.d.ts +0 -7
- package/dist/editor/FormRenderer.stories.d.ts.map +0 -1
- package/dist/editor/FormRenderer.stories.js +0 -115
- package/dist/editor/FormRenderer.stories.js.map +0 -1
- package/dist/editor/GroupManager.stories.d.ts +0 -19
- package/dist/editor/GroupManager.stories.d.ts.map +0 -1
- package/dist/editor/GroupManager.stories.js +0 -265
- package/dist/editor/GroupManager.stories.js.map +0 -1
- package/dist/editor/PermissionManager.stories.d.ts +0 -20
- package/dist/editor/PermissionManager.stories.d.ts.map +0 -1
- package/dist/editor/PermissionManager.stories.js +0 -506
- package/dist/editor/PermissionManager.stories.js.map +0 -1
- package/dist/editor/comments/FieldWrapper.stories.d.ts +0 -10
- package/dist/editor/comments/FieldWrapper.stories.d.ts.map +0 -1
- package/dist/editor/comments/FieldWrapper.stories.js +0 -173
- package/dist/editor/comments/FieldWrapper.stories.js.map +0 -1
- package/dist/editor/fields/BlockField.stories.d.ts +0 -7
- package/dist/editor/fields/BlockField.stories.d.ts.map +0 -1
- package/dist/editor/fields/BlockField.stories.js +0 -50
- package/dist/editor/fields/BlockField.stories.js.map +0 -1
- package/dist/editor/fields/fields.stories.d.ts +0 -8
- package/dist/editor/fields/fields.stories.d.ts.map +0 -1
- package/dist/editor/fields/fields.stories.js +0 -34
- package/dist/editor/fields/fields.stories.js.map +0 -1
- package/dist/test-utils/api-test-helpers.d.ts +0 -238
- package/dist/test-utils/api-test-helpers.d.ts.map +0 -1
- package/dist/test-utils/api-test-helpers.js +0 -347
- package/dist/test-utils/api-test-helpers.js.map +0 -1
- package/dist/test-utils/console-spy.d.ts +0 -56
- package/dist/test-utils/console-spy.d.ts.map +0 -1
- package/dist/test-utils/console-spy.js +0 -81
- package/dist/test-utils/console-spy.js.map +0 -1
- package/dist/test-utils/git-helpers.d.ts +0 -21
- package/dist/test-utils/git-helpers.d.ts.map +0 -1
- package/dist/test-utils/git-helpers.js +0 -23
- package/dist/test-utils/git-helpers.js.map +0 -1
- package/dist/test-utils/index.d.ts +0 -5
- package/dist/test-utils/index.d.ts.map +0 -1
- package/dist/test-utils/index.js +0 -4
- package/dist/test-utils/index.js.map +0 -1
- package/src/__integration__/errors/invalid-content.test.ts +0 -238
- package/src/__integration__/errors/permission-denied.test.ts +0 -220
- package/src/__integration__/fixtures/content-seeds.ts +0 -105
- package/src/__integration__/fixtures/schemas.ts +0 -67
- package/src/__integration__/initialization/prod-sim-init.test.ts +0 -139
- package/src/__integration__/permissions/path-permissions.test.ts +0 -314
- package/src/__integration__/permissions/role-permissions.test.ts +0 -354
- package/src/__integration__/permissions/settings-branch-isolation.test.ts +0 -317
- package/src/__integration__/settings/groups-api.test.ts +0 -403
- package/src/__integration__/test-utils/api-client.ts +0 -167
- package/src/__integration__/test-utils/multi-user.ts +0 -129
- package/src/__integration__/test-utils/test-workspace.ts +0 -130
- package/src/__integration__/user/user-context.test.ts +0 -174
- package/src/__integration__/validation/input-validation.test.ts +0 -166
- package/src/__integration__/workflows/api-editing-workflow.test.ts +0 -244
- package/src/__integration__/workflows/conflict-resolution.test.ts +0 -259
- package/src/__integration__/workflows/editing-workflow.test.ts +0 -205
- package/src/__integration__/workflows/review-workflow.test.ts +0 -260
- package/src/ai/__tests__/build.integration.test.ts +0 -224
- package/src/ai/__tests__/generate.integration.test.ts +0 -495
- package/src/ai/__tests__/handler.integration.test.ts +0 -212
- package/src/ai/__tests__/json-to-markdown.test.ts +0 -553
- package/src/ai/generate.ts +0 -410
- package/src/ai/handler.ts +0 -123
- package/src/ai/index.ts +0 -26
- package/src/ai/json-to-markdown.ts +0 -424
- package/src/ai/resolve-branch.ts +0 -34
- package/src/ai/types.ts +0 -160
- package/src/api/AGENTS.md +0 -81
- package/src/api/__test__/mock-client.ts +0 -404
- package/src/api/assets.test.ts +0 -140
- package/src/api/assets.ts +0 -154
- package/src/api/branch-merge.test.ts +0 -163
- package/src/api/branch-merge.ts +0 -113
- package/src/api/branch-review.test.ts +0 -297
- package/src/api/branch-review.ts +0 -136
- package/src/api/branch-status.test.ts +0 -85
- package/src/api/branch-status.ts +0 -153
- package/src/api/branch-withdraw.test.ts +0 -146
- package/src/api/branch-withdraw.ts +0 -81
- package/src/api/branch-workflow.integration.test.ts +0 -578
- package/src/api/branch.test.ts +0 -620
- package/src/api/branch.ts +0 -492
- package/src/api/client.test.ts +0 -349
- package/src/api/client.ts +0 -506
- package/src/api/comments.test.ts +0 -285
- package/src/api/comments.ts +0 -210
- package/src/api/content.test.ts +0 -345
- package/src/api/content.ts +0 -454
- package/src/api/entries.test.ts +0 -1339
- package/src/api/entries.ts +0 -650
- package/src/api/github-sync.ts +0 -144
- package/src/api/groups.test.ts +0 -1013
- package/src/api/groups.ts +0 -375
- package/src/api/guards.test.ts +0 -533
- package/src/api/guards.ts +0 -271
- package/src/api/index.ts +0 -87
- package/src/api/permissions.test.ts +0 -766
- package/src/api/permissions.ts +0 -334
- package/src/api/reference-options.ts +0 -118
- package/src/api/resolve-references.ts +0 -107
- package/src/api/route-builder.ts +0 -289
- package/src/api/schema.test.ts +0 -840
- package/src/api/schema.ts +0 -936
- package/src/api/security.test.ts +0 -233
- package/src/api/settings-helpers.ts +0 -84
- package/src/api/types.ts +0 -40
- package/src/api/user.test.ts +0 -127
- package/src/api/user.ts +0 -42
- package/src/api/validators.test.ts +0 -275
- package/src/api/validators.ts +0 -176
- package/src/asset-store.test.ts +0 -37
- package/src/asset-store.ts +0 -110
- package/src/auth/cache.ts +0 -7
- package/src/auth/caching-auth-plugin.test.ts +0 -154
- package/src/auth/caching-auth-plugin.ts +0 -109
- package/src/auth/context-helpers.ts +0 -75
- package/src/auth/file-based-auth-cache.test.ts +0 -257
- package/src/auth/file-based-auth-cache.ts +0 -279
- package/src/auth/index.ts +0 -12
- package/src/auth/plugin.ts +0 -51
- package/src/auth/types.ts +0 -38
- package/src/authorization/__tests__/branch.test.ts +0 -260
- package/src/authorization/__tests__/content.test.ts +0 -142
- package/src/authorization/__tests__/path.test.ts +0 -133
- package/src/authorization/__tests__/permissions-loader.test.ts +0 -200
- package/src/authorization/branch.ts +0 -94
- package/src/authorization/content.ts +0 -93
- package/src/authorization/groups/index.ts +0 -11
- package/src/authorization/groups/loader.ts +0 -127
- package/src/authorization/groups/schema.ts +0 -48
- package/src/authorization/helpers.ts +0 -48
- package/src/authorization/index.ts +0 -84
- package/src/authorization/path.ts +0 -112
- package/src/authorization/permissions/index.ts +0 -11
- package/src/authorization/permissions/loader.ts +0 -116
- package/src/authorization/permissions/schema.ts +0 -66
- package/src/authorization/test-utils.ts +0 -15
- package/src/authorization/types.ts +0 -66
- package/src/authorization/validation.test.ts +0 -100
- package/src/authorization/validation.ts +0 -62
- package/src/branch-metadata.test.ts +0 -168
- package/src/branch-metadata.ts +0 -166
- package/src/branch-registry.test.ts +0 -248
- package/src/branch-registry.ts +0 -152
- package/src/branch-schema-cache.test.ts +0 -275
- package/src/branch-schema-cache.ts +0 -189
- package/src/branch-workspace.test.ts +0 -183
- package/src/branch-workspace.ts +0 -124
- package/src/build/generate-ai-content.ts +0 -78
- package/src/build/index.ts +0 -8
- package/src/build-mode.ts +0 -27
- package/src/cli/generate-ai-content.ts +0 -100
- package/src/cli/init.test.ts +0 -240
- package/src/cli/templates/canopy.ts.template +0 -55
- package/src/cli/templates.ts +0 -47
- package/src/client.ts +0 -12
- package/src/comment-store.test.ts +0 -442
- package/src/comment-store.ts +0 -301
- package/src/config/__tests__/config.test.ts +0 -513
- package/src/config/flatten.ts +0 -174
- package/src/config/helpers.ts +0 -167
- package/src/config/index.ts +0 -86
- package/src/config/schemas/collection.ts +0 -67
- package/src/config/schemas/config.ts +0 -77
- package/src/config/schemas/field.ts +0 -108
- package/src/config/schemas/media.ts +0 -27
- package/src/config/schemas/permissions.ts +0 -21
- package/src/config/types.ts +0 -321
- package/src/config/validation.ts +0 -70
- package/src/config-test.ts +0 -65
- package/src/config.ts +0 -11
- package/src/content-id-index.test.ts +0 -512
- package/src/content-id-index.ts +0 -479
- package/src/content-reader.test.ts +0 -478
- package/src/content-reader.ts +0 -214
- package/src/content-store.test.ts +0 -1126
- package/src/content-store.ts +0 -793
- package/src/context.ts +0 -111
- package/src/editor/BranchManager.stories.tsx +0 -80
- package/src/editor/BranchManager.test.tsx +0 -324
- package/src/editor/BranchManager.tsx +0 -461
- package/src/editor/CanopyEditor.stories.tsx +0 -128
- package/src/editor/CanopyEditor.test.tsx +0 -81
- package/src/editor/CanopyEditor.tsx +0 -73
- package/src/editor/CanopyEditorPage.test.tsx +0 -59
- package/src/editor/CanopyEditorPage.tsx +0 -25
- package/src/editor/CommentsPanel.stories.tsx +0 -184
- package/src/editor/CommentsPanel.tsx +0 -338
- package/src/editor/Editor.integration.test.tsx +0 -227
- package/src/editor/Editor.stories.tsx +0 -119
- package/src/editor/Editor.tsx +0 -1221
- package/src/editor/EditorPanes.stories.tsx +0 -256
- package/src/editor/EditorPanes.test.tsx +0 -77
- package/src/editor/EditorPanes.tsx +0 -180
- package/src/editor/EntryNavigator.stories.tsx +0 -65
- package/src/editor/EntryNavigator.test.tsx +0 -598
- package/src/editor/EntryNavigator.tsx +0 -665
- package/src/editor/FormRenderer.stories.tsx +0 -212
- package/src/editor/FormRenderer.test.tsx +0 -194
- package/src/editor/FormRenderer.tsx +0 -432
- package/src/editor/GroupManager.stories.tsx +0 -301
- package/src/editor/GroupManager.test.tsx +0 -682
- package/src/editor/GroupManager.tsx +0 -9
- package/src/editor/PermissionManager.stories.tsx +0 -539
- package/src/editor/PermissionManager.test.tsx +0 -864
- package/src/editor/PermissionManager.tsx +0 -12
- package/src/editor/canopy-path.test.ts +0 -23
- package/src/editor/canopy-path.ts +0 -52
- package/src/editor/client-reference-resolver.ts +0 -118
- package/src/editor/comments/BranchComments.tsx +0 -93
- package/src/editor/comments/EntryComments.tsx +0 -94
- package/src/editor/comments/FieldWrapper.stories.tsx +0 -210
- package/src/editor/comments/FieldWrapper.tsx +0 -129
- package/src/editor/comments/InlineCommentThread.test.tsx +0 -384
- package/src/editor/comments/InlineCommentThread.tsx +0 -246
- package/src/editor/comments/ThreadCarousel.test.tsx +0 -393
- package/src/editor/comments/ThreadCarousel.tsx +0 -525
- package/src/editor/components/ConfirmDeleteModal.tsx +0 -49
- package/src/editor/components/EditorContext.tsx +0 -49
- package/src/editor/components/EditorFooter.tsx +0 -47
- package/src/editor/components/EditorHeader.tsx +0 -492
- package/src/editor/components/EditorSidebar.tsx +0 -193
- package/src/editor/components/EntryCreateModal.tsx +0 -193
- package/src/editor/components/RenameEntryModal.tsx +0 -152
- package/src/editor/components/UserBadge.test.tsx +0 -274
- package/src/editor/components/UserBadge.tsx +0 -240
- package/src/editor/components/index.ts +0 -6
- package/src/editor/context/ApiClientContext.tsx +0 -56
- package/src/editor/context/EditorStateContext.tsx +0 -221
- package/src/editor/context/index.ts +0 -40
- package/src/editor/editor-config.test.ts +0 -385
- package/src/editor/editor-config.ts +0 -94
- package/src/editor/editor-utils.test.ts +0 -772
- package/src/editor/editor-utils.ts +0 -303
- package/src/editor/env.ts +0 -4
- package/src/editor/fields/BlockField.stories.tsx +0 -79
- package/src/editor/fields/BlockField.tsx +0 -267
- package/src/editor/fields/CodeField.tsx +0 -41
- package/src/editor/fields/MarkdownField.tsx +0 -205
- package/src/editor/fields/ObjectField.tsx +0 -71
- package/src/editor/fields/ReferenceField.tsx +0 -138
- package/src/editor/fields/SelectField.tsx +0 -76
- package/src/editor/fields/TextField.tsx +0 -35
- package/src/editor/fields/ToggleField.tsx +0 -37
- package/src/editor/fields/fields.stories.tsx +0 -40
- package/src/editor/group-manager/ExternalGroupsTab.tsx +0 -114
- package/src/editor/group-manager/GroupCard.tsx +0 -102
- package/src/editor/group-manager/GroupForm.tsx +0 -66
- package/src/editor/group-manager/InternalGroupsTab.tsx +0 -147
- package/src/editor/group-manager/MemberList.tsx +0 -184
- package/src/editor/group-manager/hooks/useExternalGroupSearch.ts +0 -63
- package/src/editor/group-manager/hooks/useGroupState.ts +0 -134
- package/src/editor/group-manager/hooks/useUserSearch.ts +0 -84
- package/src/editor/group-manager/index.tsx +0 -210
- package/src/editor/group-manager/types.ts +0 -28
- package/src/editor/hooks/README.md +0 -26
- package/src/editor/hooks/__test__/test-utils.tsx +0 -183
- package/src/editor/hooks/index.ts +0 -23
- package/src/editor/hooks/useBranchActions.test.tsx +0 -267
- package/src/editor/hooks/useBranchActions.tsx +0 -121
- package/src/editor/hooks/useBranchManager.test.tsx +0 -391
- package/src/editor/hooks/useBranchManager.tsx +0 -326
- package/src/editor/hooks/useCommentSystem.test.ts +0 -615
- package/src/editor/hooks/useCommentSystem.ts +0 -347
- package/src/editor/hooks/useDraftManager.test.ts +0 -375
- package/src/editor/hooks/useDraftManager.ts +0 -259
- package/src/editor/hooks/useEditorLayout.test.ts +0 -147
- package/src/editor/hooks/useEditorLayout.ts +0 -67
- package/src/editor/hooks/useEntryManager.test.ts +0 -588
- package/src/editor/hooks/useEntryManager.ts +0 -387
- package/src/editor/hooks/useGroupManager.test.ts +0 -277
- package/src/editor/hooks/useGroupManager.ts +0 -139
- package/src/editor/hooks/usePermissionManager.test.ts +0 -211
- package/src/editor/hooks/usePermissionManager.ts +0 -113
- package/src/editor/hooks/useReferenceResolution.ts +0 -248
- package/src/editor/hooks/useSchemaManager.test.ts +0 -370
- package/src/editor/hooks/useSchemaManager.ts +0 -310
- package/src/editor/hooks/useUserContext.tsx +0 -57
- package/src/editor/hooks/useUserMetadata.test.ts +0 -191
- package/src/editor/hooks/useUserMetadata.ts +0 -71
- package/src/editor/permission-manager/GroupSelector.tsx +0 -73
- package/src/editor/permission-manager/PermissionEditor.tsx +0 -321
- package/src/editor/permission-manager/PermissionLevelBadge.tsx +0 -53
- package/src/editor/permission-manager/PermissionTree.tsx +0 -237
- package/src/editor/permission-manager/UserSelector.tsx +0 -95
- package/src/editor/permission-manager/constants.tsx +0 -18
- package/src/editor/permission-manager/hooks/useGroupsAndUsers.ts +0 -153
- package/src/editor/permission-manager/hooks/usePermissionTree.ts +0 -200
- package/src/editor/permission-manager/index.tsx +0 -294
- package/src/editor/permission-manager/types.ts +0 -58
- package/src/editor/permission-manager/utils.ts +0 -179
- package/src/editor/preview-bridge.test.tsx +0 -50
- package/src/editor/preview-bridge.tsx +0 -294
- package/src/editor/schema-editor/CollectionEditor.test.tsx +0 -238
- package/src/editor/schema-editor/CollectionEditor.tsx +0 -520
- package/src/editor/schema-editor/EntryTypeEditor.test.tsx +0 -215
- package/src/editor/schema-editor/EntryTypeEditor.tsx +0 -367
- package/src/editor/schema-editor/index.ts +0 -19
- package/src/editor/setup-test-dom.ts +0 -10
- package/src/editor/test-setup.ts +0 -33
- package/src/editor/theme.tsx +0 -119
- package/src/editor/utils/env.ts +0 -39
- package/src/entry-schema-registry.test.ts +0 -281
- package/src/entry-schema-registry.ts +0 -121
- package/src/entry-schema.ts +0 -84
- package/src/git-manager.test.ts +0 -552
- package/src/git-manager.ts +0 -667
- package/src/github-service.test.ts +0 -312
- package/src/github-service.ts +0 -295
- package/src/http/handler.test.ts +0 -275
- package/src/http/handler.ts +0 -280
- package/src/http/index.ts +0 -11
- package/src/http/router.ts +0 -164
- package/src/http/types.ts +0 -44
- package/src/id.test.ts +0 -48
- package/src/id.ts +0 -22
- package/src/index.ts +0 -26
- package/src/operating-mode/__tests__/strategies.test.ts +0 -511
- package/src/operating-mode/client-safe-strategy.ts +0 -184
- package/src/operating-mode/client-unsafe-strategy.ts +0 -303
- package/src/operating-mode/client.ts +0 -13
- package/src/operating-mode/index.ts +0 -34
- package/src/operating-mode/types.ts +0 -186
- package/src/paths/__tests__/branch.test.ts +0 -53
- package/src/paths/__tests__/normalize.test.ts +0 -141
- package/src/paths/__tests__/resolve.test.ts +0 -207
- package/src/paths/__tests__/validation.test.ts +0 -61
- package/src/paths/branch.ts +0 -115
- package/src/paths/index.ts +0 -73
- package/src/paths/normalize-server.ts +0 -40
- package/src/paths/normalize.ts +0 -107
- package/src/paths/resolve.ts +0 -61
- package/src/paths/test-utils.ts +0 -37
- package/src/paths/types.ts +0 -68
- package/src/paths/validation.test.ts +0 -480
- package/src/paths/validation.ts +0 -391
- package/src/reference-resolver.test.ts +0 -107
- package/src/reference-resolver.ts +0 -157
- package/src/schema/index.ts +0 -29
- package/src/schema/meta-loader.ts +0 -366
- package/src/schema/resolver.ts +0 -83
- package/src/schema/schema-store-types.ts +0 -56
- package/src/schema/schema-store.test.ts +0 -816
- package/src/schema/schema-store.ts +0 -795
- package/src/schema/types.ts +0 -33
- package/src/schema-meta-loader.test.ts +0 -447
- package/src/server.ts +0 -15
- package/src/services.test.ts +0 -559
- package/src/services.ts +0 -373
- package/src/settings-branch-utils.ts +0 -53
- package/src/settings-workspace.ts +0 -156
- package/src/task-queue/README.md +0 -144
- package/src/task-queue/index.ts +0 -14
- package/src/task-queue/task-queue.test.ts +0 -524
- package/src/task-queue/task-queue.ts +0 -514
- package/src/task-queue/types.ts +0 -41
- package/src/test-utils/api-test-helpers.ts +0 -445
- package/src/test-utils/console-spy.test.ts +0 -14
- package/src/test-utils/console-spy.ts +0 -125
- package/src/test-utils/git-helpers.ts +0 -31
- package/src/test-utils/index.ts +0 -4
- package/src/types.ts +0 -54
- package/src/user.ts +0 -118
- package/src/utils/debug.test.ts +0 -114
- package/src/utils/debug.ts +0 -127
- package/src/utils/error.test.ts +0 -92
- package/src/utils/error.ts +0 -83
- package/src/utils/format.ts +0 -12
- package/src/validation/__tests__/field-traversal.test.ts +0 -263
- package/src/validation/deletion-checker.ts +0 -234
- package/src/validation/field-traversal.ts +0 -146
- package/src/validation/reference-validator.ts +0 -168
- package/src/worker/cms-worker-rebase.test.ts +0 -473
- package/src/worker/cms-worker.ts +0 -777
- package/src/worker/integration.test.ts +0 -289
- package/src/worker/task-queue-config.ts +0 -25
- package/src/worker/task-queue.test.ts +0 -452
- package/src/worker/task-queue.ts +0 -58
- /package/{src/cli/templates → dist/cli/template-files}/Dockerfile.cms.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/canopycms.config.ts.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/deploy-cms.yml.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/edit-page.tsx.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/route.ts.template +0 -0
- /package/{src/cli/templates → dist/cli/template-files}/schemas.ts.template +0 -0
|
@@ -1,294 +0,0 @@
|
|
|
1
|
-
'use client'
|
|
2
|
-
|
|
3
|
-
import type { CSSProperties } from 'react'
|
|
4
|
-
import { useEffect, useRef, useState } from 'react'
|
|
5
|
-
|
|
6
|
-
import { formatCanopyPath, type CanopyPathSegment } from './canopy-path'
|
|
7
|
-
|
|
8
|
-
export const __CANOPY_PREVIEW_CLIENT__ = true
|
|
9
|
-
|
|
10
|
-
export const CANOPY_PREVIEW_MESSAGE = 'canopycms:draft:update'
|
|
11
|
-
export const CANOPY_PREVIEW_FOCUS = 'canopycms:preview:focus'
|
|
12
|
-
export const CANOPY_PREVIEW_HIGHLIGHT = 'canopycms:preview:highlight'
|
|
13
|
-
export const CANOPY_PREVIEW_READY = 'canopycms:preview:ready'
|
|
14
|
-
|
|
15
|
-
export interface DraftUpdateMessage {
|
|
16
|
-
type: typeof CANOPY_PREVIEW_MESSAGE
|
|
17
|
-
path: string
|
|
18
|
-
data?: unknown
|
|
19
|
-
isLoading?: unknown
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export const sendDraftUpdate = (iframe: HTMLIFrameElement | null, message: DraftUpdateMessage) => {
|
|
23
|
-
if (!iframe?.contentWindow) return
|
|
24
|
-
iframe.contentWindow.postMessage(message, '*')
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface PreviewFocusMessage {
|
|
28
|
-
type: typeof CANOPY_PREVIEW_FOCUS
|
|
29
|
-
entryPath: string
|
|
30
|
-
fieldPath: string
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface HighlightMessage {
|
|
34
|
-
type: typeof CANOPY_PREVIEW_HIGHLIGHT
|
|
35
|
-
enabled: boolean
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Convenience hook that wires draft updates, focus emitter, and highlight toggling together.
|
|
40
|
-
* Returns live data plus helpers for setting data-canopy-path attributes.
|
|
41
|
-
*/
|
|
42
|
-
const resolvePreviewPath = (explicit?: string): string => {
|
|
43
|
-
if (explicit) return explicit
|
|
44
|
-
if (typeof window === 'undefined') return ''
|
|
45
|
-
return `${window.location.pathname}${window.location.search}`
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export const useCanopyPreview = <T,>(opts: { path?: string; initialData: T }) => {
|
|
49
|
-
const resolvedPath = resolvePreviewPath(opts.path)
|
|
50
|
-
const { data, isLoading } = usePreviewData<T>(resolvedPath, opts.initialData)
|
|
51
|
-
const highlightEnabled = usePreviewHighlight()
|
|
52
|
-
usePreviewFocusEmitter(resolvedPath)
|
|
53
|
-
|
|
54
|
-
const fieldProps = (canopyPath: string | CanopyPathSegment[]) => ({
|
|
55
|
-
'data-canopy-path': Array.isArray(canopyPath) ? formatCanopyPath(canopyPath) : canopyPath,
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
return { data, isLoading, highlightEnabled, fieldProps }
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Hook for preview pages to listen for draft updates from the parent editor.
|
|
63
|
-
* Returns both data and loading state.
|
|
64
|
-
*/
|
|
65
|
-
export const usePreviewData = <T,>(
|
|
66
|
-
path: string,
|
|
67
|
-
initialData: T,
|
|
68
|
-
): { data: T; isLoading: Record<string, boolean> } => {
|
|
69
|
-
const [data, setData] = useState<T>(initialData)
|
|
70
|
-
const [isLoading, setIsLoading] = useState<Record<string, boolean>>({})
|
|
71
|
-
|
|
72
|
-
useEffect(() => {
|
|
73
|
-
const handler = (event: MessageEvent) => {
|
|
74
|
-
const msg = event.data as DraftUpdateMessage
|
|
75
|
-
if (!msg || msg.type !== CANOPY_PREVIEW_MESSAGE || msg.path !== path) return
|
|
76
|
-
setData(msg.data as T)
|
|
77
|
-
if (msg.isLoading !== undefined) {
|
|
78
|
-
setIsLoading(msg.isLoading as Record<string, boolean>)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
window.addEventListener('message', handler)
|
|
82
|
-
// Notify parent that this preview page is ready to receive draft updates.
|
|
83
|
-
// This is needed because onLoad in the parent fires before React effects run,
|
|
84
|
-
// so the first postMessage from the parent arrives before this listener is set up.
|
|
85
|
-
if (window.parent !== window) {
|
|
86
|
-
window.parent.postMessage({ type: CANOPY_PREVIEW_READY, path }, '*')
|
|
87
|
-
}
|
|
88
|
-
return () => window.removeEventListener('message', handler)
|
|
89
|
-
}, [path])
|
|
90
|
-
|
|
91
|
-
return { data, isLoading }
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Hook for preview pages to listen for highlight mode and toggle an outline on clickable elements.
|
|
96
|
-
*/
|
|
97
|
-
export const usePreviewHighlight = () => {
|
|
98
|
-
const [enabled, setEnabled] = useState(false)
|
|
99
|
-
|
|
100
|
-
useEffect(() => {
|
|
101
|
-
const styleId = 'canopycms-preview-highlight-style'
|
|
102
|
-
let styleEl = document.getElementById(styleId) as HTMLStyleElement | null
|
|
103
|
-
if (enabled) {
|
|
104
|
-
if (!styleEl) {
|
|
105
|
-
styleEl = document.createElement('style')
|
|
106
|
-
styleEl.id = styleId
|
|
107
|
-
styleEl.textContent = `
|
|
108
|
-
[data-canopy-path] { outline: 2px dashed rgba(79,70,229,0.6); outline-offset: 3px; cursor: pointer; }
|
|
109
|
-
`
|
|
110
|
-
document.head.appendChild(styleEl)
|
|
111
|
-
}
|
|
112
|
-
} else if (styleEl) {
|
|
113
|
-
styleEl.remove()
|
|
114
|
-
}
|
|
115
|
-
}, [enabled])
|
|
116
|
-
|
|
117
|
-
useEffect(() => {
|
|
118
|
-
const handler = (event: MessageEvent) => {
|
|
119
|
-
const msg = event.data as HighlightMessage
|
|
120
|
-
if (msg?.type !== CANOPY_PREVIEW_HIGHLIGHT) return
|
|
121
|
-
setEnabled(Boolean(msg.enabled))
|
|
122
|
-
}
|
|
123
|
-
window.addEventListener('message', handler)
|
|
124
|
-
return () => window.removeEventListener('message', handler)
|
|
125
|
-
}, [])
|
|
126
|
-
|
|
127
|
-
return enabled
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Hook for preview pages to emit focus messages when elements with data-canopy-path are clicked.
|
|
132
|
-
*/
|
|
133
|
-
export const usePreviewFocusEmitter = (entryPath: string) => {
|
|
134
|
-
useEffect(() => {
|
|
135
|
-
const handleClick = (event: MouseEvent) => {
|
|
136
|
-
const target = event.target as HTMLElement | null
|
|
137
|
-
if (!target) return
|
|
138
|
-
const el = target.closest<HTMLElement>('[data-canopy-path]')
|
|
139
|
-
const fieldPath = el?.dataset.canopyPath
|
|
140
|
-
if (!fieldPath || !window.parent) return
|
|
141
|
-
const msg: PreviewFocusMessage = {
|
|
142
|
-
type: CANOPY_PREVIEW_FOCUS,
|
|
143
|
-
entryPath,
|
|
144
|
-
fieldPath,
|
|
145
|
-
}
|
|
146
|
-
window.parent.postMessage(msg, '*')
|
|
147
|
-
}
|
|
148
|
-
document.addEventListener('click', handleClick)
|
|
149
|
-
return () => document.removeEventListener('click', handleClick)
|
|
150
|
-
}, [entryPath])
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Lightweight iframe wrapper to keep the preview in sync with form state.
|
|
155
|
-
* It posts the latest draft data to the iframe after load and when data changes.
|
|
156
|
-
*/
|
|
157
|
-
export const PreviewFrame = ({
|
|
158
|
-
src,
|
|
159
|
-
path,
|
|
160
|
-
data,
|
|
161
|
-
isLoading,
|
|
162
|
-
className,
|
|
163
|
-
style,
|
|
164
|
-
highlightEnabled,
|
|
165
|
-
}: {
|
|
166
|
-
src: string
|
|
167
|
-
path: string
|
|
168
|
-
data?: unknown
|
|
169
|
-
isLoading?: unknown
|
|
170
|
-
className?: string
|
|
171
|
-
style?: CSSProperties
|
|
172
|
-
highlightEnabled?: boolean
|
|
173
|
-
}) => {
|
|
174
|
-
const iframeRef = useRef<HTMLIFrameElement>(null)
|
|
175
|
-
// Show progress bar while waiting for the preview's ready handshake.
|
|
176
|
-
const [syncPending, setSyncPending] = useState(data !== undefined)
|
|
177
|
-
|
|
178
|
-
// Reset when navigating to a different entry (src change = new iframe page load).
|
|
179
|
-
const [prevSrc, setPrevSrc] = useState(src)
|
|
180
|
-
if (src !== prevSrc) {
|
|
181
|
-
setPrevSrc(src)
|
|
182
|
-
setSyncPending(data !== undefined)
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Inject the progress bar keyframe animation once per page.
|
|
186
|
-
useEffect(() => {
|
|
187
|
-
const styleId = 'canopycms-preview-sync-style'
|
|
188
|
-
if (!document.getElementById(styleId)) {
|
|
189
|
-
const el = document.createElement('style')
|
|
190
|
-
el.id = styleId
|
|
191
|
-
el.textContent = `@keyframes canopy-preview-sync { 0% { transform: translateX(-100%); } 100% { transform: translateX(250%); } }`
|
|
192
|
-
document.head.appendChild(el)
|
|
193
|
-
}
|
|
194
|
-
}, [])
|
|
195
|
-
|
|
196
|
-
const post = () => {
|
|
197
|
-
if (data === undefined) return
|
|
198
|
-
sendDraftUpdate(iframeRef.current, {
|
|
199
|
-
type: CANOPY_PREVIEW_MESSAGE,
|
|
200
|
-
path,
|
|
201
|
-
data,
|
|
202
|
-
isLoading,
|
|
203
|
-
})
|
|
204
|
-
}
|
|
205
|
-
const postHighlight = () => {
|
|
206
|
-
if (!iframeRef.current?.contentWindow) return
|
|
207
|
-
const msg: HighlightMessage = {
|
|
208
|
-
type: CANOPY_PREVIEW_HIGHLIGHT,
|
|
209
|
-
enabled: Boolean(highlightEnabled),
|
|
210
|
-
}
|
|
211
|
-
iframeRef.current.contentWindow.postMessage(msg, '*')
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// Keep refs pointing at the latest closures so the ready handler below never goes stale.
|
|
215
|
-
const postRef = useRef(post)
|
|
216
|
-
const postHighlightRef = useRef(postHighlight)
|
|
217
|
-
useEffect(() => {
|
|
218
|
-
postRef.current = post
|
|
219
|
-
postHighlightRef.current = postHighlight
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
useEffect(() => {
|
|
223
|
-
post()
|
|
224
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- post is tracked via ref
|
|
225
|
-
}, [data, isLoading])
|
|
226
|
-
|
|
227
|
-
useEffect(() => {
|
|
228
|
-
postHighlight()
|
|
229
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps -- postHighlight is tracked via ref
|
|
230
|
-
}, [highlightEnabled])
|
|
231
|
-
|
|
232
|
-
// When the preview page's React effects have run and its message listener is ready,
|
|
233
|
-
// it sends CANOPY_PREVIEW_READY. We respond with the current data so the preview
|
|
234
|
-
// receives the draft even if it wasn't ready when onLoad fired.
|
|
235
|
-
useEffect(() => {
|
|
236
|
-
const handleReady = (event: MessageEvent) => {
|
|
237
|
-
if (!iframeRef.current || event.source !== iframeRef.current.contentWindow) return
|
|
238
|
-
if ((event.data as { type?: string })?.type === CANOPY_PREVIEW_READY) {
|
|
239
|
-
postRef.current()
|
|
240
|
-
postHighlightRef.current()
|
|
241
|
-
setSyncPending(false)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
window.addEventListener('message', handleReady)
|
|
245
|
-
return () => window.removeEventListener('message', handleReady)
|
|
246
|
-
}, [])
|
|
247
|
-
|
|
248
|
-
return (
|
|
249
|
-
<div className={className} style={{ position: 'relative', overflow: 'hidden', ...style }}>
|
|
250
|
-
{syncPending && (
|
|
251
|
-
<div
|
|
252
|
-
style={{
|
|
253
|
-
position: 'absolute',
|
|
254
|
-
top: 0,
|
|
255
|
-
left: 0,
|
|
256
|
-
right: 0,
|
|
257
|
-
height: 3,
|
|
258
|
-
zIndex: 1,
|
|
259
|
-
overflow: 'hidden',
|
|
260
|
-
}}
|
|
261
|
-
>
|
|
262
|
-
<div
|
|
263
|
-
style={{
|
|
264
|
-
position: 'absolute',
|
|
265
|
-
top: 0,
|
|
266
|
-
left: 0,
|
|
267
|
-
height: '100%',
|
|
268
|
-
width: '40%',
|
|
269
|
-
background: 'var(--mantine-color-blue-filled, #228be6)',
|
|
270
|
-
borderRadius: '0 2px 2px 0',
|
|
271
|
-
animation: 'canopy-preview-sync 1.5s ease-in-out infinite',
|
|
272
|
-
}}
|
|
273
|
-
/>
|
|
274
|
-
</div>
|
|
275
|
-
)}
|
|
276
|
-
<iframe
|
|
277
|
-
ref={iframeRef}
|
|
278
|
-
src={src}
|
|
279
|
-
style={{
|
|
280
|
-
display: 'block',
|
|
281
|
-
position: 'absolute',
|
|
282
|
-
inset: 0,
|
|
283
|
-
width: '100%',
|
|
284
|
-
height: '100%',
|
|
285
|
-
border: 'none',
|
|
286
|
-
}}
|
|
287
|
-
onLoad={() => {
|
|
288
|
-
post()
|
|
289
|
-
postHighlight()
|
|
290
|
-
}}
|
|
291
|
-
/>
|
|
292
|
-
</div>
|
|
293
|
-
)
|
|
294
|
-
}
|
|
@@ -1,238 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
import React from 'react'
|
|
3
|
-
import { cleanup, render, screen, waitFor } from '@testing-library/react'
|
|
4
|
-
import userEvent from '@testing-library/user-event'
|
|
5
|
-
import { afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
|
|
6
|
-
import { CollectionEditor, type ExistingCollection } from './CollectionEditor'
|
|
7
|
-
import { CanopyCMSProvider } from '../theme'
|
|
8
|
-
import { unsafeAsLogicalPath } from '../../paths/test-utils'
|
|
9
|
-
|
|
10
|
-
// Setup browser APIs
|
|
11
|
-
beforeAll(() => {
|
|
12
|
-
if (!window.matchMedia) {
|
|
13
|
-
window.matchMedia = ((query: string) =>
|
|
14
|
-
({
|
|
15
|
-
matches: false,
|
|
16
|
-
media: query,
|
|
17
|
-
onchange: null,
|
|
18
|
-
addListener: () => {},
|
|
19
|
-
removeListener: () => {},
|
|
20
|
-
addEventListener: () => {},
|
|
21
|
-
removeEventListener: () => {},
|
|
22
|
-
dispatchEvent: () => false,
|
|
23
|
-
}) as MediaQueryList) as typeof window.matchMedia
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (!window.ResizeObserver) {
|
|
27
|
-
class ResizeObserver {
|
|
28
|
-
observe() {}
|
|
29
|
-
unobserve() {}
|
|
30
|
-
disconnect() {}
|
|
31
|
-
}
|
|
32
|
-
;(window as unknown as { ResizeObserver: typeof ResizeObserver }).ResizeObserver =
|
|
33
|
-
ResizeObserver
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// Mock scrollIntoView for Mantine Combobox
|
|
37
|
-
Element.prototype.scrollIntoView = vi.fn()
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
afterEach(() => {
|
|
41
|
-
cleanup()
|
|
42
|
-
vi.restoreAllMocks()
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
const renderCollectionEditor = (
|
|
46
|
-
props: Partial<React.ComponentProps<typeof CollectionEditor>> = {},
|
|
47
|
-
) => {
|
|
48
|
-
const defaultProps: React.ComponentProps<typeof CollectionEditor> = {
|
|
49
|
-
isOpen: true,
|
|
50
|
-
editingCollection: null,
|
|
51
|
-
availableSchemas: ['postSchema', 'pageSchema', 'authorSchema'],
|
|
52
|
-
onSave: vi.fn(),
|
|
53
|
-
onClose: vi.fn(),
|
|
54
|
-
...props,
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
...render(
|
|
59
|
-
<CanopyCMSProvider>
|
|
60
|
-
<CollectionEditor {...defaultProps} />
|
|
61
|
-
</CanopyCMSProvider>,
|
|
62
|
-
),
|
|
63
|
-
props: defaultProps,
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
describe('CollectionEditor', () => {
|
|
68
|
-
describe('create mode', () => {
|
|
69
|
-
it('renders create modal with empty form', () => {
|
|
70
|
-
renderCollectionEditor()
|
|
71
|
-
|
|
72
|
-
// Title appears in both modal header and button - use getAllByText
|
|
73
|
-
const createCollectionTexts = screen.getAllByText('Create Collection')
|
|
74
|
-
expect(createCollectionTexts.length).toBeGreaterThan(0)
|
|
75
|
-
expect(screen.getByLabelText(/^Name/)).toBeTruthy()
|
|
76
|
-
expect(screen.getByLabelText(/^Label/)).toBeTruthy()
|
|
77
|
-
expect(screen.getByText(/No entry types defined/i)).toBeTruthy()
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('validates name is required', async () => {
|
|
81
|
-
const user = userEvent.setup()
|
|
82
|
-
const { props } = renderCollectionEditor()
|
|
83
|
-
|
|
84
|
-
// Try to save without name
|
|
85
|
-
await user.click(screen.getByRole('button', { name: /Create Collection/i }))
|
|
86
|
-
|
|
87
|
-
expect(screen.getByText(/Name is required/i)).toBeTruthy()
|
|
88
|
-
expect(props.onSave).not.toHaveBeenCalled()
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('validates name format', async () => {
|
|
92
|
-
const user = userEvent.setup()
|
|
93
|
-
const { props } = renderCollectionEditor()
|
|
94
|
-
|
|
95
|
-
await user.type(screen.getByLabelText(/^Name/), 'Invalid Name!')
|
|
96
|
-
await user.click(screen.getByRole('button', { name: /Create Collection/i }))
|
|
97
|
-
|
|
98
|
-
expect(screen.getByText(/must start with a letter/i)).toBeTruthy()
|
|
99
|
-
expect(props.onSave).not.toHaveBeenCalled()
|
|
100
|
-
})
|
|
101
|
-
|
|
102
|
-
it('validates at least one entry type is required', async () => {
|
|
103
|
-
const user = userEvent.setup()
|
|
104
|
-
const { props } = renderCollectionEditor()
|
|
105
|
-
|
|
106
|
-
await user.type(screen.getByLabelText(/^Name/), 'posts')
|
|
107
|
-
await user.click(screen.getByRole('button', { name: /Create Collection/i }))
|
|
108
|
-
|
|
109
|
-
expect(screen.getByText(/At least one entry type is required/i)).toBeTruthy()
|
|
110
|
-
expect(props.onSave).not.toHaveBeenCalled()
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
it('opens entry type editor when clicking add entry type', async () => {
|
|
114
|
-
const user = userEvent.setup()
|
|
115
|
-
renderCollectionEditor()
|
|
116
|
-
|
|
117
|
-
await user.click(screen.getByRole('button', { name: /Add Entry Type/i }))
|
|
118
|
-
|
|
119
|
-
// Entry type modal should appear - there are now multiple "Add Entry Type" texts
|
|
120
|
-
await waitFor(() => {
|
|
121
|
-
const elements = screen.getAllByText(/Add Entry Type/i)
|
|
122
|
-
expect(elements.length).toBeGreaterThan(1) // Title and button in nested modal
|
|
123
|
-
})
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('shows parent path when provided', () => {
|
|
127
|
-
renderCollectionEditor({ parentPath: unsafeAsLogicalPath('blog') })
|
|
128
|
-
|
|
129
|
-
expect(screen.getByText(/created inside/i)).toBeTruthy()
|
|
130
|
-
expect(screen.getByText('blog')).toBeTruthy()
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('closes modal on cancel', async () => {
|
|
134
|
-
const user = userEvent.setup()
|
|
135
|
-
const { props } = renderCollectionEditor()
|
|
136
|
-
|
|
137
|
-
await user.click(screen.getByRole('button', { name: /Cancel/i }))
|
|
138
|
-
|
|
139
|
-
expect(props.onClose).toHaveBeenCalled()
|
|
140
|
-
})
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
describe('edit mode', () => {
|
|
144
|
-
const existingCollection: ExistingCollection = {
|
|
145
|
-
name: 'posts',
|
|
146
|
-
label: 'Blog Posts',
|
|
147
|
-
logicalPath: unsafeAsLogicalPath('posts'),
|
|
148
|
-
entries: [
|
|
149
|
-
{
|
|
150
|
-
name: 'post',
|
|
151
|
-
label: 'Post',
|
|
152
|
-
format: 'mdx',
|
|
153
|
-
schema: 'postSchema',
|
|
154
|
-
default: true,
|
|
155
|
-
},
|
|
156
|
-
{
|
|
157
|
-
name: 'featured',
|
|
158
|
-
label: 'Featured',
|
|
159
|
-
format: 'json',
|
|
160
|
-
schema: 'postSchema',
|
|
161
|
-
},
|
|
162
|
-
],
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
it('renders edit modal with existing data', () => {
|
|
166
|
-
renderCollectionEditor({ editingCollection: existingCollection })
|
|
167
|
-
|
|
168
|
-
expect(screen.getByText(/Edit Collection: posts/)).toBeTruthy()
|
|
169
|
-
const nameInput = screen.getByLabelText(/^Name/) as HTMLInputElement
|
|
170
|
-
expect(nameInput.value).toBe('posts')
|
|
171
|
-
expect(nameInput.disabled).toBe(false) // Name field is now editable (it's just metadata)
|
|
172
|
-
const labelInput = screen.getByLabelText(/^Label/) as HTMLInputElement
|
|
173
|
-
expect(labelInput.value).toBe('Blog Posts')
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('displays existing entry types', () => {
|
|
177
|
-
renderCollectionEditor({ editingCollection: existingCollection })
|
|
178
|
-
|
|
179
|
-
expect(screen.getByText('post')).toBeTruthy()
|
|
180
|
-
expect(screen.getByText('featured')).toBeTruthy()
|
|
181
|
-
expect(screen.getByText('Default')).toBeTruthy() // Default badge
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('only includes changed fields in update', async () => {
|
|
185
|
-
const user = userEvent.setup()
|
|
186
|
-
const { props } = renderCollectionEditor({
|
|
187
|
-
editingCollection: existingCollection,
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
// Change only the label
|
|
191
|
-
const labelInput = screen.getByLabelText(/^Label/)
|
|
192
|
-
await user.clear(labelInput)
|
|
193
|
-
await user.type(labelInput, 'Updated Posts')
|
|
194
|
-
|
|
195
|
-
await user.click(screen.getByRole('button', { name: /Save Changes/i }))
|
|
196
|
-
|
|
197
|
-
expect(props.onSave).toHaveBeenCalledWith({ label: 'Updated Posts' }, false)
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
it('shows entry type count', () => {
|
|
201
|
-
renderCollectionEditor({ editingCollection: existingCollection })
|
|
202
|
-
|
|
203
|
-
// Both entry types should be visible
|
|
204
|
-
expect(screen.getByText('post')).toBeTruthy()
|
|
205
|
-
expect(screen.getByText('featured')).toBeTruthy()
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
it('shows singleton badge for single entry collection', () => {
|
|
209
|
-
const singleEntryCollection: ExistingCollection = {
|
|
210
|
-
name: 'settings',
|
|
211
|
-
logicalPath: unsafeAsLogicalPath('settings'),
|
|
212
|
-
entries: [{ name: 'config', format: 'json', schema: 'postSchema', maxItems: 1 }],
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
renderCollectionEditor({ editingCollection: singleEntryCollection })
|
|
216
|
-
|
|
217
|
-
expect(screen.getByText('config')).toBeTruthy()
|
|
218
|
-
expect(screen.getByText('Singleton')).toBeTruthy()
|
|
219
|
-
})
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
describe('error handling', () => {
|
|
223
|
-
it('displays external error', () => {
|
|
224
|
-
renderCollectionEditor({ error: 'Collection already exists' })
|
|
225
|
-
|
|
226
|
-
expect(screen.getByText(/Collection already exists/)).toBeTruthy()
|
|
227
|
-
})
|
|
228
|
-
|
|
229
|
-
it('shows saving state', () => {
|
|
230
|
-
renderCollectionEditor({ isSaving: true })
|
|
231
|
-
|
|
232
|
-
const saveButton = screen.getByRole('button', {
|
|
233
|
-
name: /Create Collection/i,
|
|
234
|
-
})
|
|
235
|
-
expect(saveButton.getAttribute('data-loading')).toBe('true')
|
|
236
|
-
})
|
|
237
|
-
})
|
|
238
|
-
})
|