canopycms 0.0.0 → 0.0.1
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/package.json +2 -3
- 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/Dockerfile.cms.template +0 -19
- package/src/cli/templates/canopy.ts.template +0 -55
- package/src/cli/templates/canopycms.config.ts.template +0 -11
- package/src/cli/templates/deploy-cms.yml.template +0 -27
- package/src/cli/templates/edit-page.tsx.template +0 -32
- package/src/cli/templates/route.ts.template +0 -12
- package/src/cli/templates/schemas.ts.template +0 -16
- 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
|
@@ -1,387 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useRef, useState } from 'react'
|
|
2
|
-
import { notifications } from '@mantine/notifications'
|
|
3
|
-
import type { ListEntriesResponse } from '../../api/entries'
|
|
4
|
-
import type { WriteContentBody } from '../../api/content'
|
|
5
|
-
import type { EditorEntry, EditorCollection } from '../Editor'
|
|
6
|
-
import type { LogicalPath } from '../../paths/types'
|
|
7
|
-
import type { FormValue } from '../FormRenderer'
|
|
8
|
-
import {
|
|
9
|
-
buildEntriesFromListResponse,
|
|
10
|
-
buildWritePayload,
|
|
11
|
-
normalizeContentPayload,
|
|
12
|
-
} from '../editor-utils'
|
|
13
|
-
import { useApiClient } from '../context'
|
|
14
|
-
|
|
15
|
-
export interface UseEntryManagerOptions {
|
|
16
|
-
initialEntries: EditorEntry[]
|
|
17
|
-
initialSelectedId?: string
|
|
18
|
-
branchName: string
|
|
19
|
-
collections?: EditorCollection[]
|
|
20
|
-
previewBaseByCollection?: Record<string, string>
|
|
21
|
-
resolvePreviewSrc: (entry: Partial<EditorEntry>) => string | undefined
|
|
22
|
-
setBusy: (busy: boolean) => void
|
|
23
|
-
contentRoot?: string
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface UseEntryManagerReturn {
|
|
27
|
-
selectedPath: string
|
|
28
|
-
setSelectedPath: (path: string) => void
|
|
29
|
-
entries: EditorEntry[]
|
|
30
|
-
setEntries: (entries: EditorEntry[]) => void
|
|
31
|
-
collections: EditorCollection[]
|
|
32
|
-
currentEntry: EditorEntry | undefined
|
|
33
|
-
navigatorOpen: boolean
|
|
34
|
-
setNavigatorOpen: (open: boolean) => void
|
|
35
|
-
refreshEntries: (branch?: string) => Promise<void>
|
|
36
|
-
handleCreateEntry: (collectionPath: LogicalPath, entryTypeName?: string) => Promise<void>
|
|
37
|
-
renameEntry: (path: string, newSlug: string) => Promise<void>
|
|
38
|
-
loadEntry: (entry: EditorEntry) => Promise<FormValue>
|
|
39
|
-
saveEntry: (entry: EditorEntry, value: FormValue) => Promise<FormValue>
|
|
40
|
-
collectionByPath: Map<LogicalPath, EditorCollection>
|
|
41
|
-
// Entry create modal state
|
|
42
|
-
createModalOpen: boolean
|
|
43
|
-
createModalCollection: EditorCollection | null
|
|
44
|
-
createModalError: string | null
|
|
45
|
-
createModalCreating: boolean
|
|
46
|
-
handleCreateModalSubmit: (slug: string, entryTypeName: string) => Promise<void>
|
|
47
|
-
closeCreateModal: () => void
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Custom hook for managing editor entries (CRUD operations).
|
|
52
|
-
*
|
|
53
|
-
* Handles:
|
|
54
|
-
* - Entry selection and navigation
|
|
55
|
-
* - Loading and saving entry data
|
|
56
|
-
* - Refreshing entry list from API
|
|
57
|
-
* - Creating new entries
|
|
58
|
-
* - URL synchronization for selected entry
|
|
59
|
-
*
|
|
60
|
-
* @example
|
|
61
|
-
* ```tsx
|
|
62
|
-
* const {
|
|
63
|
-
* selectedPath,
|
|
64
|
-
* entries,
|
|
65
|
-
* currentEntry,
|
|
66
|
-
* refreshEntries,
|
|
67
|
-
* handleCreateEntry,
|
|
68
|
-
* loadEntry,
|
|
69
|
-
* saveEntry
|
|
70
|
-
* } = useEntryManager({
|
|
71
|
-
* initialEntries: entries,
|
|
72
|
-
* branchName,
|
|
73
|
-
* collections,
|
|
74
|
-
* resolvePreviewSrc,
|
|
75
|
-
* setBusy
|
|
76
|
-
* })
|
|
77
|
-
* ```
|
|
78
|
-
*/
|
|
79
|
-
export function useEntryManager(options: UseEntryManagerOptions): UseEntryManagerReturn {
|
|
80
|
-
const apiClient = useApiClient()
|
|
81
|
-
const [entriesState, setEntriesState] = useState<EditorEntry[]>(options.initialEntries)
|
|
82
|
-
const [collectionsState, setCollectionsState] = useState<EditorCollection[]>(
|
|
83
|
-
options.collections || [],
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
// Initialize with prop value or empty (URL sync happens in effect after mount)
|
|
87
|
-
const [selectedPath, setSelectedPath] = useState<string>(options.initialSelectedId ?? '')
|
|
88
|
-
const [navigatorOpen, setNavigatorOpen] = useState(false)
|
|
89
|
-
const isInitialMount = useRef(true)
|
|
90
|
-
const hasSyncedFromUrl = useRef(false)
|
|
91
|
-
|
|
92
|
-
// Entry create modal state
|
|
93
|
-
const [createModalOpen, setCreateModalOpen] = useState(false)
|
|
94
|
-
const [createModalCollection, setCreateModalCollection] = useState<EditorCollection | null>(null)
|
|
95
|
-
const [createModalError, setCreateModalError] = useState<string | null>(null)
|
|
96
|
-
const [createModalCreating, setCreateModalCreating] = useState(false)
|
|
97
|
-
|
|
98
|
-
// Store the URL entry param on mount (before any effects change the URL)
|
|
99
|
-
const initialUrlEntry = useRef<string | null>(null)
|
|
100
|
-
if (typeof window !== 'undefined' && initialUrlEntry.current === null) {
|
|
101
|
-
const params = new URLSearchParams(window.location.search)
|
|
102
|
-
initialUrlEntry.current = params.get('entry')
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const collectionByPath = useMemo(() => {
|
|
106
|
-
const map = new Map<LogicalPath, EditorCollection>()
|
|
107
|
-
const walk = (collections: EditorCollection[]) => {
|
|
108
|
-
for (const c of collections) {
|
|
109
|
-
map.set(c.path, c)
|
|
110
|
-
if (c.children) {
|
|
111
|
-
walk(c.children)
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
walk(collectionsState)
|
|
116
|
-
return map
|
|
117
|
-
}, [collectionsState])
|
|
118
|
-
|
|
119
|
-
const currentEntry = useMemo(
|
|
120
|
-
() => entriesState.find((e) => e.path === selectedPath),
|
|
121
|
-
[entriesState, selectedPath],
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
const loadEntry = async (entry: EditorEntry) => {
|
|
125
|
-
if (!entry.collectionPath) {
|
|
126
|
-
throw new Error('Entry missing collectionPath')
|
|
127
|
-
}
|
|
128
|
-
// Build path from collectionPath and slug (if it's a collection entry)
|
|
129
|
-
const path = entry.slug ? `${entry.collectionPath}/${entry.slug}` : entry.collectionPath
|
|
130
|
-
const result = await apiClient.content.read({
|
|
131
|
-
branch: options.branchName,
|
|
132
|
-
path,
|
|
133
|
-
})
|
|
134
|
-
if (!result.ok) throw new Error(`Load failed: ${result.status}`)
|
|
135
|
-
return normalizeContentPayload(result.data)
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
const saveEntry = async (entry: EditorEntry, value: FormValue) => {
|
|
139
|
-
if (!entry.collectionPath) {
|
|
140
|
-
throw new Error('Entry missing collectionPath')
|
|
141
|
-
}
|
|
142
|
-
const payload = buildWritePayload(entry, value)
|
|
143
|
-
// Build path from collectionPath and slug (if it's a collection entry)
|
|
144
|
-
const path = entry.slug ? `${entry.collectionPath}/${entry.slug}` : entry.collectionPath
|
|
145
|
-
const result = await apiClient.content.write(
|
|
146
|
-
{
|
|
147
|
-
branch: options.branchName,
|
|
148
|
-
path,
|
|
149
|
-
},
|
|
150
|
-
payload as unknown as WriteContentBody, // buildWritePayload returns the correct shape
|
|
151
|
-
)
|
|
152
|
-
if (!result.ok) throw new Error(`Save failed: ${result.status}`)
|
|
153
|
-
return normalizeContentPayload(result.data)
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const refreshEntries = async (branch: string = options.branchName) => {
|
|
157
|
-
if (!branch) return
|
|
158
|
-
|
|
159
|
-
// Fetch schema from schema API
|
|
160
|
-
const schemaResult = await apiClient.schema.get({ branch })
|
|
161
|
-
if (!schemaResult.ok || !schemaResult.data) {
|
|
162
|
-
throw new Error(`Schema fetch failed: ${schemaResult.status}`)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Hydrate wire flatSchema: resolve schemaRef → schema from entrySchemas dict
|
|
166
|
-
const { entrySchemas } = schemaResult.data
|
|
167
|
-
const hydratedFlatSchema = schemaResult.data.flatSchema.map((item) =>
|
|
168
|
-
item.type === 'entry-type' ? { ...item, schema: entrySchemas[item.schemaRef] ?? [] } : item,
|
|
169
|
-
) as import('../../config').FlatSchemaItem[]
|
|
170
|
-
|
|
171
|
-
// Build editor collections from hydrated flatSchema
|
|
172
|
-
// Dynamic import: lazy-load heavier editor config; only needed after API data arrives
|
|
173
|
-
const { buildEditorCollections } = await import('../editor-config')
|
|
174
|
-
const collections = buildEditorCollections(hydratedFlatSchema)
|
|
175
|
-
setCollectionsState(collections)
|
|
176
|
-
|
|
177
|
-
// Fetch entries from entries API
|
|
178
|
-
const result = await apiClient.entries.list({ branch })
|
|
179
|
-
if (!result.ok) throw new Error(`Refresh failed: ${result.status}`)
|
|
180
|
-
const data = result.data as ListEntriesResponse
|
|
181
|
-
|
|
182
|
-
// Build entries with resolved schemas from flatSchema
|
|
183
|
-
const refreshed = buildEntriesFromListResponse({
|
|
184
|
-
response: data,
|
|
185
|
-
branchName: branch,
|
|
186
|
-
resolvePreviewSrc: (entry) => options.resolvePreviewSrc(entry) ?? '',
|
|
187
|
-
contentRoot: options.contentRoot || 'content',
|
|
188
|
-
flatSchema: hydratedFlatSchema,
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
setEntriesState(refreshed)
|
|
192
|
-
|
|
193
|
-
// Only auto-select newly created entry if there were already entries before
|
|
194
|
-
if (entriesState.length > 0) {
|
|
195
|
-
const newlyCreated = refreshed.find((e) => !entriesState.find((old) => old.path === e.path))
|
|
196
|
-
if (newlyCreated) {
|
|
197
|
-
setSelectedPath(newlyCreated.path)
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Open the create entry modal for the specified collection
|
|
204
|
-
*/
|
|
205
|
-
const handleCreateEntry = async (collectionPath: LogicalPath, _?: string) => {
|
|
206
|
-
const col = collectionByPath.get(collectionPath)
|
|
207
|
-
if (!col || col.type === 'entry') {
|
|
208
|
-
return
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
setCreateModalCollection(col)
|
|
212
|
-
setCreateModalError(null)
|
|
213
|
-
setCreateModalOpen(true)
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Handle entry creation from the modal
|
|
218
|
-
*/
|
|
219
|
-
const handleCreateModalSubmit = async (slug: string, entryTypeName: string) => {
|
|
220
|
-
if (!createModalCollection) return
|
|
221
|
-
|
|
222
|
-
setCreateModalCreating(true)
|
|
223
|
-
setCreateModalError(null)
|
|
224
|
-
|
|
225
|
-
try {
|
|
226
|
-
const selectedType = createModalCollection.entryTypes?.find((et) => et.name === entryTypeName)
|
|
227
|
-
const format = selectedType?.format || createModalCollection.format
|
|
228
|
-
|
|
229
|
-
const payload =
|
|
230
|
-
format === 'json' ? { format: 'json' as const, data: {} } : { format, data: {}, body: '' }
|
|
231
|
-
|
|
232
|
-
// Use collection path (e.g., "content/posts") not name (e.g., "posts")
|
|
233
|
-
const path = `${createModalCollection.path}/${slug}`
|
|
234
|
-
const result = await apiClient.content.write(
|
|
235
|
-
{
|
|
236
|
-
branch: options.branchName,
|
|
237
|
-
path,
|
|
238
|
-
entryType: entryTypeName,
|
|
239
|
-
},
|
|
240
|
-
payload as unknown as WriteContentBody,
|
|
241
|
-
)
|
|
242
|
-
|
|
243
|
-
if (!result.ok) {
|
|
244
|
-
const errorMsg = 'error' in result ? result.error : `Create failed: ${result.status}`
|
|
245
|
-
throw new Error(errorMsg)
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
await refreshEntries()
|
|
249
|
-
notifications.show({ message: 'Created new entry', color: 'green' })
|
|
250
|
-
setCreateModalOpen(false)
|
|
251
|
-
setCreateModalCollection(null)
|
|
252
|
-
} catch (err) {
|
|
253
|
-
console.error(err)
|
|
254
|
-
const errorMessage = err instanceof Error ? err.message : 'Create failed'
|
|
255
|
-
setCreateModalError(errorMessage)
|
|
256
|
-
} finally {
|
|
257
|
-
setCreateModalCreating(false)
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Close the create entry modal
|
|
263
|
-
*/
|
|
264
|
-
const closeCreateModal = () => {
|
|
265
|
-
setCreateModalOpen(false)
|
|
266
|
-
setCreateModalCollection(null)
|
|
267
|
-
setCreateModalError(null)
|
|
268
|
-
setCreateModalCreating(false)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Rename an entry's slug
|
|
273
|
-
*/
|
|
274
|
-
const renameEntry = async (path: string, newSlug: string): Promise<void> => {
|
|
275
|
-
options.setBusy(true)
|
|
276
|
-
try {
|
|
277
|
-
const result = await apiClient.content.renameEntry(
|
|
278
|
-
{
|
|
279
|
-
branch: options.branchName,
|
|
280
|
-
path,
|
|
281
|
-
},
|
|
282
|
-
{ newSlug },
|
|
283
|
-
)
|
|
284
|
-
if (!result.ok) {
|
|
285
|
-
const errorMsg = 'error' in result ? result.error : `Rename failed: ${result.status}`
|
|
286
|
-
throw new Error(errorMsg)
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// Update the selected path if the renamed entry is currently selected
|
|
290
|
-
if (selectedPath === path && result.data) {
|
|
291
|
-
setSelectedPath(result.data.newPath)
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Refresh entries to get updated paths
|
|
295
|
-
await refreshEntries()
|
|
296
|
-
notifications.show({
|
|
297
|
-
message: 'Entry renamed successfully',
|
|
298
|
-
color: 'green',
|
|
299
|
-
})
|
|
300
|
-
} catch (err) {
|
|
301
|
-
console.error(err)
|
|
302
|
-
const errorMessage = err instanceof Error ? err.message : 'Rename failed'
|
|
303
|
-
notifications.show({ message: errorMessage, color: 'red' })
|
|
304
|
-
throw err
|
|
305
|
-
} finally {
|
|
306
|
-
options.setBusy(false)
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Clear selection and refresh entries when branch changes (reactive pattern)
|
|
311
|
-
useEffect(() => {
|
|
312
|
-
if (options.branchName) {
|
|
313
|
-
// On initial mount, preserve the initial selection from URL
|
|
314
|
-
// On subsequent branch changes, clear selection
|
|
315
|
-
if (isInitialMount.current) {
|
|
316
|
-
isInitialMount.current = false
|
|
317
|
-
} else {
|
|
318
|
-
setSelectedPath('')
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Refresh entries for new branch
|
|
322
|
-
options.setBusy(true)
|
|
323
|
-
refreshEntries(options.branchName)
|
|
324
|
-
.catch(console.error)
|
|
325
|
-
.finally(() => options.setBusy(false))
|
|
326
|
-
}
|
|
327
|
-
}, [options.branchName])
|
|
328
|
-
|
|
329
|
-
// Validate selected entry when entries change
|
|
330
|
-
useEffect(() => {
|
|
331
|
-
// Skip validation if entries haven't loaded yet
|
|
332
|
-
if (entriesState.length === 0) return
|
|
333
|
-
|
|
334
|
-
// On first load with entries, sync from URL if we haven't already
|
|
335
|
-
if (!hasSyncedFromUrl.current) {
|
|
336
|
-
hasSyncedFromUrl.current = true
|
|
337
|
-
// If there's an entry in the URL that exists in entries, select it
|
|
338
|
-
if (initialUrlEntry.current && entriesState.find((e) => e.path === initialUrlEntry.current)) {
|
|
339
|
-
setSelectedPath(initialUrlEntry.current!)
|
|
340
|
-
return
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// If the selected entry exists, keep it
|
|
345
|
-
if (entriesState.find((e) => e.path === selectedPath)) return
|
|
346
|
-
|
|
347
|
-
// Fall back to first entry
|
|
348
|
-
setSelectedPath(entriesState[0]?.path ?? '')
|
|
349
|
-
}, [entriesState, selectedPath])
|
|
350
|
-
|
|
351
|
-
// Update URL when selection changes (skip until URL sync has happened)
|
|
352
|
-
useEffect(() => {
|
|
353
|
-
if (typeof window === 'undefined') return
|
|
354
|
-
// Don't update URL until we've synced from it first
|
|
355
|
-
if (!hasSyncedFromUrl.current) return
|
|
356
|
-
const url = new URL(window.location.href)
|
|
357
|
-
if (selectedPath) {
|
|
358
|
-
url.searchParams.set('entry', selectedPath)
|
|
359
|
-
} else {
|
|
360
|
-
url.searchParams.delete('entry')
|
|
361
|
-
}
|
|
362
|
-
window.history.replaceState({}, '', url.toString())
|
|
363
|
-
}, [selectedPath])
|
|
364
|
-
|
|
365
|
-
return {
|
|
366
|
-
selectedPath,
|
|
367
|
-
setSelectedPath,
|
|
368
|
-
entries: entriesState,
|
|
369
|
-
setEntries: setEntriesState,
|
|
370
|
-
collections: collectionsState,
|
|
371
|
-
currentEntry,
|
|
372
|
-
navigatorOpen,
|
|
373
|
-
setNavigatorOpen,
|
|
374
|
-
refreshEntries,
|
|
375
|
-
handleCreateEntry,
|
|
376
|
-
renameEntry,
|
|
377
|
-
loadEntry,
|
|
378
|
-
saveEntry,
|
|
379
|
-
collectionByPath: collectionByPath,
|
|
380
|
-
createModalOpen,
|
|
381
|
-
createModalCollection,
|
|
382
|
-
createModalError,
|
|
383
|
-
createModalCreating,
|
|
384
|
-
handleCreateModalSubmit,
|
|
385
|
-
closeCreateModal,
|
|
386
|
-
}
|
|
387
|
-
}
|
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
import { renderHook, waitFor } from '@testing-library/react'
|
|
2
|
-
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
3
|
-
import { useGroupManager } from './useGroupManager'
|
|
4
|
-
import type { MockApiClient } from '../../api/__test__/mock-client'
|
|
5
|
-
import { setupMockApiClient, setupMockConsole, createApiClientWrapper } from './__test__/test-utils'
|
|
6
|
-
|
|
7
|
-
// Mock the API client module
|
|
8
|
-
vi.mock('../../api', async () => {
|
|
9
|
-
const actual = await vi.importActual('../../api')
|
|
10
|
-
return {
|
|
11
|
-
...actual,
|
|
12
|
-
createApiClient: vi.fn(),
|
|
13
|
-
}
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
// Mock notifications
|
|
17
|
-
vi.mock('@mantine/notifications', () => ({
|
|
18
|
-
notifications: {
|
|
19
|
-
show: vi.fn(),
|
|
20
|
-
},
|
|
21
|
-
}))
|
|
22
|
-
|
|
23
|
-
describe('useGroupManager', () => {
|
|
24
|
-
let mockClient: MockApiClient
|
|
25
|
-
let wrapper: ReturnType<typeof createApiClientWrapper>
|
|
26
|
-
|
|
27
|
-
beforeEach(async () => {
|
|
28
|
-
mockClient = await setupMockApiClient()
|
|
29
|
-
wrapper = createApiClientWrapper(mockClient)
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
afterEach(() => {
|
|
33
|
-
vi.clearAllMocks()
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('initializes with empty groups', () => {
|
|
37
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: false }), {
|
|
38
|
-
wrapper,
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
expect(result.current.groupsData).toEqual([])
|
|
42
|
-
expect(result.current.groupsLoading).toBe(false)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
it('loads groups when isOpen becomes true', async () => {
|
|
46
|
-
const mockGroups = [
|
|
47
|
-
{ id: 'group1', name: 'Editors', members: [] },
|
|
48
|
-
{ id: 'group2', name: 'Reviewers', members: [] },
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
mockClient.groups.getInternal.mockResolvedValueOnce({
|
|
52
|
-
ok: true,
|
|
53
|
-
status: 200,
|
|
54
|
-
data: { groups: mockGroups },
|
|
55
|
-
})
|
|
56
|
-
|
|
57
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: true }), {
|
|
58
|
-
wrapper,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// Should start loading
|
|
62
|
-
expect(result.current.groupsLoading).toBe(true)
|
|
63
|
-
|
|
64
|
-
await waitFor(() => {
|
|
65
|
-
expect(result.current.groupsLoading).toBe(false)
|
|
66
|
-
})
|
|
67
|
-
|
|
68
|
-
expect(result.current.groupsData).toEqual(mockGroups)
|
|
69
|
-
expect(mockClient.groups.getInternal).toHaveBeenCalled()
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('handles load groups error', async () => {
|
|
73
|
-
const { restore } = setupMockConsole(['error'])
|
|
74
|
-
mockClient.groups.getInternal.mockResolvedValueOnce({
|
|
75
|
-
ok: false,
|
|
76
|
-
status: 500,
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: true }), {
|
|
80
|
-
wrapper,
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
await waitFor(() => {
|
|
84
|
-
expect(result.current.groupsLoading).toBe(false)
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
expect(result.current.groupsData).toEqual([])
|
|
88
|
-
restore()
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('saves groups successfully', async () => {
|
|
92
|
-
const mockGroups = [{ id: 'group1', name: 'Editors', members: [] }]
|
|
93
|
-
|
|
94
|
-
// Mock initial load
|
|
95
|
-
mockClient.groups.getInternal.mockResolvedValueOnce({
|
|
96
|
-
ok: true,
|
|
97
|
-
status: 200,
|
|
98
|
-
data: { groups: [] },
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: true }), {
|
|
102
|
-
wrapper,
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
await waitFor(() => {
|
|
106
|
-
expect(result.current.groupsLoading).toBe(false)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
// Mock save
|
|
110
|
-
mockClient.groups.updateInternal.mockResolvedValueOnce({
|
|
111
|
-
ok: true,
|
|
112
|
-
status: 200,
|
|
113
|
-
})
|
|
114
|
-
|
|
115
|
-
// Mock reload after save
|
|
116
|
-
mockClient.groups.getInternal.mockResolvedValueOnce({
|
|
117
|
-
ok: true,
|
|
118
|
-
status: 200,
|
|
119
|
-
data: { groups: mockGroups },
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
await result.current.handleSaveGroups(mockGroups)
|
|
123
|
-
|
|
124
|
-
await waitFor(() => {
|
|
125
|
-
expect(result.current.groupsData).toEqual(mockGroups)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
expect(mockClient.groups.updateInternal).toHaveBeenCalledWith({
|
|
129
|
-
groups: mockGroups,
|
|
130
|
-
})
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
it('handles save groups error', async () => {
|
|
134
|
-
mockClient.groups.updateInternal.mockResolvedValueOnce({
|
|
135
|
-
ok: false,
|
|
136
|
-
status: 500,
|
|
137
|
-
error: 'Save failed',
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: false }), {
|
|
141
|
-
wrapper,
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
await expect(result.current.handleSaveGroups([])).rejects.toThrow('Save failed')
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('searches users successfully', async () => {
|
|
148
|
-
const mockUsers = [
|
|
149
|
-
{ id: 'user1', name: 'John Doe', email: 'john@example.com' },
|
|
150
|
-
{ id: 'user2', name: 'Jane Smith', email: 'jane@example.com' },
|
|
151
|
-
]
|
|
152
|
-
|
|
153
|
-
mockClient.permissions.searchUsers.mockResolvedValueOnce({
|
|
154
|
-
ok: true,
|
|
155
|
-
status: 200,
|
|
156
|
-
data: { users: mockUsers },
|
|
157
|
-
})
|
|
158
|
-
|
|
159
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: false }), {
|
|
160
|
-
wrapper,
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
const users = await result.current.handleSearchUsers('john', 10)
|
|
164
|
-
|
|
165
|
-
expect(users).toEqual(mockUsers)
|
|
166
|
-
// This assertion will fail until we fix the implementation
|
|
167
|
-
expect(mockClient.permissions.searchUsers).toHaveBeenCalledWith({
|
|
168
|
-
q: 'john',
|
|
169
|
-
limit: '10',
|
|
170
|
-
})
|
|
171
|
-
})
|
|
172
|
-
|
|
173
|
-
it('searches users without limit parameter', async () => {
|
|
174
|
-
const mockUsers = [{ id: 'user1', name: 'John Doe', email: 'john@example.com' }]
|
|
175
|
-
|
|
176
|
-
mockClient.permissions.searchUsers.mockResolvedValueOnce({
|
|
177
|
-
ok: true,
|
|
178
|
-
status: 200,
|
|
179
|
-
data: { users: mockUsers },
|
|
180
|
-
})
|
|
181
|
-
|
|
182
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: false }), {
|
|
183
|
-
wrapper,
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
const users = await result.current.handleSearchUsers('john')
|
|
187
|
-
|
|
188
|
-
expect(users).toEqual(mockUsers)
|
|
189
|
-
// This assertion will fail until we fix the implementation
|
|
190
|
-
expect(mockClient.permissions.searchUsers).toHaveBeenCalledWith({
|
|
191
|
-
q: 'john',
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
it('handles user search error', async () => {
|
|
196
|
-
mockClient.permissions.searchUsers.mockResolvedValueOnce({
|
|
197
|
-
ok: false,
|
|
198
|
-
status: 500,
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: false }), {
|
|
202
|
-
wrapper,
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
const users = await result.current.handleSearchUsers('john')
|
|
206
|
-
|
|
207
|
-
expect(users).toEqual([])
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
it('searches external groups successfully', async () => {
|
|
211
|
-
const mockExternalGroups = [
|
|
212
|
-
{ id: 'ext1', name: 'External Group 1' },
|
|
213
|
-
{ id: 'ext2', name: 'External Group 2' },
|
|
214
|
-
]
|
|
215
|
-
|
|
216
|
-
mockClient.groups.searchExternal.mockResolvedValueOnce({
|
|
217
|
-
ok: true,
|
|
218
|
-
status: 200,
|
|
219
|
-
data: { groups: mockExternalGroups },
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: false }), {
|
|
223
|
-
wrapper,
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
const groups = await result.current.handleSearchExternalGroups('external')
|
|
227
|
-
|
|
228
|
-
expect(groups).toEqual(mockExternalGroups)
|
|
229
|
-
expect(mockClient.groups.searchExternal).toHaveBeenCalledWith({
|
|
230
|
-
q: 'external',
|
|
231
|
-
})
|
|
232
|
-
})
|
|
233
|
-
|
|
234
|
-
it('handles external group search error', async () => {
|
|
235
|
-
mockClient.groups.searchExternal.mockResolvedValueOnce({
|
|
236
|
-
ok: false,
|
|
237
|
-
status: 500,
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: false }), {
|
|
241
|
-
wrapper,
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
const groups = await result.current.handleSearchExternalGroups('external')
|
|
245
|
-
|
|
246
|
-
expect(groups).toEqual([])
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
it('does not load groups when isOpen is false', async () => {
|
|
250
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: false }), {
|
|
251
|
-
wrapper,
|
|
252
|
-
})
|
|
253
|
-
|
|
254
|
-
expect(result.current.groupsLoading).toBe(false)
|
|
255
|
-
expect(mockClient.groups.getInternal).not.toHaveBeenCalled()
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
it('can manually reload groups', async () => {
|
|
259
|
-
const mockGroups = [{ id: 'group1', name: 'Editors', members: [] }]
|
|
260
|
-
|
|
261
|
-
mockClient.groups.getInternal.mockResolvedValueOnce({
|
|
262
|
-
ok: true,
|
|
263
|
-
status: 200,
|
|
264
|
-
data: { groups: mockGroups },
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
const { result } = renderHook(() => useGroupManager({ isOpen: false }), {
|
|
268
|
-
wrapper,
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
await result.current.loadGroups()
|
|
272
|
-
|
|
273
|
-
await waitFor(() => {
|
|
274
|
-
expect(result.current.groupsData).toEqual(mockGroups)
|
|
275
|
-
})
|
|
276
|
-
})
|
|
277
|
-
})
|