diff --git a/package-lock.json b/package-lock.json index 2f512e7e..542f9770 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", "@sanity/pkg-utils": "^3.3.7", + "@types/json-diff": "^1.0.3", "@types/node": "^20.8.8", "@typescript-eslint/eslint-plugin": "^6.18.0", "@typescript-eslint/parser": "^6.18.0", @@ -30,6 +31,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "faucet": "^0.0.4", "happy-dom": "^12.10.3", + "json-diff": "^1.0.6", "ls-engines": "^0.9.1", "nock": "^13.4.0", "prettier": "^3.1.1", @@ -2372,6 +2374,15 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@ewoudenberg/difflib": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@ewoudenberg/difflib/-/difflib-0.1.0.tgz", + "integrity": "sha512-OU5P5mJyD3OoWYMWY+yIgwvgNS9cFAU10f+DDuvtogcWQOoJIsQ4Hy2McSfUfhKjq8L0FuWVb4Rt7kgA+XK86A==", + "dev": true, + "dependencies": { + "heap": ">= 0.2.0" + } + }, "node_modules/@fastify/busboy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", @@ -4182,6 +4193,12 @@ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==", "dev": true }, + "node_modules/@types/json-diff": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/json-diff/-/json-diff-1.0.3.tgz", + "integrity": "sha512-Qvxm8fpRMv/1zZR3sQWImeRK2mBYJji20xF51Fq9Gt//Ed18u0x6/FNLogLS1xhfUWTEmDyqveJqn95ltB6Kvw==", + "dev": true + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -5968,6 +5985,18 @@ "ignored": "bin/ignored" } }, + "node_modules/dreamopt": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/dreamopt/-/dreamopt-0.8.0.tgz", + "integrity": "sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==", + "dev": true, + "dependencies": { + "wordwrap": ">=0.0.2" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -8230,6 +8259,12 @@ "node": ">= 0.4" } }, + "node_modules/heap": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", + "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", + "dev": true + }, "node_modules/hosted-git-info": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-6.1.1.tgz", @@ -9051,6 +9086,32 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-diff": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/json-diff/-/json-diff-1.0.6.tgz", + "integrity": "sha512-tcFIPRdlc35YkYdGxcamJjllUhXWv4n2rK9oJ2RsAzV4FBkuV4ojKEDgcZ+kpKxDmJKv+PFK65+1tVVOnSeEqA==", + "dev": true, + "dependencies": { + "@ewoudenberg/difflib": "0.1.0", + "colors": "^1.4.0", + "dreamopt": "~0.8.0" + }, + "bin": { + "json-diff": "bin/json-diff.js" + }, + "engines": { + "node": "*" + } + }, + "node_modules/json-diff/node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, "node_modules/json-file-plus": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/json-file-plus/-/json-file-plus-3.3.1.tgz", @@ -13853,6 +13914,12 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", diff --git a/package.json b/package.json index 26b7f6e8..5f35dac1 100644 --- a/package.json +++ b/package.json @@ -139,6 +139,7 @@ "@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-node-resolve": "^15.2.3", "@sanity/pkg-utils": "^3.3.7", + "@types/json-diff": "^1.0.3", "@types/node": "^20.8.8", "@typescript-eslint/eslint-plugin": "^6.18.0", "@typescript-eslint/parser": "^6.18.0", @@ -149,6 +150,7 @@ "eslint-plugin-simple-import-sort": "^10.0.0", "faucet": "^0.0.4", "happy-dom": "^12.10.3", + "json-diff": "^1.0.6", "ls-engines": "^0.9.1", "nock": "^13.4.0", "prettier": "^3.1.1", diff --git a/src/csm/applySourceDocuments.ts b/src/csm/applySourceDocuments.ts index 81bba3a4..d21391bd 100644 --- a/src/csm/applySourceDocuments.ts +++ b/src/csm/applySourceDocuments.ts @@ -1,9 +1,11 @@ +import {DRAFTS_PREFIX, getPublishedId} from './getPublishedId' import {parseJsonPath} from './jsonPath' import {resolveMapping} from './resolveMapping' import * as paths from './studioPath' import type { Any, ApplySourceDocumentsUpdateFunction, + ClientPerspective, ContentSourceMap, ContentSourceMapDocuments, Path, @@ -25,9 +27,14 @@ export function applySourceDocuments( sourceDocument: ContentSourceMapDocuments[number], ) => SanityDocument | undefined, updateFn: ApplySourceDocumentsUpdateFunction = defaultUpdateFunction, + perspective: ClientPerspective = 'raw', ): Result { if (!resultSourceMap) return result + if (perspective !== 'published' && perspective !== 'raw' && perspective !== 'previewDrafts') { + throw new Error(`Unknown perspective "${perspective}"`) + } + return walkMap(JSON.parse(JSON.stringify(result)), (value, path) => { const resolveMappingResult = resolveMapping(path, resultSourceMap) if (!resolveMappingResult) { @@ -48,14 +55,45 @@ export function applySourceDocuments( const sourcePath = resultSourceMap.paths[mapping.source.path] if (sourceDocument) { - const cachedDocument = getCachedDocument(sourceDocument) + const parsedPath = parseJsonPath(sourcePath + pathSuffix) + const stringifiedPath = paths.toString(parsedPath as Path) + + // The _id is sometimes used used as `key` in lists, and should not be changed optimistically + if (stringifiedPath === '_id') { + return value + } + + let cachedDocument: SanityDocument | undefined + if (perspective === 'previewDrafts') { + cachedDocument = getCachedDocument( + sourceDocument._id.startsWith(DRAFTS_PREFIX) + ? sourceDocument + : {...sourceDocument, _id: `${DRAFTS_PREFIX}.${sourceDocument._id}}`}, + ) + if (!cachedDocument) { + cachedDocument = getCachedDocument( + sourceDocument._id.startsWith(DRAFTS_PREFIX) + ? {...sourceDocument, _id: getPublishedId(sourceDocument._id)} + : sourceDocument, + ) + } + if (cachedDocument) { + cachedDocument = { + ...cachedDocument, + _id: getPublishedId(sourceDocument._id), + _originalId: sourceDocument._id, + } + } + } else { + cachedDocument = getCachedDocument(sourceDocument) + } + if (!cachedDocument) { return value } - const parsedPath = parseJsonPath(sourcePath + pathSuffix) const changedValue = cachedDocument - ? paths.get(cachedDocument, paths.toString(parsedPath as Path), value) + ? paths.get(cachedDocument, stringifiedPath, value) : value return value === changedValue ? value diff --git a/src/csm/getPublishedId.ts b/src/csm/getPublishedId.ts index da68f040..d57e4f63 100644 --- a/src/csm/getPublishedId.ts +++ b/src/csm/getPublishedId.ts @@ -1,4 +1,4 @@ -const DRAFTS_PREFIX = 'drafts.' +export const DRAFTS_PREFIX = 'drafts.' /** @internal */ export function getPublishedId(id: string): string { diff --git a/src/csm/types.ts b/src/csm/types.ts index f308fca8..b781aacc 100644 --- a/src/csm/types.ts +++ b/src/csm/types.ts @@ -3,6 +3,7 @@ import {Path} from './studioPath' export type { Any, + ClientPerspective, ContentSourceMap, ContentSourceMapDocument, ContentSourceMapDocumentBase, diff --git a/test/csm/applySourceDocuments.test.ts b/test/csm/applySourceDocuments.test.ts index ba32ab22..321193fb 100644 --- a/test/csm/applySourceDocuments.test.ts +++ b/test/csm/applySourceDocuments.test.ts @@ -1,4 +1,11 @@ -import {applySourceDocuments, type ContentSourceMap} from '@sanity/client/csm' +import { + applySourceDocuments, + type ContentSourceMap, + type ContentSourceMapDocuments, + getPublishedId, + type SanityDocument, +} from '@sanity/client/csm' +import {diffString} from 'json-diff' import {describe, expect, test} from 'vitest' describe('complex queries', () => { @@ -406,3 +413,1236 @@ describe('simple queries', () => { expect(optimisticResult[0].media.alt).toBe(draft.media[0].alt) }) }) + +describe('handling perspectives', () => { + const dataset = [ + { + _createdAt: '2024-01-09T11:10:46Z', + _rev: 'D3N5P3XyAZ16DNFKjiRI0q', + _type: 'author', + name: 'George Martin (published)', + _id: '294709c3-710d-4dc6-8f6f-f36c4786611a', + _updatedAt: '2024-01-09T15:58:47Z', + }, + { + author: { + _ref: 'd7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74', + _type: 'reference', + }, + _createdAt: '2024-01-09T13:46:42Z', + _rev: '4CNIu3sfkQOfiJtCaJf66J', + _type: 'book', + _id: '2c1de490-e7ed-413c-8d23-163d4432bb63', + title: 'Good Omens (published)', + _updatedAt: '2024-01-09T16:00:04Z', + }, + { + title: 'Fire & Ice (published)', + _updatedAt: '2024-01-09T15:59:33Z', + author: { + _ref: '294709c3-710d-4dc6-8f6f-f36c4786611a', + _type: 'reference', + }, + _createdAt: '2024-01-09T10:58:07Z', + _rev: '4CNIu3sfkQOfiJtCaJf4ix', + _type: 'book', + _id: '8826fb2c-6152-46c0-8d19-079fcd75b438', + }, + { + _rev: '4CNIu3sfkQOfiJtCaJf0OF', + _type: 'author', + name: 'Terry Pratchett (published)', + _id: 'd7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74', + _updatedAt: '2024-01-09T15:58:08Z', + _createdAt: '2024-01-09T13:45:33Z', + }, + { + _type: 'author', + name: 'George R.R. Martin (draft)', + _id: 'drafts.294709c3-710d-4dc6-8f6f-f36c4786611a', + _updatedAt: '2024-01-09T15:58:54Z', + _createdAt: '2024-01-09T11:10:46Z', + _rev: '8b3babb0-2e94-4483-a975-e1c0530631ab', + }, + { + author: { + _strengthenOnPublish: { + type: 'author', + }, + _weak: true, + _ref: 'de2baea7-4df7-4eb0-841e-db20103279fc', + _type: 'reference', + }, + _createdAt: '2024-01-09T10:58:07Z', + _rev: '3480a3e1-d8b4-49c9-8da8-70b85489dd05', + _type: 'book', + _id: 'drafts.8826fb2c-6152-46c0-8d19-079fcd75b438', + title: 'It (draft)', + _updatedAt: '2024-01-09T15:59:48Z', + }, + { + _updatedAt: '2024-01-09T16:00:13Z', + author: { + _ref: '294709c3-710d-4dc6-8f6f-f36c4786611a', + _type: 'reference', + }, + _createdAt: '2024-01-09T13:51:54Z', + _rev: 'c891041e-12d9-45f1-b487-ba41283ed918', + _type: 'book', + _id: 'drafts.8b671177-113d-4249-ae23-6b50dc017e9e', + title: 'The Winds of Winter (draft)', + }, + { + _updatedAt: '2024-01-09T15:58:16Z', + _createdAt: '2024-01-09T11:17:15Z', + _rev: 'bfa045fa-4936-4eb3-978a-bdd9316ba837', + _type: 'author', + name: 'Stephen King (draft)', + _id: 'drafts.de2baea7-4df7-4eb0-841e-db20103279fc', + }, + ] as const satisfies SanityDocument[] + const getCachedDocument: ( + sourceDocument: ContentSourceMapDocuments[number], + ) => SanityDocument | undefined = (sourceDocument) => { + return dataset.find((doc) => doc._id === sourceDocument._id) + } + const simulateAuthorMutations: typeof getCachedDocument = (sourceDocument) => { + const cachedDocument = getCachedDocument(sourceDocument) + + // Pretending Terry Pratchett is unpublished + let id = 'd7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74' + if (sourceDocument._id === id) { + return undefined + } + if (getPublishedId(sourceDocument._id) === id) { + return { + ...getCachedDocument({...sourceDocument, _id: getPublishedId(sourceDocument._id)}), + _id: sourceDocument._id, + name: 'Terry Pratchett (draft)', + } as SanityDocument + } + + // Published Stephen King + id = 'de2baea7-4df7-4eb0-841e-db20103279fc' + if (sourceDocument._id === id) { + return { + ...getCachedDocument({...sourceDocument, _id: `drafts.${sourceDocument._id}`}), + _id: id, + name: 'Stephen King (published)', + } as SanityDocument + } + if (getPublishedId(sourceDocument._id) === id) { + return undefined + } + + // Published IT + id = '8826fb2c-6152-46c0-8d19-079fcd75b438' + if (sourceDocument._id === id) { + return undefined + } + if (getPublishedId(sourceDocument._id) === id) { + return { + ...getCachedDocument({...sourceDocument, _id: `drafts.${sourceDocument._id}`}), + _id: id, + title: 'It (published)', + } as SanityDocument + } + + // Edited Good Omens + id = '2c1de490-e7ed-413c-8d23-163d4432bb63' + if (sourceDocument._id === id) { + return { + ...getCachedDocument(sourceDocument), + title: 'Good Omens (changed)', + } as SanityDocument + } + + return cachedDocument + } + test('perspective: raw', () => { + const mock = { + query: '*[_type == "book"]{\n _id,_originalId,title,author->{_id,_originalId,name}\n}', + result: [ + { + _id: '2c1de490-e7ed-413c-8d23-163d4432bb63', + _originalId: null, + title: 'Good Omens (published)', + author: { + _id: 'd7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74', + _originalId: null, + name: 'Terry Pratchett (published)', + }, + }, + { + _id: '8826fb2c-6152-46c0-8d19-079fcd75b438', + _originalId: null, + title: 'Fire & Ice (published)', + author: { + _id: '294709c3-710d-4dc6-8f6f-f36c4786611a', + _originalId: null, + name: 'George Martin (published)', + }, + }, + { + _id: 'drafts.8826fb2c-6152-46c0-8d19-079fcd75b438', + _originalId: null, + title: 'It (draft)', + author: null, + }, + { + _id: 'drafts.8b671177-113d-4249-ae23-6b50dc017e9e', + _originalId: null, + title: 'The Winds of Winter (draft)', + author: { + _id: '294709c3-710d-4dc6-8f6f-f36c4786611a', + _originalId: null, + name: 'George Martin (published)', + }, + }, + ], + resultSourceMap: { + documents: [ + { + _id: '2c1de490-e7ed-413c-8d23-163d4432bb63', + _type: 'book', + }, + { + _id: 'd7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74', + _type: 'author', + }, + { + _id: '8826fb2c-6152-46c0-8d19-079fcd75b438', + _type: 'book', + }, + { + _id: '294709c3-710d-4dc6-8f6f-f36c4786611a', + _type: 'author', + }, + { + _id: 'drafts.8826fb2c-6152-46c0-8d19-079fcd75b438', + _type: 'book', + }, + { + _id: 'drafts.8b671177-113d-4249-ae23-6b50dc017e9e', + _type: 'book', + }, + ], + paths: ["$['_id']", "$['title']", "$['name']"], + mappings: { + "$[0]['_id']": { + source: { + document: 0, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['author']['_id']": { + source: { + document: 1, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['author']['name']": { + source: { + document: 1, + path: 2, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['title']": { + source: { + document: 0, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['_id']": { + source: { + document: 2, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['author']['_id']": { + source: { + document: 3, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['author']['name']": { + source: { + document: 3, + path: 2, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['title']": { + source: { + document: 2, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + "$[2]['_id']": { + source: { + document: 4, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[2]['title']": { + source: { + document: 4, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + "$[3]['_id']": { + source: { + document: 5, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[3]['author']['_id']": { + source: { + document: 3, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[3]['author']['name']": { + source: { + document: 3, + path: 2, + type: 'documentValue', + }, + type: 'value', + }, + "$[3]['title']": { + source: { + document: 5, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + }, + }, + ms: 579, + } as const satisfies { + query: string + result: unknown + resultSourceMap: ContentSourceMap + ms: number + } + + // Ensure that a correct cache doesn't lead to unexpected updates to the `result` + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + getCachedDocument, + undefined, + 'raw', + ), + {color: false}, + ), + ).toBe('') + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + getCachedDocument, + undefined, + 'published', + ), + {color: false}, + ), + ).toBe('') + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + getCachedDocument, + undefined, + 'previewDrafts', + ), + {color: false}, + ), + ).toBe('') + + // Simulate mutations + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + simulateAuthorMutations, + undefined, + 'raw', + ), + {color: false, full: true}, + ), + ).toMatchInlineSnapshot(` + " [ + { + _id: "2c1de490-e7ed-413c-8d23-163d4432bb63" + _originalId: null + - title: "Good Omens (published)" + + title: "Good Omens (changed)" + author: { + _id: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + _originalId: null + name: "Terry Pratchett (published)" + } + } + { + _id: "8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: null + title: "Fire & Ice (published)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: null + name: "George Martin (published)" + } + } + { + _id: "drafts.8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: null + - title: "It (draft)" + + title: "It (published)" + author: null + } + { + _id: "drafts.8b671177-113d-4249-ae23-6b50dc017e9e" + _originalId: null + title: "The Winds of Winter (draft)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: null + name: "George Martin (published)" + } + } + ] + " + `) + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + simulateAuthorMutations, + undefined, + 'published', + ), + {color: false, full: true}, + ), + ).toMatchInlineSnapshot(` + " [ + { + _id: "2c1de490-e7ed-413c-8d23-163d4432bb63" + _originalId: null + - title: "Good Omens (published)" + + title: "Good Omens (changed)" + author: { + _id: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + _originalId: null + name: "Terry Pratchett (published)" + } + } + { + _id: "8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: null + title: "Fire & Ice (published)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: null + name: "George Martin (published)" + } + } + { + _id: "drafts.8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: null + - title: "It (draft)" + + title: "It (published)" + author: null + } + { + _id: "drafts.8b671177-113d-4249-ae23-6b50dc017e9e" + _originalId: null + title: "The Winds of Winter (draft)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: null + name: "George Martin (published)" + } + } + ] + " + `) + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + simulateAuthorMutations, + undefined, + 'previewDrafts', + ), + {color: false, full: true}, + ), + ).toMatchInlineSnapshot(` + " [ + { + _id: "2c1de490-e7ed-413c-8d23-163d4432bb63" + _originalId: null + - title: "Good Omens (published)" + + title: "Good Omens (changed)" + author: { + _id: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + _originalId: null + name: "Terry Pratchett (published)" + } + } + { + _id: "8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: null + title: "Fire & Ice (published)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: null + name: "George Martin (published)" + } + } + { + _id: "drafts.8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: null + - title: "It (draft)" + + title: "It (published)" + author: null + } + { + _id: "drafts.8b671177-113d-4249-ae23-6b50dc017e9e" + _originalId: null + title: "The Winds of Winter (draft)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: null + name: "George Martin (published)" + } + } + ] + " + `) + }) + test('perspective: published', () => { + const mock = { + query: '*[_type == "book"]{\n _id,_originalId,title,author->{_id,_originalId,name}\n}', + result: [ + { + _id: '2c1de490-e7ed-413c-8d23-163d4432bb63', + _originalId: null, + title: 'Good Omens (published)', + author: { + _id: 'd7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74', + _originalId: null, + name: 'Terry Pratchett (published)', + }, + }, + { + _id: '8826fb2c-6152-46c0-8d19-079fcd75b438', + _originalId: null, + title: 'Fire & Ice (published)', + author: { + _id: '294709c3-710d-4dc6-8f6f-f36c4786611a', + _originalId: null, + name: 'George Martin (published)', + }, + }, + ], + resultSourceMap: { + documents: [ + { + _id: '2c1de490-e7ed-413c-8d23-163d4432bb63', + _type: 'book', + }, + { + _id: 'd7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74', + _type: 'author', + }, + { + _id: '8826fb2c-6152-46c0-8d19-079fcd75b438', + _type: 'book', + }, + { + _id: '294709c3-710d-4dc6-8f6f-f36c4786611a', + _type: 'author', + }, + ], + paths: ["$['_id']", "$['title']", "$['name']"], + mappings: { + "$[0]['_id']": { + source: { + document: 0, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['author']['_id']": { + source: { + document: 1, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['author']['name']": { + source: { + document: 1, + path: 2, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['title']": { + source: { + document: 0, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['_id']": { + source: { + document: 2, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['author']['_id']": { + source: { + document: 3, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['author']['name']": { + source: { + document: 3, + path: 2, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['title']": { + source: { + document: 2, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + }, + }, + ms: 12, + } as const satisfies { + query: string + result: unknown + resultSourceMap: ContentSourceMap + ms: number + } + + // Ensure that a correct cache doesn't lead to unexpected updates to the `result` + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + getCachedDocument, + undefined, + 'raw', + ), + {color: false}, + ), + ).toBe('') + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + getCachedDocument, + undefined, + 'published', + ), + {color: false}, + ), + ).toBe('') + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + getCachedDocument, + undefined, + 'previewDrafts', + ), + {color: false}, + ), + ).toBe('') + + // Simulate mutations + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + simulateAuthorMutations, + undefined, + 'raw', + ), + {color: false, full: true}, + ), + ).toMatchInlineSnapshot(` + " [ + { + _id: "2c1de490-e7ed-413c-8d23-163d4432bb63" + _originalId: null + - title: "Good Omens (published)" + + title: "Good Omens (changed)" + author: { + _id: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + _originalId: null + name: "Terry Pratchett (published)" + } + } + { + _id: "8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: null + title: "Fire & Ice (published)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: null + name: "George Martin (published)" + } + } + ] + " + `) + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + simulateAuthorMutations, + undefined, + 'published', + ), + {color: false, full: true}, + ), + ).toMatchInlineSnapshot(` + " [ + { + _id: "2c1de490-e7ed-413c-8d23-163d4432bb63" + _originalId: null + - title: "Good Omens (published)" + + title: "Good Omens (changed)" + author: { + _id: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + _originalId: null + name: "Terry Pratchett (published)" + } + } + { + _id: "8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: null + title: "Fire & Ice (published)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: null + name: "George Martin (published)" + } + } + ] + " + `) + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + simulateAuthorMutations, + undefined, + 'previewDrafts', + ), + {color: false, full: true}, + ), + ).toMatchInlineSnapshot(` + " [ + { + _id: "2c1de490-e7ed-413c-8d23-163d4432bb63" + _originalId: null + - title: "Good Omens (published)" + + title: "Good Omens (changed)" + author: { + _id: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + _originalId: null + name: "Terry Pratchett (published)" + } + } + { + _id: "8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: null + title: "Fire & Ice (published)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: null + name: "George Martin (published)" + } + } + ] + " + `) + }) + test('perspective: previewDrafts', () => { + const mock = { + query: '*[_type == "book"]{\n _id,_originalId,title,author->{_id,_originalId,name}\n}', + result: [ + { + _id: '2c1de490-e7ed-413c-8d23-163d4432bb63', + _originalId: '2c1de490-e7ed-413c-8d23-163d4432bb63', + title: 'Good Omens (published)', + author: { + _id: 'd7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74', + _originalId: 'd7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74', + name: 'Terry Pratchett (published)', + }, + }, + { + _id: '8826fb2c-6152-46c0-8d19-079fcd75b438', + _originalId: 'drafts.8826fb2c-6152-46c0-8d19-079fcd75b438', + title: 'It (draft)', + author: { + _id: 'de2baea7-4df7-4eb0-841e-db20103279fc', + _originalId: 'drafts.de2baea7-4df7-4eb0-841e-db20103279fc', + name: 'Stephen King (draft)', + }, + }, + { + _id: '8b671177-113d-4249-ae23-6b50dc017e9e', + _originalId: 'drafts.8b671177-113d-4249-ae23-6b50dc017e9e', + title: 'The Winds of Winter (draft)', + author: { + _id: '294709c3-710d-4dc6-8f6f-f36c4786611a', + _originalId: 'drafts.294709c3-710d-4dc6-8f6f-f36c4786611a', + name: 'George R.R. Martin (draft)', + }, + }, + ], + resultSourceMap: { + documents: [ + { + _id: '2c1de490-e7ed-413c-8d23-163d4432bb63', + _type: 'book', + }, + { + _id: 'd7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74', + _type: 'author', + }, + { + _id: 'drafts.8826fb2c-6152-46c0-8d19-079fcd75b438', + _type: 'book', + }, + { + _id: 'drafts.de2baea7-4df7-4eb0-841e-db20103279fc', + _type: 'author', + }, + { + _id: 'drafts.8b671177-113d-4249-ae23-6b50dc017e9e', + _type: 'book', + }, + { + _id: 'drafts.294709c3-710d-4dc6-8f6f-f36c4786611a', + _type: 'author', + }, + ], + paths: ["$['_id']", "$['_originalId']", "$['title']", "$['name']"], + mappings: { + "$[0]['_id']": { + source: { + document: 0, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['_originalId']": { + source: { + document: 0, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['author']['_id']": { + source: { + document: 1, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['author']['_originalId']": { + source: { + document: 1, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['author']['name']": { + source: { + document: 1, + path: 3, + type: 'documentValue', + }, + type: 'value', + }, + "$[0]['title']": { + source: { + document: 0, + path: 2, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['_id']": { + source: { + document: 2, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['_originalId']": { + source: { + document: 2, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['author']['_id']": { + source: { + document: 3, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['author']['_originalId']": { + source: { + document: 3, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['author']['name']": { + source: { + document: 3, + path: 3, + type: 'documentValue', + }, + type: 'value', + }, + "$[1]['title']": { + source: { + document: 2, + path: 2, + type: 'documentValue', + }, + type: 'value', + }, + "$[2]['_id']": { + source: { + document: 4, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[2]['_originalId']": { + source: { + document: 4, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + "$[2]['author']['_id']": { + source: { + document: 5, + path: 0, + type: 'documentValue', + }, + type: 'value', + }, + "$[2]['author']['_originalId']": { + source: { + document: 5, + path: 1, + type: 'documentValue', + }, + type: 'value', + }, + "$[2]['author']['name']": { + source: { + document: 5, + path: 3, + type: 'documentValue', + }, + type: 'value', + }, + "$[2]['title']": { + source: { + document: 4, + path: 2, + type: 'documentValue', + }, + type: 'value', + }, + }, + }, + ms: 2881, + } as const satisfies { + query: string + result: unknown + resultSourceMap: ContentSourceMap + ms: number + } + + // Ensure that a correct cache doesn't lead to unexpected updates to the `result` + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + getCachedDocument, + undefined, + 'raw', + ), + {color: false}, + ), + ).toBe('') + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + getCachedDocument, + undefined, + 'published', + ), + {color: false}, + ), + ).toBe('') + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + getCachedDocument, + undefined, + 'previewDrafts', + ), + {color: false}, + ), + ).toBe('') + + // Simulate mutations + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + simulateAuthorMutations, + undefined, + 'raw', + ), + {color: false, full: true}, + ), + ).toMatchInlineSnapshot(` + " [ + { + _id: "2c1de490-e7ed-413c-8d23-163d4432bb63" + _originalId: "2c1de490-e7ed-413c-8d23-163d4432bb63" + - title: "Good Omens (published)" + + title: "Good Omens (changed)" + author: { + _id: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + _originalId: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + name: "Terry Pratchett (published)" + } + } + { + _id: "8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: "drafts.8826fb2c-6152-46c0-8d19-079fcd75b438" + - title: "It (draft)" + + title: "It (published)" + author: { + _id: "de2baea7-4df7-4eb0-841e-db20103279fc" + _originalId: "drafts.de2baea7-4df7-4eb0-841e-db20103279fc" + name: "Stephen King (draft)" + } + } + { + _id: "8b671177-113d-4249-ae23-6b50dc017e9e" + _originalId: "drafts.8b671177-113d-4249-ae23-6b50dc017e9e" + title: "The Winds of Winter (draft)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: "drafts.294709c3-710d-4dc6-8f6f-f36c4786611a" + name: "George R.R. Martin (draft)" + } + } + ] + " + `) + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + simulateAuthorMutations, + undefined, + 'published', + ), + {color: false, full: true}, + ), + ).toMatchInlineSnapshot(` + " [ + { + _id: "2c1de490-e7ed-413c-8d23-163d4432bb63" + _originalId: "2c1de490-e7ed-413c-8d23-163d4432bb63" + - title: "Good Omens (published)" + + title: "Good Omens (changed)" + author: { + _id: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + _originalId: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + name: "Terry Pratchett (published)" + } + } + { + _id: "8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: "drafts.8826fb2c-6152-46c0-8d19-079fcd75b438" + - title: "It (draft)" + + title: "It (published)" + author: { + _id: "de2baea7-4df7-4eb0-841e-db20103279fc" + _originalId: "drafts.de2baea7-4df7-4eb0-841e-db20103279fc" + name: "Stephen King (draft)" + } + } + { + _id: "8b671177-113d-4249-ae23-6b50dc017e9e" + _originalId: "drafts.8b671177-113d-4249-ae23-6b50dc017e9e" + title: "The Winds of Winter (draft)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: "drafts.294709c3-710d-4dc6-8f6f-f36c4786611a" + name: "George R.R. Martin (draft)" + } + } + ] + " + `) + expect( + diffString( + mock.result, + applySourceDocuments( + mock.result, + mock.resultSourceMap, + simulateAuthorMutations, + undefined, + 'previewDrafts', + ), + {color: false, full: true}, + ), + ).toMatchInlineSnapshot(` + " [ + { + _id: "2c1de490-e7ed-413c-8d23-163d4432bb63" + _originalId: "2c1de490-e7ed-413c-8d23-163d4432bb63" + - title: "Good Omens (published)" + + title: "Good Omens (changed)" + author: { + _id: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + _originalId: "d7e0f612-ab9b-4fbb-ad1e-090f6e9d0a74" + name: "Terry Pratchett (published)" + } + } + { + _id: "8826fb2c-6152-46c0-8d19-079fcd75b438" + _originalId: "drafts.8826fb2c-6152-46c0-8d19-079fcd75b438" + - title: "It (draft)" + + title: "It (published)" + author: { + _id: "de2baea7-4df7-4eb0-841e-db20103279fc" + _originalId: "drafts.de2baea7-4df7-4eb0-841e-db20103279fc" + - name: "Stephen King (draft)" + + name: "Stephen King (published)" + } + } + { + _id: "8b671177-113d-4249-ae23-6b50dc017e9e" + _originalId: "drafts.8b671177-113d-4249-ae23-6b50dc017e9e" + title: "The Winds of Winter (draft)" + author: { + _id: "294709c3-710d-4dc6-8f6f-f36c4786611a" + _originalId: "drafts.294709c3-710d-4dc6-8f6f-f36c4786611a" + name: "George R.R. Martin (draft)" + } + } + ] + " + `) + }) +})