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,864 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
|
2
|
-
import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react'
|
|
3
|
-
import { MantineProvider } from '@mantine/core'
|
|
4
|
-
import { PermissionManager } from './PermissionManager'
|
|
5
|
-
import type { PathPermission } from '../config'
|
|
6
|
-
import type { UserSearchResult, GroupMetadata } from '../auth/types'
|
|
7
|
-
import type { EditorCollection } from './Editor'
|
|
8
|
-
import { unsafeAsPermissionPath } from '../authorization/test-utils'
|
|
9
|
-
import { unsafeAsLogicalPath } from '../paths/test-utils'
|
|
10
|
-
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
cleanup()
|
|
13
|
-
vi.restoreAllMocks()
|
|
14
|
-
})
|
|
15
|
-
|
|
16
|
-
// Wrapper for Mantine components
|
|
17
|
-
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
|
18
|
-
<MantineProvider>{children}</MantineProvider>
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
describe('PermissionManager', () => {
|
|
22
|
-
const mockCollections: EditorCollection[] = [
|
|
23
|
-
{
|
|
24
|
-
path: unsafeAsLogicalPath('content/posts'),
|
|
25
|
-
name: 'posts',
|
|
26
|
-
label: 'Posts',
|
|
27
|
-
format: 'mdx',
|
|
28
|
-
type: 'collection',
|
|
29
|
-
entryTypes: [
|
|
30
|
-
{
|
|
31
|
-
name: 'entry',
|
|
32
|
-
label: 'Entry',
|
|
33
|
-
format: 'mdx',
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
path: unsafeAsLogicalPath('content/pages'),
|
|
39
|
-
name: 'pages',
|
|
40
|
-
label: 'Pages',
|
|
41
|
-
format: 'mdx',
|
|
42
|
-
type: 'collection',
|
|
43
|
-
entryTypes: [
|
|
44
|
-
{
|
|
45
|
-
name: 'entry',
|
|
46
|
-
label: 'Entry',
|
|
47
|
-
format: 'mdx',
|
|
48
|
-
},
|
|
49
|
-
],
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
path: unsafeAsLogicalPath('content/about'),
|
|
53
|
-
name: 'about',
|
|
54
|
-
label: 'About',
|
|
55
|
-
format: 'mdx',
|
|
56
|
-
type: 'collection',
|
|
57
|
-
entryTypes: [
|
|
58
|
-
{
|
|
59
|
-
name: 'entry',
|
|
60
|
-
label: 'Entry',
|
|
61
|
-
format: 'mdx',
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
},
|
|
65
|
-
]
|
|
66
|
-
|
|
67
|
-
const mockPermissions: PathPermission[] = [
|
|
68
|
-
{
|
|
69
|
-
path: unsafeAsPermissionPath('content/posts/**'),
|
|
70
|
-
read: { allowedGroups: ['editors'] },
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
path: unsafeAsPermissionPath('content/about/**'),
|
|
74
|
-
read: { allowedUsers: ['alice'] },
|
|
75
|
-
},
|
|
76
|
-
]
|
|
77
|
-
|
|
78
|
-
const mockGroups: GroupMetadata[] = [
|
|
79
|
-
{ id: 'editors', name: 'Editors', memberCount: 12 },
|
|
80
|
-
{ id: 'marketing', name: 'Marketing', memberCount: 8 },
|
|
81
|
-
]
|
|
82
|
-
|
|
83
|
-
const mockUsers: UserSearchResult[] = [
|
|
84
|
-
{ id: 'alice', name: 'Alice Johnson', email: 'alice@example.com' },
|
|
85
|
-
{ id: 'bob', name: 'Bob Smith', email: 'bob@example.com' },
|
|
86
|
-
]
|
|
87
|
-
|
|
88
|
-
let mockOnSave: (permissions: PathPermission[]) => Promise<void>
|
|
89
|
-
let mockOnSearchUsers: (query: string, limit?: number) => Promise<UserSearchResult[]>
|
|
90
|
-
let mockOnListGroups: () => Promise<GroupMetadata[]>
|
|
91
|
-
|
|
92
|
-
beforeEach(() => {
|
|
93
|
-
mockOnSave = vi.fn().mockResolvedValue(undefined)
|
|
94
|
-
mockOnSearchUsers = vi.fn().mockResolvedValue(mockUsers)
|
|
95
|
-
mockOnListGroups = vi.fn().mockResolvedValue(mockGroups)
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
describe('rendering', () => {
|
|
99
|
-
it('renders with permissions', () => {
|
|
100
|
-
render(
|
|
101
|
-
<PermissionManager
|
|
102
|
-
collections={mockCollections}
|
|
103
|
-
permissions={mockPermissions}
|
|
104
|
-
canEdit={true}
|
|
105
|
-
onSave={mockOnSave}
|
|
106
|
-
onSearchUsers={mockOnSearchUsers}
|
|
107
|
-
onListGroups={mockOnListGroups}
|
|
108
|
-
/>,
|
|
109
|
-
{ wrapper },
|
|
110
|
-
)
|
|
111
|
-
|
|
112
|
-
expect(screen.getByText('content')).toBeTruthy()
|
|
113
|
-
expect(screen.getByText('Expand All')).toBeTruthy()
|
|
114
|
-
expect(screen.getByText('Collapse All')).toBeTruthy()
|
|
115
|
-
})
|
|
116
|
-
|
|
117
|
-
it('renders loading state', () => {
|
|
118
|
-
render(
|
|
119
|
-
<PermissionManager
|
|
120
|
-
collections={mockCollections}
|
|
121
|
-
permissions={[]}
|
|
122
|
-
canEdit={true}
|
|
123
|
-
loading={true}
|
|
124
|
-
onSave={mockOnSave}
|
|
125
|
-
onSearchUsers={mockOnSearchUsers}
|
|
126
|
-
onListGroups={mockOnListGroups}
|
|
127
|
-
/>,
|
|
128
|
-
{ wrapper },
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
expect(screen.getByText('Loading permissions...')).toBeTruthy()
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('renders read-only warning for non-admin users', () => {
|
|
135
|
-
render(
|
|
136
|
-
<PermissionManager
|
|
137
|
-
collections={mockCollections}
|
|
138
|
-
permissions={mockPermissions}
|
|
139
|
-
canEdit={false}
|
|
140
|
-
onSearchUsers={mockOnSearchUsers}
|
|
141
|
-
onListGroups={mockOnListGroups}
|
|
142
|
-
/>,
|
|
143
|
-
{ wrapper },
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
expect(screen.getByText('Read-only')).toBeTruthy()
|
|
147
|
-
expect(screen.getByText(/You need admin access to edit permissions/i)).toBeTruthy()
|
|
148
|
-
})
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
describe('tree navigation', () => {
|
|
152
|
-
it('shows content node by default', () => {
|
|
153
|
-
render(
|
|
154
|
-
<PermissionManager
|
|
155
|
-
collections={mockCollections}
|
|
156
|
-
permissions={mockPermissions}
|
|
157
|
-
canEdit={true}
|
|
158
|
-
onSave={mockOnSave}
|
|
159
|
-
onSearchUsers={mockOnSearchUsers}
|
|
160
|
-
onListGroups={mockOnListGroups}
|
|
161
|
-
/>,
|
|
162
|
-
{ wrapper },
|
|
163
|
-
)
|
|
164
|
-
|
|
165
|
-
expect(screen.getByText('content')).toBeTruthy()
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
it('expands and shows child nodes when content is expanded', async () => {
|
|
169
|
-
render(
|
|
170
|
-
<PermissionManager
|
|
171
|
-
collections={mockCollections}
|
|
172
|
-
permissions={mockPermissions}
|
|
173
|
-
canEdit={true}
|
|
174
|
-
onSave={mockOnSave}
|
|
175
|
-
onSearchUsers={mockOnSearchUsers}
|
|
176
|
-
onListGroups={mockOnListGroups}
|
|
177
|
-
/>,
|
|
178
|
-
{ wrapper },
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
// Content is expanded by default, wait for children to appear after Collapse animation
|
|
182
|
-
await waitFor(() => {
|
|
183
|
-
expect(screen.getByText('Posts')).toBeTruthy()
|
|
184
|
-
expect(screen.getByText('Pages')).toBeTruthy()
|
|
185
|
-
expect(screen.getByText('About')).toBeTruthy()
|
|
186
|
-
})
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
it('expands all nodes when Expand All clicked', async () => {
|
|
190
|
-
render(
|
|
191
|
-
<PermissionManager
|
|
192
|
-
collections={mockCollections}
|
|
193
|
-
permissions={mockPermissions}
|
|
194
|
-
canEdit={true}
|
|
195
|
-
onSave={mockOnSave}
|
|
196
|
-
onSearchUsers={mockOnSearchUsers}
|
|
197
|
-
onListGroups={mockOnListGroups}
|
|
198
|
-
/>,
|
|
199
|
-
{ wrapper },
|
|
200
|
-
)
|
|
201
|
-
|
|
202
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
203
|
-
fireEvent.click(expandAllButton)
|
|
204
|
-
|
|
205
|
-
await waitFor(() => {
|
|
206
|
-
expect(screen.getByText('Posts')).toBeTruthy()
|
|
207
|
-
expect(screen.getByText('Pages')).toBeTruthy()
|
|
208
|
-
})
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('collapses all nodes when Collapse All clicked', async () => {
|
|
212
|
-
render(
|
|
213
|
-
<PermissionManager
|
|
214
|
-
collections={mockCollections}
|
|
215
|
-
permissions={mockPermissions}
|
|
216
|
-
canEdit={true}
|
|
217
|
-
onSave={mockOnSave}
|
|
218
|
-
onSearchUsers={mockOnSearchUsers}
|
|
219
|
-
onListGroups={mockOnListGroups}
|
|
220
|
-
/>,
|
|
221
|
-
{ wrapper },
|
|
222
|
-
)
|
|
223
|
-
|
|
224
|
-
// First expand all to ensure nodes are visible
|
|
225
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
226
|
-
fireEvent.click(expandAllButton)
|
|
227
|
-
|
|
228
|
-
await waitFor(() => {
|
|
229
|
-
expect(screen.getByText('Posts')).toBeTruthy()
|
|
230
|
-
})
|
|
231
|
-
|
|
232
|
-
// Then collapse all
|
|
233
|
-
const collapseAllButton = screen.getByText('Collapse All')
|
|
234
|
-
fireEvent.click(collapseAllButton)
|
|
235
|
-
|
|
236
|
-
// After collapsing, the Collapse component hides content but doesn't remove from DOM
|
|
237
|
-
// We can verify by checking that the content node still exists (it's the root)
|
|
238
|
-
await waitFor(() => {
|
|
239
|
-
expect(screen.getByText('content')).toBeTruthy()
|
|
240
|
-
})
|
|
241
|
-
})
|
|
242
|
-
|
|
243
|
-
it('preserves nested collection hierarchy in tree', async () => {
|
|
244
|
-
// Collections with nested structure
|
|
245
|
-
const nestedCollections: EditorCollection[] = [
|
|
246
|
-
{
|
|
247
|
-
path: unsafeAsLogicalPath('content/docs'),
|
|
248
|
-
name: 'docs',
|
|
249
|
-
label: 'Docs',
|
|
250
|
-
format: 'md',
|
|
251
|
-
type: 'collection',
|
|
252
|
-
entryTypes: [
|
|
253
|
-
{
|
|
254
|
-
name: 'entry',
|
|
255
|
-
label: 'Entry',
|
|
256
|
-
format: 'md',
|
|
257
|
-
},
|
|
258
|
-
],
|
|
259
|
-
children: [
|
|
260
|
-
{
|
|
261
|
-
path: unsafeAsLogicalPath('content/docs/api'),
|
|
262
|
-
name: 'api',
|
|
263
|
-
label: 'API',
|
|
264
|
-
format: 'md',
|
|
265
|
-
type: 'collection',
|
|
266
|
-
entryTypes: [
|
|
267
|
-
{
|
|
268
|
-
name: 'entry',
|
|
269
|
-
label: 'Entry',
|
|
270
|
-
format: 'md',
|
|
271
|
-
},
|
|
272
|
-
],
|
|
273
|
-
},
|
|
274
|
-
{
|
|
275
|
-
path: unsafeAsLogicalPath('content/docs/guides'),
|
|
276
|
-
name: 'guides',
|
|
277
|
-
label: 'Guides',
|
|
278
|
-
format: 'md',
|
|
279
|
-
type: 'collection',
|
|
280
|
-
entryTypes: [
|
|
281
|
-
{
|
|
282
|
-
name: 'entry',
|
|
283
|
-
label: 'Entry',
|
|
284
|
-
format: 'md',
|
|
285
|
-
},
|
|
286
|
-
],
|
|
287
|
-
},
|
|
288
|
-
],
|
|
289
|
-
},
|
|
290
|
-
]
|
|
291
|
-
|
|
292
|
-
render(
|
|
293
|
-
<PermissionManager
|
|
294
|
-
collections={nestedCollections}
|
|
295
|
-
permissions={[]}
|
|
296
|
-
canEdit={true}
|
|
297
|
-
onSave={mockOnSave}
|
|
298
|
-
onSearchUsers={mockOnSearchUsers}
|
|
299
|
-
onListGroups={mockOnListGroups}
|
|
300
|
-
/>,
|
|
301
|
-
{ wrapper },
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
// Expand the content root
|
|
305
|
-
const contentNode = screen.getByText('content')
|
|
306
|
-
fireEvent.click(contentNode)
|
|
307
|
-
|
|
308
|
-
await waitFor(() => {
|
|
309
|
-
expect(screen.getByText('Docs')).toBeTruthy()
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
// Expand the docs collection
|
|
313
|
-
const docsNode = screen.getByText('Docs')
|
|
314
|
-
fireEvent.click(docsNode)
|
|
315
|
-
|
|
316
|
-
// Wait for nested items to appear
|
|
317
|
-
await waitFor(
|
|
318
|
-
() => {
|
|
319
|
-
// CRITICAL: Nested items should appear under 'docs', not at root level
|
|
320
|
-
const apiNode = screen.queryByText('API')
|
|
321
|
-
const guidesNode = screen.queryByText('Guides')
|
|
322
|
-
|
|
323
|
-
// These items should exist when docs is expanded
|
|
324
|
-
expect(apiNode).not.toBeNull()
|
|
325
|
-
expect(guidesNode).not.toBeNull()
|
|
326
|
-
},
|
|
327
|
-
{ timeout: 3000 },
|
|
328
|
-
)
|
|
329
|
-
})
|
|
330
|
-
})
|
|
331
|
-
|
|
332
|
-
describe('node selection', () => {
|
|
333
|
-
it('shows permission editor when node is clicked', async () => {
|
|
334
|
-
render(
|
|
335
|
-
<PermissionManager
|
|
336
|
-
collections={mockCollections}
|
|
337
|
-
permissions={mockPermissions}
|
|
338
|
-
canEdit={true}
|
|
339
|
-
onSave={mockOnSave}
|
|
340
|
-
onSearchUsers={mockOnSearchUsers}
|
|
341
|
-
onListGroups={mockOnListGroups}
|
|
342
|
-
/>,
|
|
343
|
-
{ wrapper },
|
|
344
|
-
)
|
|
345
|
-
|
|
346
|
-
// Expand tree first to make nodes visible
|
|
347
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
348
|
-
fireEvent.click(expandAllButton)
|
|
349
|
-
|
|
350
|
-
await waitFor(() => screen.getByText('Posts'))
|
|
351
|
-
|
|
352
|
-
const postsNode = screen.getByText('Posts')
|
|
353
|
-
fireEvent.click(postsNode)
|
|
354
|
-
|
|
355
|
-
await waitFor(() => {
|
|
356
|
-
expect(screen.getByText(/Path: content\/posts\/\*\*/)).toBeTruthy()
|
|
357
|
-
const addGroupsButtons = screen.getAllByText('Add Groups')
|
|
358
|
-
expect(addGroupsButtons.length).toBeGreaterThan(0)
|
|
359
|
-
const addUserButtons = screen.getAllByText('Add User')
|
|
360
|
-
expect(addUserButtons.length).toBeGreaterThan(0)
|
|
361
|
-
})
|
|
362
|
-
})
|
|
363
|
-
})
|
|
364
|
-
|
|
365
|
-
describe('permission badges in tree', () => {
|
|
366
|
-
it('shows group badges on nodes with permissions', async () => {
|
|
367
|
-
render(
|
|
368
|
-
<PermissionManager
|
|
369
|
-
collections={mockCollections}
|
|
370
|
-
permissions={mockPermissions}
|
|
371
|
-
canEdit={true}
|
|
372
|
-
onSave={mockOnSave}
|
|
373
|
-
onSearchUsers={mockOnSearchUsers}
|
|
374
|
-
onListGroups={mockOnListGroups}
|
|
375
|
-
/>,
|
|
376
|
-
{ wrapper },
|
|
377
|
-
)
|
|
378
|
-
|
|
379
|
-
// Content is expanded by default - posts should be visible
|
|
380
|
-
expect(screen.getByText('Posts')).toBeTruthy()
|
|
381
|
-
|
|
382
|
-
// Click posts node to see its permissions (posts has group 'editors' assigned)
|
|
383
|
-
fireEvent.click(screen.getByText('Posts'))
|
|
384
|
-
|
|
385
|
-
// Wait for group badges to appear in the permission editor
|
|
386
|
-
await waitFor(() => {
|
|
387
|
-
const badges = screen.getAllByText('Editors')
|
|
388
|
-
expect(badges.length).toBeGreaterThan(0)
|
|
389
|
-
})
|
|
390
|
-
})
|
|
391
|
-
|
|
392
|
-
it('shows user badges on nodes with user permissions', async () => {
|
|
393
|
-
render(
|
|
394
|
-
<PermissionManager
|
|
395
|
-
collections={mockCollections}
|
|
396
|
-
permissions={mockPermissions}
|
|
397
|
-
canEdit={true}
|
|
398
|
-
onSave={mockOnSave}
|
|
399
|
-
onSearchUsers={mockOnSearchUsers}
|
|
400
|
-
onListGroups={mockOnListGroups}
|
|
401
|
-
/>,
|
|
402
|
-
{ wrapper },
|
|
403
|
-
)
|
|
404
|
-
|
|
405
|
-
// Content is expanded by default - about should be visible
|
|
406
|
-
expect(screen.getByText('About')).toBeTruthy()
|
|
407
|
-
|
|
408
|
-
// Click about node to see its permissions (about has user 'alice' assigned)
|
|
409
|
-
fireEvent.click(screen.getByText('About'))
|
|
410
|
-
|
|
411
|
-
// Wait for user badges to appear in the permission editor
|
|
412
|
-
await waitFor(() => {
|
|
413
|
-
const badges = screen.getAllByText('alice')
|
|
414
|
-
expect(badges.length).toBeGreaterThan(0)
|
|
415
|
-
})
|
|
416
|
-
})
|
|
417
|
-
})
|
|
418
|
-
|
|
419
|
-
describe('loading groups', () => {
|
|
420
|
-
it('loads groups on mount when canEdit is true', async () => {
|
|
421
|
-
render(
|
|
422
|
-
<PermissionManager
|
|
423
|
-
collections={mockCollections}
|
|
424
|
-
permissions={mockPermissions}
|
|
425
|
-
canEdit={true}
|
|
426
|
-
onSave={mockOnSave}
|
|
427
|
-
onSearchUsers={mockOnSearchUsers}
|
|
428
|
-
onListGroups={mockOnListGroups}
|
|
429
|
-
/>,
|
|
430
|
-
{ wrapper },
|
|
431
|
-
)
|
|
432
|
-
|
|
433
|
-
await waitFor(() => {
|
|
434
|
-
expect(mockOnListGroups).toHaveBeenCalled()
|
|
435
|
-
})
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
it('does not load groups when canEdit is false', () => {
|
|
439
|
-
render(
|
|
440
|
-
<PermissionManager
|
|
441
|
-
collections={mockCollections}
|
|
442
|
-
permissions={mockPermissions}
|
|
443
|
-
canEdit={false}
|
|
444
|
-
onSearchUsers={mockOnSearchUsers}
|
|
445
|
-
onListGroups={mockOnListGroups}
|
|
446
|
-
/>,
|
|
447
|
-
{ wrapper },
|
|
448
|
-
)
|
|
449
|
-
|
|
450
|
-
expect(mockOnListGroups).not.toHaveBeenCalled()
|
|
451
|
-
})
|
|
452
|
-
|
|
453
|
-
it('shows error when group loading fails', async () => {
|
|
454
|
-
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
455
|
-
const mockError = vi.fn().mockRejectedValue(new Error('Network error'))
|
|
456
|
-
|
|
457
|
-
render(
|
|
458
|
-
<PermissionManager
|
|
459
|
-
collections={mockCollections}
|
|
460
|
-
permissions={mockPermissions}
|
|
461
|
-
canEdit={true}
|
|
462
|
-
onSave={mockOnSave}
|
|
463
|
-
onSearchUsers={mockOnSearchUsers}
|
|
464
|
-
onListGroups={mockError}
|
|
465
|
-
/>,
|
|
466
|
-
{ wrapper },
|
|
467
|
-
)
|
|
468
|
-
|
|
469
|
-
await waitFor(() => {
|
|
470
|
-
expect(screen.getByText(/Failed to load groups/i)).toBeTruthy()
|
|
471
|
-
})
|
|
472
|
-
|
|
473
|
-
consoleErrorSpy.mockRestore()
|
|
474
|
-
})
|
|
475
|
-
})
|
|
476
|
-
|
|
477
|
-
describe('group search', () => {
|
|
478
|
-
it('shows group search panel when Add Groups clicked', async () => {
|
|
479
|
-
render(
|
|
480
|
-
<PermissionManager
|
|
481
|
-
collections={mockCollections}
|
|
482
|
-
permissions={mockPermissions}
|
|
483
|
-
canEdit={true}
|
|
484
|
-
onSave={mockOnSave}
|
|
485
|
-
onSearchUsers={mockOnSearchUsers}
|
|
486
|
-
onListGroups={mockOnListGroups}
|
|
487
|
-
/>,
|
|
488
|
-
{ wrapper },
|
|
489
|
-
)
|
|
490
|
-
|
|
491
|
-
// Expand tree first
|
|
492
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
493
|
-
fireEvent.click(expandAllButton)
|
|
494
|
-
|
|
495
|
-
await waitFor(() => screen.getByText('Posts'))
|
|
496
|
-
|
|
497
|
-
// Click posts node to select it
|
|
498
|
-
const postsNode = screen.getByText('Posts')
|
|
499
|
-
fireEvent.click(postsNode)
|
|
500
|
-
|
|
501
|
-
let addGroupsButton: HTMLElement
|
|
502
|
-
await waitFor(() => {
|
|
503
|
-
const buttons = screen.getAllByText('Add Groups')
|
|
504
|
-
expect(buttons.length).toBeGreaterThan(0)
|
|
505
|
-
addGroupsButton = buttons[0]
|
|
506
|
-
})
|
|
507
|
-
|
|
508
|
-
// Click Add Groups
|
|
509
|
-
fireEvent.click(addGroupsButton!)
|
|
510
|
-
|
|
511
|
-
await waitFor(() => {
|
|
512
|
-
expect(screen.getByPlaceholderText('Search groups...')).toBeTruthy()
|
|
513
|
-
})
|
|
514
|
-
})
|
|
515
|
-
|
|
516
|
-
it('filters groups as user types', async () => {
|
|
517
|
-
render(
|
|
518
|
-
<PermissionManager
|
|
519
|
-
collections={mockCollections}
|
|
520
|
-
permissions={mockPermissions}
|
|
521
|
-
canEdit={true}
|
|
522
|
-
onSave={mockOnSave}
|
|
523
|
-
onSearchUsers={mockOnSearchUsers}
|
|
524
|
-
onListGroups={mockOnListGroups}
|
|
525
|
-
/>,
|
|
526
|
-
{ wrapper },
|
|
527
|
-
)
|
|
528
|
-
|
|
529
|
-
// Expand tree first
|
|
530
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
531
|
-
fireEvent.click(expandAllButton)
|
|
532
|
-
|
|
533
|
-
await waitFor(() => screen.getByText('Posts'))
|
|
534
|
-
|
|
535
|
-
// Open group search
|
|
536
|
-
fireEvent.click(screen.getByText('Posts'))
|
|
537
|
-
|
|
538
|
-
let addGroupsButton: HTMLElement
|
|
539
|
-
await waitFor(() => {
|
|
540
|
-
const buttons = screen.getAllByText('Add Groups')
|
|
541
|
-
expect(buttons.length).toBeGreaterThan(0)
|
|
542
|
-
addGroupsButton = buttons[0]
|
|
543
|
-
})
|
|
544
|
-
fireEvent.click(addGroupsButton!)
|
|
545
|
-
|
|
546
|
-
await waitFor(() => {
|
|
547
|
-
expect(screen.getByPlaceholderText('Search groups...')).toBeTruthy()
|
|
548
|
-
})
|
|
549
|
-
|
|
550
|
-
const searchInput = screen.getByPlaceholderText('Search groups...')
|
|
551
|
-
fireEvent.change(searchInput, { target: { value: 'edit' } })
|
|
552
|
-
|
|
553
|
-
await waitFor(() => {
|
|
554
|
-
const editorMatches = screen.getAllByText('Editors')
|
|
555
|
-
expect(editorMatches.length).toBeGreaterThan(0)
|
|
556
|
-
expect(screen.queryByText('Marketing')).toBeFalsy()
|
|
557
|
-
})
|
|
558
|
-
})
|
|
559
|
-
|
|
560
|
-
it('closes search panel when Cancel clicked', async () => {
|
|
561
|
-
render(
|
|
562
|
-
<PermissionManager
|
|
563
|
-
collections={mockCollections}
|
|
564
|
-
permissions={mockPermissions}
|
|
565
|
-
canEdit={true}
|
|
566
|
-
onSave={mockOnSave}
|
|
567
|
-
onSearchUsers={mockOnSearchUsers}
|
|
568
|
-
onListGroups={mockOnListGroups}
|
|
569
|
-
/>,
|
|
570
|
-
{ wrapper },
|
|
571
|
-
)
|
|
572
|
-
|
|
573
|
-
// Expand tree first
|
|
574
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
575
|
-
fireEvent.click(expandAllButton)
|
|
576
|
-
|
|
577
|
-
await waitFor(() => screen.getByText('Posts'))
|
|
578
|
-
|
|
579
|
-
fireEvent.click(screen.getByText('Posts'))
|
|
580
|
-
|
|
581
|
-
let addGroupsButton: HTMLElement
|
|
582
|
-
await waitFor(() => {
|
|
583
|
-
const buttons = screen.getAllByText('Add Groups')
|
|
584
|
-
expect(buttons.length).toBeGreaterThan(0)
|
|
585
|
-
addGroupsButton = buttons[0]
|
|
586
|
-
})
|
|
587
|
-
|
|
588
|
-
fireEvent.click(addGroupsButton!)
|
|
589
|
-
await waitFor(() => screen.getByPlaceholderText('Search groups...'))
|
|
590
|
-
|
|
591
|
-
// Click Cancel - the button text changes from "Add Groups" to "Cancel"
|
|
592
|
-
const cancelButtons = screen.getAllByText('Cancel')
|
|
593
|
-
fireEvent.click(cancelButtons[0])
|
|
594
|
-
|
|
595
|
-
await waitFor(() => {
|
|
596
|
-
expect(screen.queryByPlaceholderText('Search groups...')).toBeFalsy()
|
|
597
|
-
})
|
|
598
|
-
})
|
|
599
|
-
})
|
|
600
|
-
|
|
601
|
-
describe('user search', () => {
|
|
602
|
-
it('shows user search panel when Add User clicked', async () => {
|
|
603
|
-
render(
|
|
604
|
-
<PermissionManager
|
|
605
|
-
collections={mockCollections}
|
|
606
|
-
permissions={mockPermissions}
|
|
607
|
-
canEdit={true}
|
|
608
|
-
onSave={mockOnSave}
|
|
609
|
-
onSearchUsers={mockOnSearchUsers}
|
|
610
|
-
onListGroups={mockOnListGroups}
|
|
611
|
-
/>,
|
|
612
|
-
{ wrapper },
|
|
613
|
-
)
|
|
614
|
-
|
|
615
|
-
// Expand tree first
|
|
616
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
617
|
-
fireEvent.click(expandAllButton)
|
|
618
|
-
|
|
619
|
-
await waitFor(() => screen.getByText('Posts'))
|
|
620
|
-
|
|
621
|
-
fireEvent.click(screen.getByText('Posts'))
|
|
622
|
-
|
|
623
|
-
let addUserButton: HTMLElement
|
|
624
|
-
await waitFor(() => {
|
|
625
|
-
const buttons = screen.getAllByText('Add User')
|
|
626
|
-
expect(buttons.length).toBeGreaterThan(0)
|
|
627
|
-
addUserButton = buttons[0]
|
|
628
|
-
})
|
|
629
|
-
|
|
630
|
-
fireEvent.click(addUserButton!)
|
|
631
|
-
|
|
632
|
-
await waitFor(() => {
|
|
633
|
-
expect(screen.getByPlaceholderText(/Search users by name or email/i)).toBeTruthy()
|
|
634
|
-
})
|
|
635
|
-
})
|
|
636
|
-
|
|
637
|
-
it('debounces search input', async () => {
|
|
638
|
-
vi.useFakeTimers()
|
|
639
|
-
|
|
640
|
-
try {
|
|
641
|
-
render(
|
|
642
|
-
<PermissionManager
|
|
643
|
-
collections={mockCollections}
|
|
644
|
-
permissions={mockPermissions}
|
|
645
|
-
canEdit={true}
|
|
646
|
-
onSave={mockOnSave}
|
|
647
|
-
onSearchUsers={mockOnSearchUsers}
|
|
648
|
-
onListGroups={mockOnListGroups}
|
|
649
|
-
/>,
|
|
650
|
-
{ wrapper },
|
|
651
|
-
)
|
|
652
|
-
|
|
653
|
-
// Expand tree first
|
|
654
|
-
vi.useRealTimers()
|
|
655
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
656
|
-
fireEvent.click(expandAllButton)
|
|
657
|
-
|
|
658
|
-
await waitFor(() => screen.getByText('Posts'))
|
|
659
|
-
vi.useFakeTimers()
|
|
660
|
-
|
|
661
|
-
// Open user search
|
|
662
|
-
fireEvent.click(screen.getByText('Posts'))
|
|
663
|
-
|
|
664
|
-
vi.useRealTimers()
|
|
665
|
-
let addUserButton: HTMLElement
|
|
666
|
-
await waitFor(() => {
|
|
667
|
-
const buttons = screen.getAllByText('Add User')
|
|
668
|
-
expect(buttons.length).toBeGreaterThan(0)
|
|
669
|
-
addUserButton = buttons[0]
|
|
670
|
-
})
|
|
671
|
-
vi.useFakeTimers()
|
|
672
|
-
|
|
673
|
-
fireEvent.click(addUserButton!)
|
|
674
|
-
|
|
675
|
-
vi.useRealTimers()
|
|
676
|
-
await waitFor(() => screen.getByPlaceholderText(/Search users/i))
|
|
677
|
-
vi.useFakeTimers()
|
|
678
|
-
|
|
679
|
-
const searchInput = screen.getByPlaceholderText(/Search users/i)
|
|
680
|
-
fireEvent.change(searchInput, { target: { value: 'a' } })
|
|
681
|
-
fireEvent.change(searchInput, { target: { value: 'al' } })
|
|
682
|
-
|
|
683
|
-
expect(mockOnSearchUsers).not.toHaveBeenCalled()
|
|
684
|
-
|
|
685
|
-
await vi.advanceTimersByTimeAsync(300)
|
|
686
|
-
|
|
687
|
-
expect(mockOnSearchUsers).toHaveBeenCalledTimes(1)
|
|
688
|
-
expect(mockOnSearchUsers).toHaveBeenCalledWith('al', 10)
|
|
689
|
-
} finally {
|
|
690
|
-
vi.useRealTimers()
|
|
691
|
-
}
|
|
692
|
-
})
|
|
693
|
-
|
|
694
|
-
it('shows error when user search fails', async () => {
|
|
695
|
-
const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
|
|
696
|
-
const mockError = vi.fn().mockRejectedValue(new Error('Search failed'))
|
|
697
|
-
|
|
698
|
-
render(
|
|
699
|
-
<PermissionManager
|
|
700
|
-
collections={mockCollections}
|
|
701
|
-
permissions={mockPermissions}
|
|
702
|
-
canEdit={true}
|
|
703
|
-
onSave={mockOnSave}
|
|
704
|
-
onSearchUsers={mockError}
|
|
705
|
-
onListGroups={mockOnListGroups}
|
|
706
|
-
/>,
|
|
707
|
-
{ wrapper },
|
|
708
|
-
)
|
|
709
|
-
|
|
710
|
-
// Expand tree first
|
|
711
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
712
|
-
fireEvent.click(expandAllButton)
|
|
713
|
-
|
|
714
|
-
await waitFor(() => screen.getByText('Posts'))
|
|
715
|
-
|
|
716
|
-
fireEvent.click(screen.getByText('Posts'))
|
|
717
|
-
|
|
718
|
-
let addUserButton: HTMLElement
|
|
719
|
-
await waitFor(() => {
|
|
720
|
-
const buttons = screen.getAllByText('Add User')
|
|
721
|
-
expect(buttons.length).toBeGreaterThan(0)
|
|
722
|
-
addUserButton = buttons[0]
|
|
723
|
-
})
|
|
724
|
-
fireEvent.click(addUserButton!)
|
|
725
|
-
|
|
726
|
-
await waitFor(() => screen.getByPlaceholderText(/Search users/i))
|
|
727
|
-
|
|
728
|
-
const searchInput = screen.getByPlaceholderText(/Search users/i)
|
|
729
|
-
fireEvent.change(searchInput, { target: { value: 'test' } })
|
|
730
|
-
|
|
731
|
-
await waitFor(
|
|
732
|
-
() => {
|
|
733
|
-
expect(screen.getByText(/Failed to search users/i)).toBeTruthy()
|
|
734
|
-
},
|
|
735
|
-
{ timeout: 2000 },
|
|
736
|
-
)
|
|
737
|
-
|
|
738
|
-
consoleErrorSpy.mockRestore()
|
|
739
|
-
})
|
|
740
|
-
})
|
|
741
|
-
|
|
742
|
-
describe('saving permissions', () => {
|
|
743
|
-
it('saves changes and calls onSave', async () => {
|
|
744
|
-
render(
|
|
745
|
-
<PermissionManager
|
|
746
|
-
collections={mockCollections}
|
|
747
|
-
permissions={mockPermissions}
|
|
748
|
-
canEdit={true}
|
|
749
|
-
onSave={mockOnSave}
|
|
750
|
-
onSearchUsers={mockOnSearchUsers}
|
|
751
|
-
onListGroups={mockOnListGroups}
|
|
752
|
-
/>,
|
|
753
|
-
{ wrapper },
|
|
754
|
-
)
|
|
755
|
-
|
|
756
|
-
// Expand tree first
|
|
757
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
758
|
-
fireEvent.click(expandAllButton)
|
|
759
|
-
|
|
760
|
-
await waitFor(() => screen.getByText('Posts'))
|
|
761
|
-
|
|
762
|
-
// Open posts node and add a user
|
|
763
|
-
fireEvent.click(screen.getByText('Posts'))
|
|
764
|
-
|
|
765
|
-
let addUserButton: HTMLElement
|
|
766
|
-
await waitFor(() => {
|
|
767
|
-
const buttons = screen.getAllByText('Add User')
|
|
768
|
-
expect(buttons.length).toBeGreaterThan(0)
|
|
769
|
-
addUserButton = buttons[0]
|
|
770
|
-
})
|
|
771
|
-
fireEvent.click(addUserButton!)
|
|
772
|
-
|
|
773
|
-
await waitFor(() => screen.getByPlaceholderText(/Search users/i))
|
|
774
|
-
|
|
775
|
-
const searchInput = screen.getByPlaceholderText(/Search users/i)
|
|
776
|
-
fireEvent.change(searchInput, { target: { value: 'alice' } })
|
|
777
|
-
|
|
778
|
-
// Wait for search results
|
|
779
|
-
let aliceElement: HTMLElement
|
|
780
|
-
await waitFor(
|
|
781
|
-
() => {
|
|
782
|
-
const aliceMatches = screen.getAllByText('Alice Johnson')
|
|
783
|
-
expect(aliceMatches.length).toBeGreaterThan(0)
|
|
784
|
-
aliceElement = aliceMatches[0]
|
|
785
|
-
},
|
|
786
|
-
{ timeout: 2000 },
|
|
787
|
-
)
|
|
788
|
-
|
|
789
|
-
// Click on the user to add them
|
|
790
|
-
fireEvent.click(aliceElement!)
|
|
791
|
-
|
|
792
|
-
// Should show save button
|
|
793
|
-
await waitFor(() => {
|
|
794
|
-
expect(screen.getByText('Save Permissions')).toBeTruthy()
|
|
795
|
-
})
|
|
796
|
-
|
|
797
|
-
// Click save
|
|
798
|
-
const saveButton = screen.getByText('Save Permissions')
|
|
799
|
-
fireEvent.click(saveButton)
|
|
800
|
-
|
|
801
|
-
await waitFor(() => {
|
|
802
|
-
expect(mockOnSave).toHaveBeenCalled()
|
|
803
|
-
})
|
|
804
|
-
})
|
|
805
|
-
|
|
806
|
-
it('handles save errors gracefully', async () => {
|
|
807
|
-
const mockError = vi.fn().mockRejectedValue(new Error('Network error'))
|
|
808
|
-
|
|
809
|
-
render(
|
|
810
|
-
<PermissionManager
|
|
811
|
-
collections={mockCollections}
|
|
812
|
-
permissions={mockPermissions}
|
|
813
|
-
canEdit={true}
|
|
814
|
-
onSave={mockError}
|
|
815
|
-
onSearchUsers={mockOnSearchUsers}
|
|
816
|
-
onListGroups={mockOnListGroups}
|
|
817
|
-
/>,
|
|
818
|
-
{ wrapper },
|
|
819
|
-
)
|
|
820
|
-
|
|
821
|
-
// Expand tree first
|
|
822
|
-
const expandAllButton = screen.getByText('Expand All')
|
|
823
|
-
fireEvent.click(expandAllButton)
|
|
824
|
-
|
|
825
|
-
await waitFor(() => screen.getByText('Posts'))
|
|
826
|
-
|
|
827
|
-
// Make a change by adding a user
|
|
828
|
-
fireEvent.click(screen.getByText('Posts'))
|
|
829
|
-
|
|
830
|
-
let addUserButton: HTMLElement
|
|
831
|
-
await waitFor(() => {
|
|
832
|
-
const buttons = screen.getAllByText('Add User')
|
|
833
|
-
expect(buttons.length).toBeGreaterThan(0)
|
|
834
|
-
addUserButton = buttons[0]
|
|
835
|
-
})
|
|
836
|
-
fireEvent.click(addUserButton!)
|
|
837
|
-
await waitFor(() => screen.getByPlaceholderText(/Search users/i))
|
|
838
|
-
|
|
839
|
-
const searchInput = screen.getByPlaceholderText(/Search users/i)
|
|
840
|
-
fireEvent.change(searchInput, { target: { value: 'bob' } })
|
|
841
|
-
|
|
842
|
-
let bobElement: HTMLElement
|
|
843
|
-
await waitFor(
|
|
844
|
-
() => {
|
|
845
|
-
const bobMatches = screen.getAllByText('Bob Smith')
|
|
846
|
-
expect(bobMatches.length).toBeGreaterThan(0)
|
|
847
|
-
bobElement = bobMatches[0]
|
|
848
|
-
},
|
|
849
|
-
{ timeout: 2000 },
|
|
850
|
-
)
|
|
851
|
-
fireEvent.click(bobElement!)
|
|
852
|
-
|
|
853
|
-
await waitFor(() => screen.getByText('Save Permissions'))
|
|
854
|
-
fireEvent.click(screen.getByText('Save Permissions'))
|
|
855
|
-
|
|
856
|
-
await waitFor(() => {
|
|
857
|
-
expect(screen.getByText('Network error')).toBeTruthy()
|
|
858
|
-
})
|
|
859
|
-
})
|
|
860
|
-
})
|
|
861
|
-
|
|
862
|
-
// Note: onClose prop is accepted but not used to render a close button
|
|
863
|
-
// The close button is provided by parent component (Drawer/Modal)
|
|
864
|
-
})
|