@sanity/sdk 3.0.0-rc.0 → 3.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/_exports/_internal.d.ts +163 -0
- package/dist/_exports/_internal.d.ts.map +1 -0
- package/dist/_exports/_internal.js +332 -0
- package/dist/_exports/_internal.js.map +1 -0
- package/dist/_exports/agent.d.ts +65 -0
- package/dist/_exports/agent.d.ts.map +1 -0
- package/dist/_exports/agent.js +71 -0
- package/dist/_exports/agent.js.map +1 -0
- package/dist/_exports/comlink.d.ts +118 -0
- package/dist/_exports/comlink.d.ts.map +1 -0
- package/dist/_exports/comlink.js +69 -0
- package/dist/_exports/comlink.js.map +1 -0
- package/dist/authStore-CtE9bLHW.js +754 -0
- package/dist/authStore-CtE9bLHW.js.map +1 -0
- package/dist/authStore-DKFX8fn7.d.ts +3179 -0
- package/dist/authStore-DKFX8fn7.d.ts.map +1 -0
- package/dist/clientStore-BtE2-q2i.js +90 -0
- package/dist/clientStore-BtE2-q2i.js.map +1 -0
- package/dist/createSanityInstance-DUI1bGIg.d.ts +278 -0
- package/dist/createSanityInstance-DUI1bGIg.d.ts.map +1 -0
- package/dist/createStateSourceAction-BOKtqFNm.js +300 -0
- package/dist/createStateSourceAction-BOKtqFNm.js.map +1 -0
- package/dist/getEnv-D5RGkQLu.js +8 -0
- package/dist/getEnv-D5RGkQLu.js.map +1 -0
- package/dist/getNodeState-CR3__B2D.js +67 -0
- package/dist/getNodeState-CR3__B2D.js.map +1 -0
- package/dist/index.d.ts +3 -2332
- package/dist/index.js +4171 -4332
- package/dist/index.js.map +1 -1
- package/dist/logger-DAtJb5RQ.js +173 -0
- package/dist/logger-DAtJb5RQ.js.map +1 -0
- package/dist/object-CxXlSVg7.js +83 -0
- package/dist/object-CxXlSVg7.js.map +1 -0
- package/dist/queryStore-DPf9sN3V.js +1346 -0
- package/dist/queryStore-DPf9sN3V.js.map +1 -0
- package/dist/rolldown-runtime-vyAXikos.js +9 -0
- package/dist/setCleanupTimeout-BiM6q1KF.js +31 -0
- package/dist/setCleanupTimeout-BiM6q1KF.js.map +1 -0
- package/dist/telemetryManager-Dq_qCkeI.js +127 -0
- package/dist/telemetryManager-Dq_qCkeI.js.map +1 -0
- package/dist/version-BChjsi4u.js +9 -0
- package/dist/version-BChjsi4u.js.map +1 -0
- package/package.json +47 -52
- package/src/_exports/_internal.ts +27 -0
- package/src/_exports/agent.ts +19 -0
- package/src/_exports/comlink.ts +16 -0
- package/src/_exports/index.ts +125 -64
- package/src/access/checkPermissions.test.ts +55 -0
- package/src/access/checkPermissions.ts +54 -0
- package/src/agent/agentActions.test.ts +16 -60
- package/src/agent/agentActions.ts +25 -38
- package/src/applications/applications.test-d.ts +88 -0
- package/src/applications/applications.test.ts +130 -0
- package/src/applications/applications.ts +367 -0
- package/src/auth/authLogger.ts +30 -0
- package/src/auth/authStore.test.ts +194 -13
- package/src/auth/authStore.ts +58 -26
- package/src/auth/getOrganizationVerificationState.test.ts +18 -17
- package/src/auth/getOrganizationVerificationState.ts +4 -4
- package/src/auth/handleAuthCallback.test.ts +35 -1
- package/src/auth/handleAuthCallback.ts +25 -6
- package/src/auth/logout.test.ts +75 -2
- package/src/auth/logout.ts +23 -4
- package/src/auth/refreshStampedToken.test.ts +219 -469
- package/src/auth/refreshStampedToken.ts +79 -195
- package/src/auth/studioAuth.ts +1 -1
- package/src/auth/studioModeAuth.ts +2 -1
- package/src/auth/subscribeToStateAndFetchCurrentUser.test.ts +146 -13
- package/src/auth/subscribeToStateAndFetchCurrentUser.ts +37 -6
- package/src/auth/subscribeToStorageEventsAndSetToken.test.ts +2 -0
- package/src/client/clientStore.test.ts +62 -37
- package/src/client/clientStore.ts +45 -75
- package/src/client/liveEvents.test.ts +151 -0
- package/src/client/liveEvents.ts +107 -0
- package/src/comlink/controller/actions/destroyController.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.test.ts +4 -1
- package/src/comlink/controller/actions/getOrCreateChannel.ts +2 -2
- package/src/comlink/controller/actions/getOrCreateController.test.ts +4 -1
- package/src/comlink/controller/actions/releaseChannel.test.ts +1 -1
- package/src/comlink/controller/comlinkControllerStore.test.ts +4 -1
- package/src/comlink/node/actions/getOrCreateNode.test.ts +9 -3
- package/src/comlink/node/actions/getOrCreateNode.ts +2 -2
- package/src/comlink/node/actions/releaseNode.test.ts +7 -4
- package/src/comlink/node/comlinkNodeStore.test.ts +2 -2
- package/src/comlink/node/getNodeState.test.ts +1 -1
- package/src/comlink/node/getNodeState.ts +2 -1
- package/src/comments/addonDatasetStore.test.ts +215 -0
- package/src/comments/addonDatasetStore.ts +249 -0
- package/src/comments/buildCommentThreads.test.ts +83 -0
- package/src/comments/buildCommentThreads.ts +54 -0
- package/src/comments/commentActions.test.ts +540 -0
- package/src/comments/commentActions.ts +555 -0
- package/src/comments/commentFieldPath.test.ts +40 -0
- package/src/comments/commentFieldPath.ts +44 -0
- package/src/comments/commentsConstants.ts +80 -0
- package/src/comments/commentsStore.test.ts +419 -0
- package/src/comments/commentsStore.ts +483 -0
- package/src/comments/normalizeComment.test.ts +116 -0
- package/src/comments/normalizeComment.ts +45 -0
- package/src/comments/observeComments.test.ts +193 -0
- package/src/comments/observeComments.ts +113 -0
- package/src/comments/reducers.test.ts +277 -0
- package/src/comments/reducers.ts +316 -0
- package/src/comments/types.ts +249 -0
- package/src/comments/weakenReferences.test.ts +47 -0
- package/src/comments/weakenReferences.ts +29 -0
- package/src/config/__tests__/handles.test.ts +18 -12
- package/src/config/handles.ts +25 -7
- package/src/config/sanityConfig.ts +40 -82
- package/src/datasets/datasets.test.ts +2 -2
- package/src/datasets/datasets.ts +10 -4
- package/src/document/actions.test.ts +115 -33
- package/src/document/actions.ts +212 -15
- package/src/document/applyDocumentActions.test.ts +35 -13
- package/src/document/applyDocumentActions.ts +30 -9
- package/src/document/documentConstants.ts +15 -0
- package/src/document/documentStore.concurrency.test.ts +1306 -0
- package/src/document/documentStore.test.ts +753 -273
- package/src/document/documentStore.ts +197 -75
- package/src/document/events.test.ts +57 -2
- package/src/document/events.ts +82 -26
- package/src/document/listen.test.ts +56 -0
- package/src/document/listen.ts +74 -21
- package/src/document/listenerEventOperators.test.ts +311 -0
- package/src/document/listenerEventOperators.ts +217 -0
- package/src/document/patchOperations.test.ts +76 -2
- package/src/document/patchOperations.ts +60 -1
- package/src/document/permissions.test.ts +107 -60
- package/src/document/permissions.ts +44 -19
- package/src/document/processActions/create.ts +138 -0
- package/src/document/processActions/delete.ts +100 -0
- package/src/document/processActions/discard.ts +63 -0
- package/src/document/processActions/edit.ts +154 -0
- package/src/document/processActions/processActions.ts +218 -0
- package/src/document/processActions/publish.ts +120 -0
- package/src/document/processActions/releaseArchive.ts +77 -0
- package/src/document/processActions/releaseCreate.ts +59 -0
- package/src/document/processActions/releaseDelete.ts +65 -0
- package/src/document/processActions/releaseEdit.ts +37 -0
- package/src/document/processActions/releasePublish.ts +45 -0
- package/src/document/processActions/releaseSchedule.ts +87 -0
- package/src/document/processActions/releaseUtil.ts +31 -0
- package/src/document/processActions/shared.ts +308 -0
- package/src/document/processActions/unpublish.ts +85 -0
- package/src/document/processActions.test.ts +1129 -121
- package/src/document/reducers.test.ts +772 -64
- package/src/document/reducers.ts +205 -51
- package/src/document/resourceRules.test.ts +178 -0
- package/src/document/resourceRules.ts +117 -0
- package/src/document/sharedListener.test.ts +16 -14
- package/src/document/sharedListener.ts +24 -8
- package/src/document/util.test.ts +46 -0
- package/src/document/util.ts +50 -0
- package/src/favorites/favorites.test.ts +5 -6
- package/src/installations/installations.test-d.ts +73 -0
- package/src/installations/installations.test.ts +62 -0
- package/src/installations/installations.ts +206 -0
- package/src/organization/organization.test-d.ts +36 -0
- package/src/organization/organization.test.ts +138 -0
- package/src/organization/organization.ts +138 -0
- package/src/organizations/organizations.test-d.ts +56 -0
- package/src/organizations/organizations.test.ts +150 -0
- package/src/organizations/organizations.ts +107 -0
- package/src/presence/bifurTransport.test.ts +201 -9
- package/src/presence/bifurTransport.ts +98 -14
- package/src/presence/paths.test.ts +70 -0
- package/src/presence/paths.ts +41 -0
- package/src/presence/presenceStore.test.ts +915 -24
- package/src/presence/presenceStore.ts +576 -104
- package/src/presence/types.ts +175 -3
- package/src/preview/previewProjectionUtils.test.ts +9 -22
- package/src/preview/previewProjectionUtils.ts +7 -3
- package/src/preview/types.ts +0 -15
- package/src/project/project.test-d.ts +37 -0
- package/src/project/project.test.ts +157 -12
- package/src/project/project.ts +138 -25
- package/src/projection/getProjectionState.test.ts +18 -20
- package/src/projection/getProjectionState.ts +23 -3
- package/src/projection/projectionQuery.ts +5 -5
- package/src/projection/projectionStore.test.ts +1 -9
- package/src/projection/resolveProjection.test.ts +1 -2
- package/src/projection/resolveProjection.ts +16 -0
- package/src/projection/subscribeToStateAndFetchBatches.test.ts +1 -1
- package/src/projection/subscribeToStateAndFetchBatches.ts +32 -23
- package/src/projects/projects.test-d.ts +32 -0
- package/src/projects/projects.test.ts +108 -42
- package/src/projects/projects.ts +63 -27
- package/src/query/queryStore.test.ts +259 -123
- package/src/query/queryStore.ts +85 -69
- package/src/query/reducers.test.ts +0 -42
- package/src/query/reducers.ts +2 -18
- package/src/releases/getPerspectiveState.test.ts +17 -23
- package/src/releases/getPerspectiveState.ts +8 -20
- package/src/releases/observeReleases.test.ts +127 -0
- package/src/releases/observeReleases.ts +102 -0
- package/src/releases/releasesStore.test.ts +100 -53
- package/src/releases/releasesStore.ts +69 -44
- package/src/releases/utils/sortReleases.test.ts +38 -2
- package/src/releases/utils/sortReleases.ts +15 -10
- package/src/store/createActionBinder.test.ts +109 -90
- package/src/store/createActionBinder.ts +36 -28
- package/src/store/createSanityInstance.test.ts +15 -13
- package/src/store/createSanityInstance.ts +14 -2
- package/src/store/createStateSourceAction.test.ts +64 -2
- package/src/store/createStateSourceAction.ts +38 -43
- package/src/store/createStoreInstance.test.ts +4 -2
- package/src/store/fetcherStore.test-d.ts +121 -0
- package/src/store/fetcherStore.test.ts +299 -0
- package/src/store/fetcherStore.ts +801 -0
- package/src/telemetry/environment.test.ts +119 -0
- package/src/telemetry/environment.ts +92 -0
- package/src/telemetry/events.ts +42 -0
- package/src/telemetry/initTelemetry.test.ts +432 -0
- package/src/telemetry/initTelemetry.ts +218 -0
- package/src/telemetry/telemetryManager.test.ts +327 -0
- package/src/telemetry/telemetryManager.ts +199 -0
- package/src/users/reducers.test.ts +3 -1
- package/src/users/reducers.ts +5 -6
- package/src/users/types.ts +4 -4
- package/src/users/usersStore.test.ts +16 -12
- package/src/users/usersStore.ts +5 -1
- package/src/utils/buildQuery.test.ts +14 -0
- package/src/utils/buildQuery.ts +17 -0
- package/src/utils/createFetcherStore.test.ts +7 -5
- package/src/utils/createFetcherStore.ts +8 -5
- package/src/utils/getStagingApiHost.test.ts +21 -0
- package/src/utils/getStagingApiHost.ts +14 -0
- package/src/utils/ids.test.ts +1 -29
- package/src/utils/ids.ts +0 -10
- package/src/utils/includeShape.ts +36 -0
- package/src/utils/isImportError.test.ts +72 -0
- package/src/utils/isImportError.ts +34 -0
- package/src/utils/logger.test.ts +12 -0
- package/src/utils/logger.ts +8 -3
- package/src/utils/object.test.ts +95 -0
- package/src/utils/object.ts +142 -0
- package/src/utils/setCleanupTimeout.ts +37 -0
- package/src/document/processActions.ts +0 -707
- package/src/utils/defineIntent.test.ts +0 -477
- package/src/utils/defineIntent.ts +0 -244
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {type Reference, type SanityDocument} from '@sanity/types'
|
|
1
|
+
import {type CreateMutation, type Reference, type SanityDocument} from '@sanity/types'
|
|
2
2
|
import {parse} from 'groq-js'
|
|
3
3
|
import {describe, expect, it} from 'vitest'
|
|
4
4
|
|
|
5
|
-
import {type DocumentAction} from './actions'
|
|
6
|
-
import {ActionError, processActions} from './processActions'
|
|
7
|
-
import {type DocumentSet} from './processMutations'
|
|
5
|
+
import {type Action, type DocumentAction} from './actions'
|
|
6
|
+
import {ActionError, processActions} from './processActions/processActions'
|
|
7
|
+
import {type DocumentSet, processMutations} from './processMutations'
|
|
8
8
|
|
|
9
9
|
// Helper: Create a sample document that conforms to SanityDocument.
|
|
10
10
|
const createDoc = (id: string, title: string, rev: string = 'initial'): SanityDocument => ({
|
|
@@ -50,7 +50,6 @@ describe('processActions', () => {
|
|
|
50
50
|
documentId: 'doc1',
|
|
51
51
|
type: 'document.create',
|
|
52
52
|
documentType: 'article',
|
|
53
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
54
53
|
},
|
|
55
54
|
]
|
|
56
55
|
const result = processActions({
|
|
@@ -97,7 +96,6 @@ describe('processActions', () => {
|
|
|
97
96
|
documentId: 'doc1',
|
|
98
97
|
type: 'document.create',
|
|
99
98
|
documentType: 'article',
|
|
100
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
101
99
|
},
|
|
102
100
|
]
|
|
103
101
|
expect(() =>
|
|
@@ -115,7 +113,6 @@ describe('processActions', () => {
|
|
|
115
113
|
documentId: 'doc1',
|
|
116
114
|
type: 'document.create',
|
|
117
115
|
documentType: 'article',
|
|
118
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
119
116
|
},
|
|
120
117
|
]
|
|
121
118
|
const result = processActions({
|
|
@@ -144,7 +141,6 @@ describe('processActions', () => {
|
|
|
144
141
|
documentId: 'doc1',
|
|
145
142
|
type: 'document.create',
|
|
146
143
|
documentType: 'article',
|
|
147
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
148
144
|
},
|
|
149
145
|
]
|
|
150
146
|
const grants = {...defaultGrants, create: alwaysDeny}
|
|
@@ -166,7 +162,6 @@ describe('processActions', () => {
|
|
|
166
162
|
author: 'John Doe',
|
|
167
163
|
count: 42,
|
|
168
164
|
},
|
|
169
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
170
165
|
},
|
|
171
166
|
]
|
|
172
167
|
const result = processActions({
|
|
@@ -203,7 +198,6 @@ describe('processActions', () => {
|
|
|
203
198
|
title: 'Overridden Title',
|
|
204
199
|
newField: 'New Value',
|
|
205
200
|
},
|
|
206
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
207
201
|
},
|
|
208
202
|
]
|
|
209
203
|
const result = processActions({
|
|
@@ -233,7 +227,6 @@ describe('processActions', () => {
|
|
|
233
227
|
type: 'document.create',
|
|
234
228
|
documentType: 'article',
|
|
235
229
|
initialValue: {},
|
|
236
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
237
230
|
},
|
|
238
231
|
]
|
|
239
232
|
const result = processActions({
|
|
@@ -265,7 +258,6 @@ describe('processActions', () => {
|
|
|
265
258
|
documentId: 'doc1',
|
|
266
259
|
documentType: 'article',
|
|
267
260
|
type: 'document.delete',
|
|
268
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
269
261
|
},
|
|
270
262
|
]
|
|
271
263
|
const result = processActions({
|
|
@@ -302,7 +294,6 @@ describe('processActions', () => {
|
|
|
302
294
|
documentId: 'doc1',
|
|
303
295
|
documentType: 'article',
|
|
304
296
|
type: 'document.delete',
|
|
305
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
306
297
|
},
|
|
307
298
|
]
|
|
308
299
|
const result = processActions({
|
|
@@ -335,7 +326,6 @@ describe('processActions', () => {
|
|
|
335
326
|
documentId: 'doc1',
|
|
336
327
|
documentType: 'article',
|
|
337
328
|
type: 'document.delete',
|
|
338
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
339
329
|
},
|
|
340
330
|
]
|
|
341
331
|
const grants = {...defaultGrants, update: alwaysDeny}
|
|
@@ -355,7 +345,6 @@ describe('processActions', () => {
|
|
|
355
345
|
documentId: 'doc1',
|
|
356
346
|
documentType: 'article',
|
|
357
347
|
type: 'document.discard',
|
|
358
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
359
348
|
},
|
|
360
349
|
]
|
|
361
350
|
const result = processActions({
|
|
@@ -385,7 +374,6 @@ describe('processActions', () => {
|
|
|
385
374
|
documentId: 'doc1',
|
|
386
375
|
documentType: 'article',
|
|
387
376
|
type: 'document.discard',
|
|
388
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
389
377
|
},
|
|
390
378
|
]
|
|
391
379
|
const grants = {...defaultGrants, update: alwaysDeny}
|
|
@@ -406,7 +394,6 @@ describe('processActions', () => {
|
|
|
406
394
|
documentType: 'article',
|
|
407
395
|
type: 'document.edit',
|
|
408
396
|
patches: [{set: {title: 'Edited Title'}}],
|
|
409
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
410
397
|
},
|
|
411
398
|
]
|
|
412
399
|
const result = processActions({
|
|
@@ -439,7 +426,6 @@ describe('processActions', () => {
|
|
|
439
426
|
documentType: 'article',
|
|
440
427
|
type: 'document.edit',
|
|
441
428
|
patches: [{set: {title: 'New Draft Title'}}],
|
|
442
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
443
429
|
},
|
|
444
430
|
]
|
|
445
431
|
const result = processActions({
|
|
@@ -457,6 +443,190 @@ describe('processActions', () => {
|
|
|
457
443
|
expect(result.outgoingActions[0].actionType).toBe('sanity.action.document.edit')
|
|
458
444
|
})
|
|
459
445
|
|
|
446
|
+
it('preserves inc/dec operations instead of collapsing them into sets', () => {
|
|
447
|
+
const draft = {...createDoc('drafts.doc1', 'Title', '1'), count: 5, score: 10}
|
|
448
|
+
const base: DocumentSet = {'drafts.doc1': draft}
|
|
449
|
+
const working: DocumentSet = {'drafts.doc1': draft}
|
|
450
|
+
const actions: DocumentAction[] = [
|
|
451
|
+
{
|
|
452
|
+
documentId: 'doc1',
|
|
453
|
+
documentType: 'article',
|
|
454
|
+
type: 'document.edit',
|
|
455
|
+
patches: [{inc: {count: 1}, dec: {score: 3}}],
|
|
456
|
+
},
|
|
457
|
+
]
|
|
458
|
+
const result = processActions({
|
|
459
|
+
actions,
|
|
460
|
+
transactionId,
|
|
461
|
+
base,
|
|
462
|
+
working,
|
|
463
|
+
timestamp,
|
|
464
|
+
grants: defaultGrants,
|
|
465
|
+
})
|
|
466
|
+
|
|
467
|
+
// the local working copy reflects the applied increments
|
|
468
|
+
expect(result.working['drafts.doc1']).toMatchObject({count: 6, score: 7})
|
|
469
|
+
// the outgoing patch keeps the operational inc/dec instead of a set.
|
|
470
|
+
// the setIfMissing seeds the base value in case the field was removed
|
|
471
|
+
// concurrently: the server runs setIfMissing before inc/dec, and inc on
|
|
472
|
+
// a missing path would otherwise fail the whole transaction
|
|
473
|
+
const patches = result.outgoingActions.map((action) => 'patch' in action && action.patch)
|
|
474
|
+
expect(patches).toEqual([
|
|
475
|
+
{setIfMissing: {count: 5, score: 10}, inc: {count: 1}, dec: {score: 3}},
|
|
476
|
+
])
|
|
477
|
+
})
|
|
478
|
+
|
|
479
|
+
it('accumulates multiple inc/dec records on the same path into one operation', () => {
|
|
480
|
+
const draft = {...createDoc('drafts.doc1', 'Title', '1'), count: 5}
|
|
481
|
+
const base: DocumentSet = {'drafts.doc1': draft}
|
|
482
|
+
const working: DocumentSet = {'drafts.doc1': draft}
|
|
483
|
+
const actions: DocumentAction[] = [
|
|
484
|
+
{
|
|
485
|
+
documentId: 'doc1',
|
|
486
|
+
documentType: 'article',
|
|
487
|
+
type: 'document.edit',
|
|
488
|
+
patches: [{inc: {count: 1}}, {inc: {count: 2}}, {dec: {count: 4}}],
|
|
489
|
+
},
|
|
490
|
+
]
|
|
491
|
+
const result = processActions({
|
|
492
|
+
actions,
|
|
493
|
+
transactionId,
|
|
494
|
+
base,
|
|
495
|
+
working,
|
|
496
|
+
timestamp,
|
|
497
|
+
grants: defaultGrants,
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
// 5 + 1 + 2 - 4 = 4 locally
|
|
501
|
+
expect(result.working['drafts.doc1']).toMatchObject({count: 4})
|
|
502
|
+
// the three records collapse into one accumulated dec (net -1) with the
|
|
503
|
+
// base value seeded for the concurrent-removal degradation path
|
|
504
|
+
const patches = result.outgoingActions.map((action) => 'patch' in action && action.patch)
|
|
505
|
+
expect(patches).toEqual([{setIfMissing: {count: 5}, dec: {count: 1}}])
|
|
506
|
+
})
|
|
507
|
+
|
|
508
|
+
it('keeps the diffed set when an inc is combined with a set of the same field', () => {
|
|
509
|
+
const draft = {...createDoc('drafts.doc1', 'Title', '1'), count: 5}
|
|
510
|
+
const base: DocumentSet = {'drafts.doc1': draft}
|
|
511
|
+
const working: DocumentSet = {'drafts.doc1': draft}
|
|
512
|
+
const actions: DocumentAction[] = [
|
|
513
|
+
{
|
|
514
|
+
documentId: 'doc1',
|
|
515
|
+
documentType: 'article',
|
|
516
|
+
type: 'document.edit',
|
|
517
|
+
// set to 100 first, then inc: the final value (101) is not
|
|
518
|
+
// explainable by the inc alone, so it must remain a set
|
|
519
|
+
patches: [{set: {count: 100}}, {inc: {count: 1}}],
|
|
520
|
+
},
|
|
521
|
+
]
|
|
522
|
+
const result = processActions({
|
|
523
|
+
actions,
|
|
524
|
+
transactionId,
|
|
525
|
+
base,
|
|
526
|
+
working,
|
|
527
|
+
timestamp,
|
|
528
|
+
grants: defaultGrants,
|
|
529
|
+
})
|
|
530
|
+
|
|
531
|
+
expect(result.working['drafts.doc1']).toMatchObject({count: 101})
|
|
532
|
+
const patches = result.outgoingActions.map((action) => 'patch' in action && action.patch)
|
|
533
|
+
expect(patches).toEqual([{set: {count: 101}}])
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
it('preserves inc operations targeting keyed array items', () => {
|
|
537
|
+
const draft = {
|
|
538
|
+
...createDoc('drafts.doc1', 'Title', '1'),
|
|
539
|
+
items: [{_key: 'itemA', qty: 2}],
|
|
540
|
+
}
|
|
541
|
+
const base: DocumentSet = {'drafts.doc1': draft}
|
|
542
|
+
const working: DocumentSet = {'drafts.doc1': draft}
|
|
543
|
+
const actions: DocumentAction[] = [
|
|
544
|
+
{
|
|
545
|
+
documentId: 'doc1',
|
|
546
|
+
documentType: 'article',
|
|
547
|
+
type: 'document.edit',
|
|
548
|
+
patches: [{inc: {'items[_key=="itemA"].qty': 3}}],
|
|
549
|
+
},
|
|
550
|
+
]
|
|
551
|
+
const result = processActions({
|
|
552
|
+
actions,
|
|
553
|
+
transactionId,
|
|
554
|
+
base,
|
|
555
|
+
working,
|
|
556
|
+
timestamp,
|
|
557
|
+
grants: defaultGrants,
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
expect(result.working['drafts.doc1']?.['items']).toEqual([{_key: 'itemA', qty: 5}])
|
|
561
|
+
const patches = result.outgoingActions.map((action) => 'patch' in action && action.patch)
|
|
562
|
+
expect(patches).toEqual([
|
|
563
|
+
{setIfMissing: {'items[_key=="itemA"].qty': 2}, inc: {'items[_key=="itemA"].qty': 3}},
|
|
564
|
+
])
|
|
565
|
+
})
|
|
566
|
+
|
|
567
|
+
it('preserves inc operations through the liveEdit mutation path', () => {
|
|
568
|
+
const liveDoc = {...createLiveEditDoc('live-doc', 'Title', '1'), count: 5}
|
|
569
|
+
const base: DocumentSet = {'live-doc': liveDoc}
|
|
570
|
+
const working: DocumentSet = {'live-doc': liveDoc}
|
|
571
|
+
const actions: DocumentAction[] = [
|
|
572
|
+
{
|
|
573
|
+
documentId: 'live-doc',
|
|
574
|
+
documentType: 'liveArticle',
|
|
575
|
+
type: 'document.edit',
|
|
576
|
+
liveEdit: true,
|
|
577
|
+
patches: [{inc: {count: 2}}],
|
|
578
|
+
},
|
|
579
|
+
]
|
|
580
|
+
const result = processActions({
|
|
581
|
+
actions,
|
|
582
|
+
transactionId,
|
|
583
|
+
base,
|
|
584
|
+
working,
|
|
585
|
+
timestamp,
|
|
586
|
+
grants: defaultGrants,
|
|
587
|
+
})
|
|
588
|
+
|
|
589
|
+
expect(result.working['live-doc']).toMatchObject({count: 7})
|
|
590
|
+
expect(result.outgoingMutations).toEqual([
|
|
591
|
+
{patch: {id: 'live-doc', setIfMissing: {count: 5}, inc: {count: 2}}},
|
|
592
|
+
])
|
|
593
|
+
})
|
|
594
|
+
|
|
595
|
+
it('degrades to the diffed set result when the inc target was removed concurrently', () => {
|
|
596
|
+
const draft = {...createDoc('drafts.doc1', 'Title', '1'), count: 5}
|
|
597
|
+
const base: DocumentSet = {'drafts.doc1': draft}
|
|
598
|
+
const working: DocumentSet = {'drafts.doc1': draft}
|
|
599
|
+
const actions: DocumentAction[] = [
|
|
600
|
+
{
|
|
601
|
+
documentId: 'doc1',
|
|
602
|
+
documentType: 'article',
|
|
603
|
+
type: 'document.edit',
|
|
604
|
+
patches: [{inc: {count: 1}}],
|
|
605
|
+
},
|
|
606
|
+
]
|
|
607
|
+
const result = processActions({
|
|
608
|
+
actions,
|
|
609
|
+
transactionId,
|
|
610
|
+
base,
|
|
611
|
+
working,
|
|
612
|
+
timestamp,
|
|
613
|
+
grants: defaultGrants,
|
|
614
|
+
})
|
|
615
|
+
const patch = result.outgoingActions.map((action) => 'patch' in action && action.patch)[0]
|
|
616
|
+
|
|
617
|
+
// simulate the server applying the emitted patch to a document where
|
|
618
|
+
// another client removed the counter in the meantime: setIfMissing
|
|
619
|
+
// seeds the base value, then inc applies, matching what the collapsed
|
|
620
|
+
// set would have written (instead of inc failing the transaction)
|
|
621
|
+
const {count: _count, ...draftWithoutCount} = draft
|
|
622
|
+
const serverResult = processMutations({
|
|
623
|
+
documents: {'drafts.doc1': draftWithoutCount as SanityDocument},
|
|
624
|
+
mutations: [{patch: {id: 'drafts.doc1', ...(patch as object)}}],
|
|
625
|
+
transactionId: 'txn-server',
|
|
626
|
+
})
|
|
627
|
+
expect(serverResult['drafts.doc1']).toMatchObject({count: 6})
|
|
628
|
+
})
|
|
629
|
+
|
|
460
630
|
it('should edit a document when base and working diverge', () => {
|
|
461
631
|
const publishedBase = createDoc('doc1', 'Original Boring Title')
|
|
462
632
|
const publishedWorking = createDoc('doc1', 'Local Boring Title')
|
|
@@ -468,7 +638,6 @@ describe('processActions', () => {
|
|
|
468
638
|
documentType: 'article',
|
|
469
639
|
type: 'document.edit',
|
|
470
640
|
patches: [{set: {title: 'Original Cool Title'}}],
|
|
471
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
472
641
|
},
|
|
473
642
|
]
|
|
474
643
|
const result = processActions({
|
|
@@ -498,7 +667,6 @@ describe('processActions', () => {
|
|
|
498
667
|
documentType: 'article',
|
|
499
668
|
type: 'document.edit',
|
|
500
669
|
patches: [{set: {title: 'Should Fail'}}],
|
|
501
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
502
670
|
},
|
|
503
671
|
]
|
|
504
672
|
expect(() =>
|
|
@@ -516,7 +684,6 @@ describe('processActions', () => {
|
|
|
516
684
|
documentType: 'article',
|
|
517
685
|
type: 'document.edit',
|
|
518
686
|
patches: [{set: {title: 'Edited Title'}}],
|
|
519
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
520
687
|
},
|
|
521
688
|
]
|
|
522
689
|
const grants = {...defaultGrants, create: alwaysDeny}
|
|
@@ -535,7 +702,6 @@ describe('processActions', () => {
|
|
|
535
702
|
documentType: 'article',
|
|
536
703
|
type: 'document.edit',
|
|
537
704
|
patches: [{set: {title: 'New Title'}}],
|
|
538
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
539
705
|
},
|
|
540
706
|
]
|
|
541
707
|
const grants = {...defaultGrants, update: alwaysDeny}
|
|
@@ -545,6 +711,151 @@ describe('processActions', () => {
|
|
|
545
711
|
})
|
|
546
712
|
})
|
|
547
713
|
|
|
714
|
+
describe('document.edit with preserveOperations', () => {
|
|
715
|
+
const createKeyedDoc = (): SanityDocument => ({
|
|
716
|
+
_id: 'drafts.doc1',
|
|
717
|
+
_type: 'article',
|
|
718
|
+
_createdAt: '2025-01-01T00:00:00.000Z',
|
|
719
|
+
_updatedAt: '2025-01-01T00:00:00.000Z',
|
|
720
|
+
_rev: 'initial',
|
|
721
|
+
items: [
|
|
722
|
+
{_key: 'a', value: 'first'},
|
|
723
|
+
{_key: 'b', value: 'second'},
|
|
724
|
+
],
|
|
725
|
+
})
|
|
726
|
+
|
|
727
|
+
it('forwards the given patches verbatim instead of re-diffing', () => {
|
|
728
|
+
const draft = createKeyedDoc()
|
|
729
|
+
const base: DocumentSet = {'drafts.doc1': draft}
|
|
730
|
+
const working: DocumentSet = {'drafts.doc1': draft}
|
|
731
|
+
const patches = [
|
|
732
|
+
{insert: {after: 'items[_key=="a"]', items: [{_key: 'c', value: 'in between'}]}},
|
|
733
|
+
]
|
|
734
|
+
const actions: DocumentAction[] = [
|
|
735
|
+
{
|
|
736
|
+
documentId: 'doc1',
|
|
737
|
+
documentType: 'article',
|
|
738
|
+
type: 'document.edit',
|
|
739
|
+
patches,
|
|
740
|
+
preserveOperations: true,
|
|
741
|
+
},
|
|
742
|
+
]
|
|
743
|
+
const result = processActions({
|
|
744
|
+
actions,
|
|
745
|
+
transactionId,
|
|
746
|
+
base,
|
|
747
|
+
working,
|
|
748
|
+
timestamp,
|
|
749
|
+
grants: defaultGrants,
|
|
750
|
+
})
|
|
751
|
+
|
|
752
|
+
// the outgoing action carries the caller's keyed patch untouched
|
|
753
|
+
expect(result.outgoingActions).toEqual([
|
|
754
|
+
{
|
|
755
|
+
actionType: 'sanity.action.document.edit',
|
|
756
|
+
draftId: 'drafts.doc1',
|
|
757
|
+
publishedId: 'doc1',
|
|
758
|
+
patch: patches[0],
|
|
759
|
+
},
|
|
760
|
+
])
|
|
761
|
+
|
|
762
|
+
// and it still applies to the local working copy
|
|
763
|
+
expect(result.working['drafts.doc1']?.['items']).toEqual([
|
|
764
|
+
{_key: 'a', value: 'first'},
|
|
765
|
+
{_key: 'c', value: 'in between'},
|
|
766
|
+
{_key: 'b', value: 'second'},
|
|
767
|
+
])
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
it('applies keyed unsets and sets to the local working copy', () => {
|
|
771
|
+
const draft = createKeyedDoc()
|
|
772
|
+
const base: DocumentSet = {'drafts.doc1': draft}
|
|
773
|
+
const working: DocumentSet = {'drafts.doc1': draft}
|
|
774
|
+
const actions: DocumentAction[] = [
|
|
775
|
+
{
|
|
776
|
+
documentId: 'doc1',
|
|
777
|
+
documentType: 'article',
|
|
778
|
+
type: 'document.edit',
|
|
779
|
+
patches: [
|
|
780
|
+
{unset: ['items[_key=="a"]']},
|
|
781
|
+
{set: {'items[_key=="b"].value': 'updated second'}},
|
|
782
|
+
],
|
|
783
|
+
preserveOperations: true,
|
|
784
|
+
},
|
|
785
|
+
]
|
|
786
|
+
const result = processActions({
|
|
787
|
+
actions,
|
|
788
|
+
transactionId,
|
|
789
|
+
base,
|
|
790
|
+
working,
|
|
791
|
+
timestamp,
|
|
792
|
+
grants: defaultGrants,
|
|
793
|
+
})
|
|
794
|
+
|
|
795
|
+
expect(result.working['drafts.doc1']?.['items']).toEqual([
|
|
796
|
+
{_key: 'b', value: 'updated second'},
|
|
797
|
+
])
|
|
798
|
+
expect(result.outgoingActions.map((action) => 'patch' in action && action.patch)).toEqual([
|
|
799
|
+
{unset: ['items[_key=="a"]']},
|
|
800
|
+
{set: {'items[_key=="b"].value': 'updated second'}},
|
|
801
|
+
])
|
|
802
|
+
})
|
|
803
|
+
|
|
804
|
+
it('surfaces patch application failures as ActionError', () => {
|
|
805
|
+
const draft = {...createKeyedDoc(), count: 5}
|
|
806
|
+
const base: DocumentSet = {'drafts.doc1': draft}
|
|
807
|
+
const working: DocumentSet = {'drafts.doc1': draft}
|
|
808
|
+
const actions: DocumentAction[] = [
|
|
809
|
+
{
|
|
810
|
+
documentId: 'doc1',
|
|
811
|
+
documentType: 'article',
|
|
812
|
+
type: 'document.edit',
|
|
813
|
+
// diff-match-patch on a non-string value cannot apply
|
|
814
|
+
patches: [{diffMatchPatch: {count: '@@ -1,3 +1,3 @@\n-foo\n+bar\n'}}],
|
|
815
|
+
preserveOperations: true,
|
|
816
|
+
},
|
|
817
|
+
]
|
|
818
|
+
expect(() =>
|
|
819
|
+
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
820
|
+
).toThrow(ActionError)
|
|
821
|
+
expect(() =>
|
|
822
|
+
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
823
|
+
).toThrow(/Failed to apply patches to the base document/)
|
|
824
|
+
})
|
|
825
|
+
|
|
826
|
+
it('forwards patches verbatim through the liveEdit mutation path', () => {
|
|
827
|
+
const liveDoc = {...createKeyedDoc(), _id: 'live-doc'}
|
|
828
|
+
const base: DocumentSet = {'live-doc': liveDoc}
|
|
829
|
+
const working: DocumentSet = {'live-doc': liveDoc}
|
|
830
|
+
const patches = [{insert: {before: 'items[_key=="a"]', items: [{_key: 'z', value: 'start'}]}}]
|
|
831
|
+
const actions: DocumentAction[] = [
|
|
832
|
+
{
|
|
833
|
+
documentId: 'live-doc',
|
|
834
|
+
documentType: 'liveArticle',
|
|
835
|
+
type: 'document.edit',
|
|
836
|
+
liveEdit: true,
|
|
837
|
+
patches,
|
|
838
|
+
preserveOperations: true,
|
|
839
|
+
},
|
|
840
|
+
]
|
|
841
|
+
const result = processActions({
|
|
842
|
+
actions,
|
|
843
|
+
transactionId,
|
|
844
|
+
base,
|
|
845
|
+
working,
|
|
846
|
+
timestamp,
|
|
847
|
+
grants: defaultGrants,
|
|
848
|
+
})
|
|
849
|
+
|
|
850
|
+
expect(result.outgoingMutations).toEqual([{patch: {id: 'live-doc', ...patches[0]}}])
|
|
851
|
+
expect(result.working['live-doc']?.['items']).toEqual([
|
|
852
|
+
{_key: 'z', value: 'start'},
|
|
853
|
+
{_key: 'a', value: 'first'},
|
|
854
|
+
{_key: 'b', value: 'second'},
|
|
855
|
+
])
|
|
856
|
+
})
|
|
857
|
+
})
|
|
858
|
+
|
|
548
859
|
describe('document.publish', () => {
|
|
549
860
|
it('should publish a draft document', () => {
|
|
550
861
|
const draft = createDoc('drafts.doc1', 'Draft Title', '1')
|
|
@@ -555,7 +866,6 @@ describe('processActions', () => {
|
|
|
555
866
|
documentId: 'doc1',
|
|
556
867
|
documentType: 'article',
|
|
557
868
|
type: 'document.publish',
|
|
558
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
559
869
|
},
|
|
560
870
|
]
|
|
561
871
|
const result = processActions({
|
|
@@ -591,7 +901,6 @@ describe('processActions', () => {
|
|
|
591
901
|
documentId: 'doc1',
|
|
592
902
|
documentType: 'article',
|
|
593
903
|
type: 'document.publish',
|
|
594
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
595
904
|
},
|
|
596
905
|
]
|
|
597
906
|
expect(() =>
|
|
@@ -610,7 +919,6 @@ describe('processActions', () => {
|
|
|
610
919
|
documentId: 'doc1',
|
|
611
920
|
documentType: 'article',
|
|
612
921
|
type: 'document.publish',
|
|
613
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
614
922
|
},
|
|
615
923
|
]
|
|
616
924
|
expect(() =>
|
|
@@ -623,12 +931,7 @@ describe('processActions', () => {
|
|
|
623
931
|
const base: DocumentSet = {'drafts.doc1': draft}
|
|
624
932
|
const working: DocumentSet = {'drafts.doc1': draft}
|
|
625
933
|
const actions: DocumentAction[] = [
|
|
626
|
-
{
|
|
627
|
-
documentId: 'doc1',
|
|
628
|
-
documentType: 'article',
|
|
629
|
-
type: 'document.publish',
|
|
630
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
631
|
-
},
|
|
934
|
+
{documentId: 'doc1', documentType: 'article', type: 'document.publish'},
|
|
632
935
|
]
|
|
633
936
|
const grants = {...defaultGrants, update: alwaysDeny}
|
|
634
937
|
expect(() =>
|
|
@@ -642,12 +945,7 @@ describe('processActions', () => {
|
|
|
642
945
|
const base: DocumentSet = {'drafts.doc1': draft, 'doc1': published}
|
|
643
946
|
const working: DocumentSet = {'drafts.doc1': draft, 'doc1': published}
|
|
644
947
|
const actions: DocumentAction[] = [
|
|
645
|
-
{
|
|
646
|
-
documentId: 'doc1',
|
|
647
|
-
documentType: 'article',
|
|
648
|
-
type: 'document.publish',
|
|
649
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
650
|
-
},
|
|
948
|
+
{documentId: 'doc1', documentType: 'article', type: 'document.publish'},
|
|
651
949
|
]
|
|
652
950
|
const grants = {
|
|
653
951
|
...defaultGrants,
|
|
@@ -666,12 +964,7 @@ describe('processActions', () => {
|
|
|
666
964
|
const base: DocumentSet = {'drafts.doc1': draft}
|
|
667
965
|
const working: DocumentSet = {'drafts.doc1': draft}
|
|
668
966
|
const actions: DocumentAction[] = [
|
|
669
|
-
{
|
|
670
|
-
documentId: 'doc1',
|
|
671
|
-
documentType: 'article',
|
|
672
|
-
type: 'document.publish',
|
|
673
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
674
|
-
},
|
|
967
|
+
{documentId: 'doc1', documentType: 'article', type: 'document.publish'},
|
|
675
968
|
]
|
|
676
969
|
const grants = {...defaultGrants, create: alwaysDeny}
|
|
677
970
|
expect(() =>
|
|
@@ -707,7 +1000,6 @@ describe('processActions', () => {
|
|
|
707
1000
|
documentId: 'doc1',
|
|
708
1001
|
documentType: 'article',
|
|
709
1002
|
type: 'document.publish',
|
|
710
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
711
1003
|
},
|
|
712
1004
|
]
|
|
713
1005
|
const result = processActions({
|
|
@@ -740,7 +1032,6 @@ describe('processActions', () => {
|
|
|
740
1032
|
documentId: 'doc1',
|
|
741
1033
|
documentType: 'article',
|
|
742
1034
|
type: 'document.unpublish',
|
|
743
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
744
1035
|
},
|
|
745
1036
|
]
|
|
746
1037
|
const result = processActions({
|
|
@@ -775,7 +1066,6 @@ describe('processActions', () => {
|
|
|
775
1066
|
documentId: 'doc1',
|
|
776
1067
|
documentType: 'article',
|
|
777
1068
|
type: 'document.unpublish',
|
|
778
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
779
1069
|
},
|
|
780
1070
|
]
|
|
781
1071
|
expect(() =>
|
|
@@ -794,7 +1084,6 @@ describe('processActions', () => {
|
|
|
794
1084
|
documentId: 'doc1',
|
|
795
1085
|
documentType: 'article',
|
|
796
1086
|
type: 'document.unpublish',
|
|
797
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
798
1087
|
},
|
|
799
1088
|
]
|
|
800
1089
|
const result = processActions({
|
|
@@ -826,12 +1115,7 @@ describe('processActions', () => {
|
|
|
826
1115
|
const base: DocumentSet = {doc1: published}
|
|
827
1116
|
const working: DocumentSet = {doc1: published}
|
|
828
1117
|
const actions: DocumentAction[] = [
|
|
829
|
-
{
|
|
830
|
-
documentId: 'doc1',
|
|
831
|
-
documentType: 'article',
|
|
832
|
-
type: 'document.unpublish',
|
|
833
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
834
|
-
},
|
|
1118
|
+
{documentId: 'doc1', documentType: 'article', type: 'document.unpublish'},
|
|
835
1119
|
]
|
|
836
1120
|
const grants = {...defaultGrants, update: alwaysDeny}
|
|
837
1121
|
expect(() =>
|
|
@@ -844,12 +1128,7 @@ describe('processActions', () => {
|
|
|
844
1128
|
const base: DocumentSet = {doc1: published}
|
|
845
1129
|
const working: DocumentSet = {doc1: published}
|
|
846
1130
|
const actions: DocumentAction[] = [
|
|
847
|
-
{
|
|
848
|
-
documentId: 'doc1',
|
|
849
|
-
documentType: 'article',
|
|
850
|
-
type: 'document.unpublish',
|
|
851
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
852
|
-
},
|
|
1131
|
+
{documentId: 'doc1', documentType: 'article', type: 'document.unpublish'},
|
|
853
1132
|
]
|
|
854
1133
|
const grants = {...defaultGrants, create: alwaysDeny}
|
|
855
1134
|
expect(() =>
|
|
@@ -864,25 +1143,14 @@ describe('processActions', () => {
|
|
|
864
1143
|
const base: DocumentSet = {doc1: published}
|
|
865
1144
|
const working: DocumentSet = {doc1: published}
|
|
866
1145
|
const actions: DocumentAction[] = [
|
|
867
|
-
{
|
|
868
|
-
documentId: 'doc1',
|
|
869
|
-
documentType: 'article',
|
|
870
|
-
type: 'document.create',
|
|
871
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
872
|
-
},
|
|
1146
|
+
{documentId: 'doc1', documentType: 'article', type: 'document.create'},
|
|
873
1147
|
{
|
|
874
1148
|
documentId: 'doc1',
|
|
875
1149
|
documentType: 'article',
|
|
876
1150
|
type: 'document.edit',
|
|
877
1151
|
patches: [{set: {title: 'Edited Title'}}],
|
|
878
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
879
|
-
},
|
|
880
|
-
{
|
|
881
|
-
documentId: 'doc1',
|
|
882
|
-
documentType: 'article',
|
|
883
|
-
type: 'document.publish',
|
|
884
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
885
1152
|
},
|
|
1153
|
+
{documentId: 'doc1', documentType: 'article', type: 'document.publish'},
|
|
886
1154
|
]
|
|
887
1155
|
const result = processActions({
|
|
888
1156
|
actions,
|
|
@@ -928,7 +1196,6 @@ describe('processActions', () => {
|
|
|
928
1196
|
type: 'document.create',
|
|
929
1197
|
documentType: 'liveArticle',
|
|
930
1198
|
liveEdit: true,
|
|
931
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
932
1199
|
},
|
|
933
1200
|
]
|
|
934
1201
|
|
|
@@ -947,38 +1214,63 @@ describe('processActions', () => {
|
|
|
947
1214
|
expect(doc?._type).toBe('liveArticle')
|
|
948
1215
|
expect(doc?._rev).toBe(transactionId)
|
|
949
1216
|
|
|
950
|
-
// Should use
|
|
951
|
-
expect(result.outgoingActions).toHaveLength(
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
expect(action.attributes._id).toBe('live1')
|
|
957
|
-
expect(action.attributes._type).toBe('liveArticle')
|
|
958
|
-
} else {
|
|
959
|
-
throw new Error('Expected action to have attributes and publishedId')
|
|
960
|
-
}
|
|
1217
|
+
// Should not use actions
|
|
1218
|
+
expect(result.outgoingActions).toHaveLength(0)
|
|
1219
|
+
expect(result.outgoingMutations).toHaveLength(1)
|
|
1220
|
+
const mutation = result.outgoingMutations[0] as CreateMutation
|
|
1221
|
+
expect(mutation.create._id).toBe('live1')
|
|
1222
|
+
expect(mutation.create._type).toBe('liveArticle')
|
|
961
1223
|
})
|
|
962
1224
|
|
|
963
|
-
it('should
|
|
964
|
-
const
|
|
965
|
-
const
|
|
966
|
-
const working: DocumentSet = {live1: existingDoc}
|
|
1225
|
+
it('should apply initialValue when creating a liveEdit document', () => {
|
|
1226
|
+
const base: DocumentSet = {}
|
|
1227
|
+
const working: DocumentSet = {}
|
|
967
1228
|
const actions: DocumentAction[] = [
|
|
968
1229
|
{
|
|
969
1230
|
documentId: 'live1',
|
|
970
1231
|
type: 'document.create',
|
|
971
1232
|
documentType: 'liveArticle',
|
|
972
1233
|
liveEdit: true,
|
|
973
|
-
|
|
1234
|
+
initialValue: {title: 'Initial Title', count: 42},
|
|
974
1235
|
},
|
|
975
1236
|
]
|
|
976
1237
|
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
1238
|
+
const result = processActions({
|
|
1239
|
+
actions,
|
|
1240
|
+
transactionId,
|
|
1241
|
+
base,
|
|
1242
|
+
working,
|
|
1243
|
+
timestamp,
|
|
1244
|
+
grants: defaultGrants,
|
|
1245
|
+
})
|
|
1246
|
+
|
|
1247
|
+
const doc = result.working['live1']
|
|
1248
|
+
expect(doc?.['title']).toBe('Initial Title')
|
|
1249
|
+
expect(doc?.['count']).toBe(42)
|
|
1250
|
+
|
|
1251
|
+
const mutation = result.outgoingMutations[0] as CreateMutation
|
|
1252
|
+
expect(mutation.create['title']).toBe('Initial Title')
|
|
1253
|
+
expect(mutation.create['count']).toBe(42)
|
|
1254
|
+
})
|
|
1255
|
+
|
|
1256
|
+
it('should throw an error if liveEdit document already exists', () => {
|
|
1257
|
+
const existingDoc = createLiveEditDoc('live1', 'Existing')
|
|
1258
|
+
const base: DocumentSet = {live1: existingDoc}
|
|
1259
|
+
const working: DocumentSet = {live1: existingDoc}
|
|
1260
|
+
const actions: DocumentAction[] = [
|
|
1261
|
+
{
|
|
1262
|
+
documentId: 'live1',
|
|
1263
|
+
type: 'document.create',
|
|
1264
|
+
documentType: 'liveArticle',
|
|
1265
|
+
liveEdit: true,
|
|
1266
|
+
},
|
|
1267
|
+
]
|
|
1268
|
+
|
|
1269
|
+
expect(() =>
|
|
1270
|
+
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1271
|
+
).toThrow('This document already exists')
|
|
1272
|
+
})
|
|
1273
|
+
})
|
|
982
1274
|
|
|
983
1275
|
describe('document.edit', () => {
|
|
984
1276
|
it('should edit a liveEdit document directly', () => {
|
|
@@ -992,7 +1284,6 @@ describe('processActions', () => {
|
|
|
992
1284
|
documentType: 'liveArticle',
|
|
993
1285
|
liveEdit: true,
|
|
994
1286
|
patches: [{set: {title: 'Updated Title'}}],
|
|
995
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
996
1287
|
},
|
|
997
1288
|
]
|
|
998
1289
|
|
|
@@ -1010,12 +1301,15 @@ describe('processActions', () => {
|
|
|
1010
1301
|
expect(editedDoc?.['title']).toBe('Updated Title')
|
|
1011
1302
|
expect(editedDoc?._id).toBe('live1')
|
|
1012
1303
|
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1304
|
+
expect(result.outgoingMutations[0]).toEqual({
|
|
1305
|
+
patch: {
|
|
1306
|
+
id: 'live1',
|
|
1307
|
+
diffMatchPatch: {
|
|
1308
|
+
title: '@@ -1,12 +1,11 @@\n-Original\n+Updated\n Tit\n',
|
|
1309
|
+
},
|
|
1310
|
+
},
|
|
1018
1311
|
})
|
|
1312
|
+
expect(result.outgoingActions).toHaveLength(0)
|
|
1019
1313
|
})
|
|
1020
1314
|
|
|
1021
1315
|
it('should throw an error if liveEdit document does not exist', () => {
|
|
@@ -1028,7 +1322,6 @@ describe('processActions', () => {
|
|
|
1028
1322
|
documentType: 'liveArticle',
|
|
1029
1323
|
liveEdit: true,
|
|
1030
1324
|
patches: [{set: {title: 'New Title'}}],
|
|
1031
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
1032
1325
|
},
|
|
1033
1326
|
]
|
|
1034
1327
|
|
|
@@ -1049,7 +1342,6 @@ describe('processActions', () => {
|
|
|
1049
1342
|
type: 'document.delete',
|
|
1050
1343
|
documentType: 'liveArticle',
|
|
1051
1344
|
liveEdit: true,
|
|
1052
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
1053
1345
|
},
|
|
1054
1346
|
]
|
|
1055
1347
|
|
|
@@ -1064,12 +1356,9 @@ describe('processActions', () => {
|
|
|
1064
1356
|
|
|
1065
1357
|
expect(result.working['live1']).toBeNull()
|
|
1066
1358
|
|
|
1067
|
-
expect(result.outgoingActions).
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
publishedId: 'live1',
|
|
1071
|
-
},
|
|
1072
|
-
])
|
|
1359
|
+
expect(result.outgoingActions).toHaveLength(0)
|
|
1360
|
+
expect(result.outgoingMutations).toHaveLength(1)
|
|
1361
|
+
expect(result.outgoingMutations[0]).toEqual({delete: {id: 'live1'}})
|
|
1073
1362
|
})
|
|
1074
1363
|
|
|
1075
1364
|
it('should throw an error if liveEdit document does not exist', () => {
|
|
@@ -1081,7 +1370,6 @@ describe('processActions', () => {
|
|
|
1081
1370
|
type: 'document.delete',
|
|
1082
1371
|
documentType: 'liveArticle',
|
|
1083
1372
|
liveEdit: true,
|
|
1084
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
1085
1373
|
},
|
|
1086
1374
|
]
|
|
1087
1375
|
|
|
@@ -1102,16 +1390,12 @@ describe('processActions', () => {
|
|
|
1102
1390
|
type: 'document.publish',
|
|
1103
1391
|
documentType: 'liveArticle',
|
|
1104
1392
|
liveEdit: true,
|
|
1105
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
1106
1393
|
},
|
|
1107
1394
|
]
|
|
1108
1395
|
|
|
1109
1396
|
expect(() =>
|
|
1110
1397
|
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1111
|
-
).toThrow(
|
|
1112
|
-
expect(() =>
|
|
1113
|
-
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1114
|
-
).toThrow('LiveEdit documents do not support drafts or publishing')
|
|
1398
|
+
).toThrow(/Publishing is not supported for liveEdit or version/)
|
|
1115
1399
|
})
|
|
1116
1400
|
})
|
|
1117
1401
|
|
|
@@ -1126,16 +1410,12 @@ describe('processActions', () => {
|
|
|
1126
1410
|
type: 'document.unpublish',
|
|
1127
1411
|
documentType: 'liveArticle',
|
|
1128
1412
|
liveEdit: true,
|
|
1129
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
1130
1413
|
},
|
|
1131
1414
|
]
|
|
1132
1415
|
|
|
1133
1416
|
expect(() =>
|
|
1134
1417
|
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1135
|
-
).toThrow(
|
|
1136
|
-
expect(() =>
|
|
1137
|
-
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1138
|
-
).toThrow('LiveEdit documents do not support drafts or publishing')
|
|
1418
|
+
).toThrow(/Unpublishing is not supported for liveEdit or version/)
|
|
1139
1419
|
})
|
|
1140
1420
|
})
|
|
1141
1421
|
|
|
@@ -1150,17 +1430,745 @@ describe('processActions', () => {
|
|
|
1150
1430
|
type: 'document.discard',
|
|
1151
1431
|
documentType: 'liveArticle',
|
|
1152
1432
|
liveEdit: true,
|
|
1153
|
-
resource: {projectId: 'p', dataset: 'd'},
|
|
1154
1433
|
},
|
|
1155
1434
|
]
|
|
1156
1435
|
|
|
1157
1436
|
expect(() =>
|
|
1158
1437
|
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1159
|
-
).toThrow('Cannot discard changes for liveEdit document')
|
|
1438
|
+
).toThrow('Cannot discard changes for liveEdit document "live1"')
|
|
1439
|
+
|
|
1160
1440
|
expect(() =>
|
|
1161
1441
|
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1162
1442
|
).toThrow('LiveEdit documents do not support drafts')
|
|
1163
1443
|
})
|
|
1164
1444
|
})
|
|
1165
1445
|
})
|
|
1446
|
+
|
|
1447
|
+
describe('release perspective (version) documents', () => {
|
|
1448
|
+
const releaseName = 'test-release'
|
|
1449
|
+
const perspective = {releaseName}
|
|
1450
|
+
const versionId = `versions.${releaseName}.doc1`
|
|
1451
|
+
|
|
1452
|
+
describe('document.create', () => {
|
|
1453
|
+
it('should create a version document for a release perspective', () => {
|
|
1454
|
+
const base: DocumentSet = {}
|
|
1455
|
+
const working: DocumentSet = {}
|
|
1456
|
+
const actions: DocumentAction[] = [
|
|
1457
|
+
{
|
|
1458
|
+
documentId: versionId,
|
|
1459
|
+
type: 'document.create',
|
|
1460
|
+
documentType: 'article',
|
|
1461
|
+
perspective,
|
|
1462
|
+
},
|
|
1463
|
+
]
|
|
1464
|
+
const result = processActions({
|
|
1465
|
+
actions,
|
|
1466
|
+
transactionId,
|
|
1467
|
+
base,
|
|
1468
|
+
working,
|
|
1469
|
+
timestamp,
|
|
1470
|
+
grants: defaultGrants,
|
|
1471
|
+
})
|
|
1472
|
+
|
|
1473
|
+
const versionDoc = result.working[versionId]
|
|
1474
|
+
expect(versionDoc).toBeDefined()
|
|
1475
|
+
expect(versionDoc?._id).toBe(versionId)
|
|
1476
|
+
expect(versionDoc?._type).toBe('article')
|
|
1477
|
+
expect(versionDoc?._rev).toBe(transactionId)
|
|
1478
|
+
|
|
1479
|
+
expect(result.outgoingActions).toEqual([
|
|
1480
|
+
{
|
|
1481
|
+
actionType: 'sanity.action.document.version.create',
|
|
1482
|
+
publishedId: 'doc1',
|
|
1483
|
+
attributes: expect.objectContaining({_id: versionId, _type: 'article'}),
|
|
1484
|
+
},
|
|
1485
|
+
])
|
|
1486
|
+
})
|
|
1487
|
+
|
|
1488
|
+
it('should create a version document using the published document as a base', () => {
|
|
1489
|
+
const published = createDoc('doc1', 'Published Title')
|
|
1490
|
+
const base: DocumentSet = {doc1: published}
|
|
1491
|
+
const working: DocumentSet = {doc1: published}
|
|
1492
|
+
const actions: DocumentAction[] = [
|
|
1493
|
+
{
|
|
1494
|
+
documentId: versionId,
|
|
1495
|
+
type: 'document.create',
|
|
1496
|
+
documentType: 'article',
|
|
1497
|
+
perspective,
|
|
1498
|
+
},
|
|
1499
|
+
]
|
|
1500
|
+
const result = processActions({
|
|
1501
|
+
actions,
|
|
1502
|
+
transactionId,
|
|
1503
|
+
base,
|
|
1504
|
+
working,
|
|
1505
|
+
timestamp,
|
|
1506
|
+
grants: defaultGrants,
|
|
1507
|
+
})
|
|
1508
|
+
|
|
1509
|
+
const versionDoc = result.working[versionId]
|
|
1510
|
+
expect(versionDoc).toBeDefined()
|
|
1511
|
+
expect(versionDoc?._id).toBe(versionId)
|
|
1512
|
+
expect(versionDoc?.['title']).toBe('Published Title')
|
|
1513
|
+
})
|
|
1514
|
+
|
|
1515
|
+
it('should throw if a version document already exists', () => {
|
|
1516
|
+
const existingVersion = createDoc(versionId, 'Existing Version')
|
|
1517
|
+
const base: DocumentSet = {[versionId]: existingVersion}
|
|
1518
|
+
const working: DocumentSet = {[versionId]: existingVersion}
|
|
1519
|
+
const actions: DocumentAction[] = [
|
|
1520
|
+
{
|
|
1521
|
+
documentId: versionId,
|
|
1522
|
+
type: 'document.create',
|
|
1523
|
+
documentType: 'article',
|
|
1524
|
+
perspective,
|
|
1525
|
+
},
|
|
1526
|
+
]
|
|
1527
|
+
expect(() =>
|
|
1528
|
+
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1529
|
+
).toThrow(/release version.*already exists/i)
|
|
1530
|
+
})
|
|
1531
|
+
|
|
1532
|
+
it('should create a version document with initial values', () => {
|
|
1533
|
+
const base: DocumentSet = {}
|
|
1534
|
+
const working: DocumentSet = {}
|
|
1535
|
+
const actions: DocumentAction[] = [
|
|
1536
|
+
{
|
|
1537
|
+
documentId: versionId,
|
|
1538
|
+
type: 'document.create',
|
|
1539
|
+
documentType: 'article',
|
|
1540
|
+
perspective,
|
|
1541
|
+
initialValue: {title: 'Release Version Title', count: 7},
|
|
1542
|
+
},
|
|
1543
|
+
]
|
|
1544
|
+
const result = processActions({
|
|
1545
|
+
actions,
|
|
1546
|
+
transactionId,
|
|
1547
|
+
base,
|
|
1548
|
+
working,
|
|
1549
|
+
timestamp,
|
|
1550
|
+
grants: defaultGrants,
|
|
1551
|
+
})
|
|
1552
|
+
|
|
1553
|
+
const versionDoc = result.working[versionId]
|
|
1554
|
+
expect(versionDoc?.['title']).toBe('Release Version Title')
|
|
1555
|
+
expect(versionDoc?.['count']).toBe(7)
|
|
1556
|
+
})
|
|
1557
|
+
|
|
1558
|
+
it('should throw PermissionActionError if create permission is denied', () => {
|
|
1559
|
+
const base: DocumentSet = {}
|
|
1560
|
+
const working: DocumentSet = {}
|
|
1561
|
+
const actions: DocumentAction[] = [
|
|
1562
|
+
{
|
|
1563
|
+
documentId: versionId,
|
|
1564
|
+
type: 'document.create',
|
|
1565
|
+
documentType: 'article',
|
|
1566
|
+
perspective,
|
|
1567
|
+
},
|
|
1568
|
+
]
|
|
1569
|
+
const grants = {...defaultGrants, create: alwaysDeny}
|
|
1570
|
+
expect(() =>
|
|
1571
|
+
processActions({actions, transactionId, base, working, timestamp, grants}),
|
|
1572
|
+
).toThrow(/You do not have permission to create a release version/)
|
|
1573
|
+
})
|
|
1574
|
+
})
|
|
1575
|
+
|
|
1576
|
+
describe('document.edit', () => {
|
|
1577
|
+
it('should edit a version document directly', () => {
|
|
1578
|
+
const version = createDoc(versionId, 'Original Version Title')
|
|
1579
|
+
const base: DocumentSet = {[versionId]: version}
|
|
1580
|
+
const working: DocumentSet = {[versionId]: version}
|
|
1581
|
+
const actions: DocumentAction[] = [
|
|
1582
|
+
{
|
|
1583
|
+
documentId: versionId,
|
|
1584
|
+
type: 'document.edit',
|
|
1585
|
+
documentType: 'article',
|
|
1586
|
+
perspective,
|
|
1587
|
+
patches: [{set: {title: 'Updated Version Title'}}],
|
|
1588
|
+
},
|
|
1589
|
+
]
|
|
1590
|
+
const result = processActions({
|
|
1591
|
+
actions,
|
|
1592
|
+
transactionId,
|
|
1593
|
+
base,
|
|
1594
|
+
working,
|
|
1595
|
+
timestamp,
|
|
1596
|
+
grants: defaultGrants,
|
|
1597
|
+
})
|
|
1598
|
+
|
|
1599
|
+
const editedDoc = result.working[versionId]
|
|
1600
|
+
expect(editedDoc?.['title']).toBe('Updated Version Title')
|
|
1601
|
+
expect(editedDoc?._id).toBe(versionId)
|
|
1602
|
+
|
|
1603
|
+
// outgoing action should use versionId as draftId and the base publishedId
|
|
1604
|
+
expect(result.outgoingActions[0]).toMatchObject({
|
|
1605
|
+
actionType: 'sanity.action.document.edit',
|
|
1606
|
+
draftId: versionId,
|
|
1607
|
+
publishedId: 'doc1',
|
|
1608
|
+
})
|
|
1609
|
+
})
|
|
1610
|
+
|
|
1611
|
+
it('should throw if the version document does not exist', () => {
|
|
1612
|
+
const base: DocumentSet = {}
|
|
1613
|
+
const working: DocumentSet = {}
|
|
1614
|
+
const actions: DocumentAction[] = [
|
|
1615
|
+
{
|
|
1616
|
+
documentId: versionId,
|
|
1617
|
+
type: 'document.edit',
|
|
1618
|
+
documentType: 'article',
|
|
1619
|
+
perspective,
|
|
1620
|
+
patches: [{set: {title: 'Should Fail'}}],
|
|
1621
|
+
},
|
|
1622
|
+
]
|
|
1623
|
+
expect(() =>
|
|
1624
|
+
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1625
|
+
).toThrow(/does not exist in the release/)
|
|
1626
|
+
})
|
|
1627
|
+
|
|
1628
|
+
it('should throw PermissionActionError if update permission is denied', () => {
|
|
1629
|
+
const version = createDoc(versionId, 'Version Title')
|
|
1630
|
+
const base: DocumentSet = {[versionId]: version}
|
|
1631
|
+
const working: DocumentSet = {[versionId]: version}
|
|
1632
|
+
const actions: DocumentAction[] = [
|
|
1633
|
+
{
|
|
1634
|
+
documentId: versionId,
|
|
1635
|
+
type: 'document.edit',
|
|
1636
|
+
documentType: 'article',
|
|
1637
|
+
perspective,
|
|
1638
|
+
patches: [{set: {title: 'No Permission'}}],
|
|
1639
|
+
},
|
|
1640
|
+
]
|
|
1641
|
+
const grants = {...defaultGrants, update: alwaysDeny}
|
|
1642
|
+
expect(() =>
|
|
1643
|
+
processActions({actions, transactionId, base, working, timestamp, grants}),
|
|
1644
|
+
).toThrow(/You do not have permission to edit document/)
|
|
1645
|
+
})
|
|
1646
|
+
})
|
|
1647
|
+
|
|
1648
|
+
describe('document.discard', () => {
|
|
1649
|
+
it('should discard a version document', () => {
|
|
1650
|
+
const version = createDoc(versionId, 'Version Title')
|
|
1651
|
+
const base: DocumentSet = {[versionId]: version}
|
|
1652
|
+
const working: DocumentSet = {[versionId]: version}
|
|
1653
|
+
const actions: DocumentAction[] = [
|
|
1654
|
+
{
|
|
1655
|
+
documentId: versionId,
|
|
1656
|
+
type: 'document.discard',
|
|
1657
|
+
documentType: 'article',
|
|
1658
|
+
perspective,
|
|
1659
|
+
},
|
|
1660
|
+
]
|
|
1661
|
+
const result = processActions({
|
|
1662
|
+
actions,
|
|
1663
|
+
transactionId,
|
|
1664
|
+
base,
|
|
1665
|
+
working,
|
|
1666
|
+
timestamp,
|
|
1667
|
+
grants: defaultGrants,
|
|
1668
|
+
})
|
|
1669
|
+
|
|
1670
|
+
expect(result.working[versionId]).toBeNull()
|
|
1671
|
+
expect(result.outgoingActions).toEqual([
|
|
1672
|
+
{
|
|
1673
|
+
actionType: 'sanity.action.document.version.discard',
|
|
1674
|
+
versionId,
|
|
1675
|
+
},
|
|
1676
|
+
])
|
|
1677
|
+
})
|
|
1678
|
+
|
|
1679
|
+
it('should throw if there is no version document to discard', () => {
|
|
1680
|
+
const base: DocumentSet = {}
|
|
1681
|
+
const working: DocumentSet = {}
|
|
1682
|
+
const actions: DocumentAction[] = [
|
|
1683
|
+
{
|
|
1684
|
+
documentId: versionId,
|
|
1685
|
+
type: 'document.discard',
|
|
1686
|
+
documentType: 'article',
|
|
1687
|
+
perspective,
|
|
1688
|
+
},
|
|
1689
|
+
]
|
|
1690
|
+
expect(() =>
|
|
1691
|
+
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1692
|
+
).toThrow(/no draft or version available to discard/)
|
|
1693
|
+
})
|
|
1694
|
+
|
|
1695
|
+
it('should throw PermissionActionError if update permission is denied', () => {
|
|
1696
|
+
const version = createDoc(versionId, 'Version Title')
|
|
1697
|
+
const base: DocumentSet = {[versionId]: version}
|
|
1698
|
+
const working: DocumentSet = {[versionId]: version}
|
|
1699
|
+
const actions: DocumentAction[] = [
|
|
1700
|
+
{
|
|
1701
|
+
documentId: versionId,
|
|
1702
|
+
type: 'document.discard',
|
|
1703
|
+
documentType: 'article',
|
|
1704
|
+
perspective,
|
|
1705
|
+
},
|
|
1706
|
+
]
|
|
1707
|
+
const grants = {...defaultGrants, update: alwaysDeny}
|
|
1708
|
+
expect(() =>
|
|
1709
|
+
processActions({actions, transactionId, base, working, timestamp, grants}),
|
|
1710
|
+
).toThrow(/You do not have permission to discard changes/)
|
|
1711
|
+
})
|
|
1712
|
+
})
|
|
1713
|
+
|
|
1714
|
+
describe('document.publish', () => {
|
|
1715
|
+
it('should throw for release perspective documents', () => {
|
|
1716
|
+
const version = createDoc(versionId, 'Version Title')
|
|
1717
|
+
const base: DocumentSet = {[versionId]: version}
|
|
1718
|
+
const working: DocumentSet = {[versionId]: version}
|
|
1719
|
+
const actions: DocumentAction[] = [
|
|
1720
|
+
{
|
|
1721
|
+
documentId: versionId,
|
|
1722
|
+
type: 'document.publish',
|
|
1723
|
+
documentType: 'article',
|
|
1724
|
+
perspective,
|
|
1725
|
+
},
|
|
1726
|
+
]
|
|
1727
|
+
expect(() =>
|
|
1728
|
+
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1729
|
+
).toThrow(/Publishing is not supported for liveEdit or version/)
|
|
1730
|
+
})
|
|
1731
|
+
})
|
|
1732
|
+
|
|
1733
|
+
describe('document.delete', () => {
|
|
1734
|
+
it('should throw for release perspective documents', () => {
|
|
1735
|
+
const version = createDoc(versionId, 'Version Title')
|
|
1736
|
+
const base: DocumentSet = {[versionId]: version}
|
|
1737
|
+
const working: DocumentSet = {[versionId]: version}
|
|
1738
|
+
const actions: DocumentAction[] = [
|
|
1739
|
+
{
|
|
1740
|
+
documentId: versionId,
|
|
1741
|
+
type: 'document.delete',
|
|
1742
|
+
documentType: 'article',
|
|
1743
|
+
perspective,
|
|
1744
|
+
},
|
|
1745
|
+
]
|
|
1746
|
+
expect(() =>
|
|
1747
|
+
processActions({actions, transactionId, base, working, timestamp, grants: defaultGrants}),
|
|
1748
|
+
).toThrow(/Cannot delete a version document/)
|
|
1749
|
+
})
|
|
1750
|
+
})
|
|
1751
|
+
})
|
|
1752
|
+
|
|
1753
|
+
describe('release actions', () => {
|
|
1754
|
+
const releaseName = 'my-release'
|
|
1755
|
+
const releaseDocId = `_.releases.${releaseName}`
|
|
1756
|
+
|
|
1757
|
+
const createReleaseDoc = (overrides: Partial<SanityDocument> = {}): SanityDocument => ({
|
|
1758
|
+
_id: releaseDocId,
|
|
1759
|
+
_type: 'system.release',
|
|
1760
|
+
_createdAt: '2025-01-01T00:00:00.000Z',
|
|
1761
|
+
_updatedAt: '2025-01-01T00:00:00.000Z',
|
|
1762
|
+
_rev: 'initial',
|
|
1763
|
+
name: releaseName,
|
|
1764
|
+
state: 'active',
|
|
1765
|
+
metadata: {releaseType: 'undecided'},
|
|
1766
|
+
...overrides,
|
|
1767
|
+
})
|
|
1768
|
+
|
|
1769
|
+
describe('release.create', () => {
|
|
1770
|
+
it('inserts an optimistic release doc and emits a release.create action', () => {
|
|
1771
|
+
const base: DocumentSet = {}
|
|
1772
|
+
const working: DocumentSet = {}
|
|
1773
|
+
const actions: Action[] = [
|
|
1774
|
+
{
|
|
1775
|
+
type: 'release.create',
|
|
1776
|
+
releaseId: releaseName,
|
|
1777
|
+
metadata: {title: 'My release', releaseType: 'asap'},
|
|
1778
|
+
},
|
|
1779
|
+
]
|
|
1780
|
+
|
|
1781
|
+
const result = processActions({
|
|
1782
|
+
actions,
|
|
1783
|
+
transactionId,
|
|
1784
|
+
base,
|
|
1785
|
+
working,
|
|
1786
|
+
timestamp,
|
|
1787
|
+
grants: defaultGrants,
|
|
1788
|
+
})
|
|
1789
|
+
|
|
1790
|
+
const doc = result.working[releaseDocId]
|
|
1791
|
+
expect(doc).toBeDefined()
|
|
1792
|
+
expect(doc?._type).toBe('system.release')
|
|
1793
|
+
expect(doc?.['name']).toBe(releaseName)
|
|
1794
|
+
expect(doc?.['state']).toBe('active')
|
|
1795
|
+
expect(doc?.['metadata']).toEqual({title: 'My release', releaseType: 'asap'})
|
|
1796
|
+
|
|
1797
|
+
expect(result.outgoingActions).toEqual([
|
|
1798
|
+
{
|
|
1799
|
+
actionType: 'sanity.action.release.create',
|
|
1800
|
+
releaseId: releaseName,
|
|
1801
|
+
metadata: {title: 'My release', releaseType: 'asap'},
|
|
1802
|
+
},
|
|
1803
|
+
])
|
|
1804
|
+
})
|
|
1805
|
+
|
|
1806
|
+
it('throws if the release already exists in base or working', () => {
|
|
1807
|
+
const existing = createReleaseDoc()
|
|
1808
|
+
expect(() =>
|
|
1809
|
+
processActions({
|
|
1810
|
+
actions: [
|
|
1811
|
+
{
|
|
1812
|
+
type: 'release.create',
|
|
1813
|
+
releaseId: releaseName,
|
|
1814
|
+
metadata: {releaseType: 'undecided'},
|
|
1815
|
+
},
|
|
1816
|
+
],
|
|
1817
|
+
transactionId,
|
|
1818
|
+
base: {[releaseDocId]: existing},
|
|
1819
|
+
working: {[releaseDocId]: existing},
|
|
1820
|
+
timestamp,
|
|
1821
|
+
grants: defaultGrants,
|
|
1822
|
+
}),
|
|
1823
|
+
).toThrow(/already exists/)
|
|
1824
|
+
})
|
|
1825
|
+
|
|
1826
|
+
it('throws PermissionActionError when the create grant is denied', () => {
|
|
1827
|
+
expect(() =>
|
|
1828
|
+
processActions({
|
|
1829
|
+
actions: [
|
|
1830
|
+
{
|
|
1831
|
+
type: 'release.create',
|
|
1832
|
+
releaseId: releaseName,
|
|
1833
|
+
metadata: {releaseType: 'undecided'},
|
|
1834
|
+
},
|
|
1835
|
+
],
|
|
1836
|
+
transactionId,
|
|
1837
|
+
base: {},
|
|
1838
|
+
working: {},
|
|
1839
|
+
timestamp,
|
|
1840
|
+
grants: {...defaultGrants, create: alwaysDeny},
|
|
1841
|
+
}),
|
|
1842
|
+
).toThrow(/permission to create release/)
|
|
1843
|
+
})
|
|
1844
|
+
})
|
|
1845
|
+
|
|
1846
|
+
describe('release.edit', () => {
|
|
1847
|
+
it('applies the patch optimistically and forwards the original patch to the outgoing action', () => {
|
|
1848
|
+
const existing = createReleaseDoc({
|
|
1849
|
+
metadata: {releaseType: 'undecided'},
|
|
1850
|
+
} as Partial<SanityDocument>)
|
|
1851
|
+
const patch = {set: {'metadata.title': 'Updated title'}}
|
|
1852
|
+
|
|
1853
|
+
const result = processActions({
|
|
1854
|
+
actions: [{type: 'release.edit', releaseId: releaseName, patch}],
|
|
1855
|
+
transactionId,
|
|
1856
|
+
base: {[releaseDocId]: existing},
|
|
1857
|
+
working: {[releaseDocId]: existing},
|
|
1858
|
+
timestamp,
|
|
1859
|
+
grants: defaultGrants,
|
|
1860
|
+
})
|
|
1861
|
+
|
|
1862
|
+
const updated = result.working[releaseDocId] as SanityDocument & {
|
|
1863
|
+
metadata: {title?: string; releaseType: string}
|
|
1864
|
+
}
|
|
1865
|
+
expect(updated.metadata.title).toBe('Updated title')
|
|
1866
|
+
// releaseType wasn't unset
|
|
1867
|
+
expect(updated.metadata.releaseType).toBe('undecided')
|
|
1868
|
+
|
|
1869
|
+
expect(result.outgoingActions).toEqual([
|
|
1870
|
+
{
|
|
1871
|
+
actionType: 'sanity.action.release.edit',
|
|
1872
|
+
releaseId: releaseName,
|
|
1873
|
+
patch,
|
|
1874
|
+
},
|
|
1875
|
+
])
|
|
1876
|
+
})
|
|
1877
|
+
|
|
1878
|
+
it('throws when editing a release that does not exist', () => {
|
|
1879
|
+
expect(() =>
|
|
1880
|
+
processActions({
|
|
1881
|
+
actions: [
|
|
1882
|
+
{
|
|
1883
|
+
type: 'release.edit',
|
|
1884
|
+
releaseId: releaseName,
|
|
1885
|
+
patch: {set: {'metadata.title': 'x'}},
|
|
1886
|
+
},
|
|
1887
|
+
],
|
|
1888
|
+
transactionId,
|
|
1889
|
+
base: {},
|
|
1890
|
+
working: {},
|
|
1891
|
+
timestamp,
|
|
1892
|
+
grants: defaultGrants,
|
|
1893
|
+
}),
|
|
1894
|
+
).toThrow(/does not exist/)
|
|
1895
|
+
})
|
|
1896
|
+
|
|
1897
|
+
it('throws PermissionActionError when the working release fails the update grant', () => {
|
|
1898
|
+
const existing = createReleaseDoc()
|
|
1899
|
+
expect(() =>
|
|
1900
|
+
processActions({
|
|
1901
|
+
actions: [
|
|
1902
|
+
{
|
|
1903
|
+
type: 'release.edit',
|
|
1904
|
+
releaseId: releaseName,
|
|
1905
|
+
patch: {set: {'metadata.title': 'x'}},
|
|
1906
|
+
},
|
|
1907
|
+
],
|
|
1908
|
+
transactionId,
|
|
1909
|
+
base: {[releaseDocId]: existing},
|
|
1910
|
+
working: {[releaseDocId]: existing},
|
|
1911
|
+
timestamp,
|
|
1912
|
+
grants: {...defaultGrants, update: alwaysDeny},
|
|
1913
|
+
}),
|
|
1914
|
+
).toThrow(/permission to edit release/)
|
|
1915
|
+
})
|
|
1916
|
+
})
|
|
1917
|
+
|
|
1918
|
+
describe('fire-and-forget actions', () => {
|
|
1919
|
+
const existing = createReleaseDoc()
|
|
1920
|
+
const cases: Array<{
|
|
1921
|
+
name: string
|
|
1922
|
+
action: Action
|
|
1923
|
+
expectedOutgoing: Record<string, unknown>
|
|
1924
|
+
}> = [
|
|
1925
|
+
{
|
|
1926
|
+
name: 'release.publish',
|
|
1927
|
+
action: {type: 'release.publish', releaseId: releaseName},
|
|
1928
|
+
expectedOutgoing: {actionType: 'sanity.action.release.publish', releaseId: releaseName},
|
|
1929
|
+
},
|
|
1930
|
+
{
|
|
1931
|
+
name: 'release.schedule',
|
|
1932
|
+
action: {
|
|
1933
|
+
type: 'release.schedule',
|
|
1934
|
+
releaseId: releaseName,
|
|
1935
|
+
publishAt: '2026-01-01T00:00:00.000Z',
|
|
1936
|
+
},
|
|
1937
|
+
expectedOutgoing: {
|
|
1938
|
+
actionType: 'sanity.action.release.schedule',
|
|
1939
|
+
releaseId: releaseName,
|
|
1940
|
+
publishAt: '2026-01-01T00:00:00.000Z',
|
|
1941
|
+
},
|
|
1942
|
+
},
|
|
1943
|
+
{
|
|
1944
|
+
name: 'release.unschedule',
|
|
1945
|
+
action: {type: 'release.unschedule', releaseId: releaseName},
|
|
1946
|
+
expectedOutgoing: {
|
|
1947
|
+
actionType: 'sanity.action.release.unschedule',
|
|
1948
|
+
releaseId: releaseName,
|
|
1949
|
+
},
|
|
1950
|
+
},
|
|
1951
|
+
{
|
|
1952
|
+
name: 'release.archive',
|
|
1953
|
+
action: {type: 'release.archive', releaseId: releaseName},
|
|
1954
|
+
expectedOutgoing: {actionType: 'sanity.action.release.archive', releaseId: releaseName},
|
|
1955
|
+
},
|
|
1956
|
+
{
|
|
1957
|
+
name: 'release.unarchive',
|
|
1958
|
+
action: {type: 'release.unarchive', releaseId: releaseName},
|
|
1959
|
+
expectedOutgoing: {actionType: 'sanity.action.release.unarchive', releaseId: releaseName},
|
|
1960
|
+
},
|
|
1961
|
+
]
|
|
1962
|
+
|
|
1963
|
+
it.each(cases)(
|
|
1964
|
+
'$name does not mutate local state and emits the matching outgoing action',
|
|
1965
|
+
({action, expectedOutgoing}) => {
|
|
1966
|
+
const base: DocumentSet = {[releaseDocId]: existing}
|
|
1967
|
+
const working: DocumentSet = {[releaseDocId]: existing}
|
|
1968
|
+
|
|
1969
|
+
const result = processActions({
|
|
1970
|
+
actions: [action],
|
|
1971
|
+
transactionId,
|
|
1972
|
+
base,
|
|
1973
|
+
working,
|
|
1974
|
+
timestamp,
|
|
1975
|
+
grants: defaultGrants,
|
|
1976
|
+
})
|
|
1977
|
+
|
|
1978
|
+
// working and base reference the same doc instance — no optimistic
|
|
1979
|
+
// mutation was applied
|
|
1980
|
+
expect(result.working[releaseDocId]).toBe(existing)
|
|
1981
|
+
expect(result.outgoingActions).toEqual([expectedOutgoing])
|
|
1982
|
+
expect(result.outgoingMutations).toEqual([])
|
|
1983
|
+
},
|
|
1984
|
+
)
|
|
1985
|
+
|
|
1986
|
+
it.each(cases)('$name throws if the release does not exist', ({action}) => {
|
|
1987
|
+
expect(() =>
|
|
1988
|
+
processActions({
|
|
1989
|
+
actions: [action],
|
|
1990
|
+
transactionId,
|
|
1991
|
+
base: {},
|
|
1992
|
+
working: {},
|
|
1993
|
+
timestamp,
|
|
1994
|
+
grants: defaultGrants,
|
|
1995
|
+
}),
|
|
1996
|
+
).toThrow(ActionError)
|
|
1997
|
+
})
|
|
1998
|
+
|
|
1999
|
+
it.each(cases)(
|
|
2000
|
+
'$name throws PermissionActionError when update grant is denied',
|
|
2001
|
+
({action}) => {
|
|
2002
|
+
const base: DocumentSet = {[releaseDocId]: existing}
|
|
2003
|
+
const working: DocumentSet = {[releaseDocId]: existing}
|
|
2004
|
+
expect(() =>
|
|
2005
|
+
processActions({
|
|
2006
|
+
actions: [action],
|
|
2007
|
+
transactionId,
|
|
2008
|
+
base,
|
|
2009
|
+
working,
|
|
2010
|
+
timestamp,
|
|
2011
|
+
grants: {...defaultGrants, update: alwaysDeny},
|
|
2012
|
+
}),
|
|
2013
|
+
).toThrow(/permission to/)
|
|
2014
|
+
},
|
|
2015
|
+
)
|
|
2016
|
+
})
|
|
2017
|
+
|
|
2018
|
+
describe('release.schedule publishAt validation', () => {
|
|
2019
|
+
const existing = createReleaseDoc()
|
|
2020
|
+
|
|
2021
|
+
it.each(['not-a-date', '', '2026-13-40T99:99:99Z'])(
|
|
2022
|
+
'throws ActionError when publishAt is not a valid timestamp (%s)',
|
|
2023
|
+
(publishAt) => {
|
|
2024
|
+
expect(() =>
|
|
2025
|
+
processActions({
|
|
2026
|
+
actions: [{type: 'release.schedule', releaseId: releaseName, publishAt}],
|
|
2027
|
+
transactionId,
|
|
2028
|
+
base: {[releaseDocId]: existing},
|
|
2029
|
+
working: {[releaseDocId]: existing},
|
|
2030
|
+
timestamp,
|
|
2031
|
+
grants: defaultGrants,
|
|
2032
|
+
}),
|
|
2033
|
+
).toThrow(/must be a valid ISO 8601 timestamp/)
|
|
2034
|
+
},
|
|
2035
|
+
)
|
|
2036
|
+
})
|
|
2037
|
+
|
|
2038
|
+
describe('release.delete', () => {
|
|
2039
|
+
it.each(['archived', 'published'] as const)(
|
|
2040
|
+
'optimistically removes the local release when state is %s',
|
|
2041
|
+
(state) => {
|
|
2042
|
+
const existing = createReleaseDoc({state} as Partial<SanityDocument>)
|
|
2043
|
+
const result = processActions({
|
|
2044
|
+
actions: [{type: 'release.delete', releaseId: releaseName}],
|
|
2045
|
+
transactionId,
|
|
2046
|
+
base: {[releaseDocId]: existing},
|
|
2047
|
+
working: {[releaseDocId]: existing},
|
|
2048
|
+
timestamp,
|
|
2049
|
+
grants: defaultGrants,
|
|
2050
|
+
})
|
|
2051
|
+
|
|
2052
|
+
// processMutations sets deleted entries to null
|
|
2053
|
+
expect(result.working[releaseDocId]).toBeNull()
|
|
2054
|
+
expect(result.outgoingMutations).toEqual([{delete: {id: releaseDocId}}])
|
|
2055
|
+
expect(result.outgoingActions).toEqual([
|
|
2056
|
+
{actionType: 'sanity.action.release.delete', releaseId: releaseName},
|
|
2057
|
+
])
|
|
2058
|
+
},
|
|
2059
|
+
)
|
|
2060
|
+
|
|
2061
|
+
it.each(['active', 'scheduled', 'archiving', 'publishing'] as const)(
|
|
2062
|
+
'throws when state is %s (server only accepts archived/published)',
|
|
2063
|
+
(state) => {
|
|
2064
|
+
const existing = createReleaseDoc({state} as Partial<SanityDocument>)
|
|
2065
|
+
expect(() =>
|
|
2066
|
+
processActions({
|
|
2067
|
+
actions: [{type: 'release.delete', releaseId: releaseName}],
|
|
2068
|
+
transactionId,
|
|
2069
|
+
base: {[releaseDocId]: existing},
|
|
2070
|
+
working: {[releaseDocId]: existing},
|
|
2071
|
+
timestamp,
|
|
2072
|
+
grants: defaultGrants,
|
|
2073
|
+
}),
|
|
2074
|
+
).toThrow(/Archive it first/)
|
|
2075
|
+
},
|
|
2076
|
+
)
|
|
2077
|
+
|
|
2078
|
+
it('throws if the release does not exist', () => {
|
|
2079
|
+
expect(() =>
|
|
2080
|
+
processActions({
|
|
2081
|
+
actions: [{type: 'release.delete', releaseId: releaseName}],
|
|
2082
|
+
transactionId,
|
|
2083
|
+
base: {},
|
|
2084
|
+
working: {},
|
|
2085
|
+
timestamp,
|
|
2086
|
+
grants: defaultGrants,
|
|
2087
|
+
}),
|
|
2088
|
+
).toThrow(/does not exist/)
|
|
2089
|
+
})
|
|
2090
|
+
|
|
2091
|
+
it('throws PermissionActionError when the update grant is denied', () => {
|
|
2092
|
+
const existing = createReleaseDoc({state: 'archived'} as Partial<SanityDocument>)
|
|
2093
|
+
expect(() =>
|
|
2094
|
+
processActions({
|
|
2095
|
+
actions: [{type: 'release.delete', releaseId: releaseName}],
|
|
2096
|
+
transactionId,
|
|
2097
|
+
base: {[releaseDocId]: existing},
|
|
2098
|
+
working: {[releaseDocId]: existing},
|
|
2099
|
+
timestamp,
|
|
2100
|
+
grants: {...defaultGrants, update: alwaysDeny},
|
|
2101
|
+
}),
|
|
2102
|
+
).toThrow(/permission to delete release/)
|
|
2103
|
+
})
|
|
2104
|
+
})
|
|
2105
|
+
|
|
2106
|
+
describe('mixed with liveEdit document actions', () => {
|
|
2107
|
+
const liveEditDocId = 'live-edit-doc'
|
|
2108
|
+
const liveEditDoc = {
|
|
2109
|
+
_id: liveEditDocId,
|
|
2110
|
+
_type: 'liveEditType',
|
|
2111
|
+
_createdAt: '2025-01-01T00:00:00.000Z',
|
|
2112
|
+
_updatedAt: '2025-01-01T00:00:00.000Z',
|
|
2113
|
+
_rev: 'initial',
|
|
2114
|
+
}
|
|
2115
|
+
const liveEditAction = {
|
|
2116
|
+
type: 'document.edit',
|
|
2117
|
+
documentId: liveEditDocId,
|
|
2118
|
+
documentType: 'liveEditType',
|
|
2119
|
+
liveEdit: true,
|
|
2120
|
+
patches: [{set: {title: 'x'}}],
|
|
2121
|
+
} as const
|
|
2122
|
+
|
|
2123
|
+
it('throws ActionError when a transaction mixes a release action with a liveEdit document action', () => {
|
|
2124
|
+
expect(() =>
|
|
2125
|
+
processActions({
|
|
2126
|
+
actions: [
|
|
2127
|
+
{
|
|
2128
|
+
type: 'release.create',
|
|
2129
|
+
releaseId: releaseName,
|
|
2130
|
+
metadata: {releaseType: 'undecided'},
|
|
2131
|
+
},
|
|
2132
|
+
liveEditAction as unknown as Action,
|
|
2133
|
+
],
|
|
2134
|
+
transactionId,
|
|
2135
|
+
base: {[liveEditDocId]: liveEditDoc},
|
|
2136
|
+
working: {[liveEditDocId]: liveEditDoc},
|
|
2137
|
+
timestamp,
|
|
2138
|
+
grants: defaultGrants,
|
|
2139
|
+
}),
|
|
2140
|
+
).toThrow(/Cannot combine liveEdit document actions with other actions/)
|
|
2141
|
+
})
|
|
2142
|
+
|
|
2143
|
+
it('throws ActionError when a transaction mixes a non-liveEdit document action with a liveEdit document action', () => {
|
|
2144
|
+
const otherDocId = 'other-doc'
|
|
2145
|
+
const otherDoc = {
|
|
2146
|
+
_id: otherDocId,
|
|
2147
|
+
_type: 'someType',
|
|
2148
|
+
_createdAt: '2025-01-01T00:00:00.000Z',
|
|
2149
|
+
_updatedAt: '2025-01-01T00:00:00.000Z',
|
|
2150
|
+
_rev: 'initial',
|
|
2151
|
+
} as SanityDocument
|
|
2152
|
+
|
|
2153
|
+
expect(() =>
|
|
2154
|
+
processActions({
|
|
2155
|
+
actions: [
|
|
2156
|
+
{
|
|
2157
|
+
type: 'document.edit',
|
|
2158
|
+
documentId: otherDocId,
|
|
2159
|
+
documentType: 'someType',
|
|
2160
|
+
patches: [{set: {title: 'y'}}],
|
|
2161
|
+
},
|
|
2162
|
+
liveEditAction as unknown as Action,
|
|
2163
|
+
],
|
|
2164
|
+
transactionId,
|
|
2165
|
+
base: {[liveEditDocId]: liveEditDoc, [otherDocId]: otherDoc},
|
|
2166
|
+
working: {[liveEditDocId]: liveEditDoc, [otherDocId]: otherDoc},
|
|
2167
|
+
timestamp,
|
|
2168
|
+
grants: defaultGrants,
|
|
2169
|
+
}),
|
|
2170
|
+
).toThrow(/Cannot combine liveEdit document actions with other actions/)
|
|
2171
|
+
})
|
|
2172
|
+
})
|
|
2173
|
+
})
|
|
1166
2174
|
})
|