@sanity/sdk 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +17 -29
- package/dist/index.js +25 -117
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/_exports/index.ts +9 -1
- package/src/document/documentStore.ts +7 -3
- package/src/document/patchOperations.test.ts +8 -342
- package/src/document/patchOperations.ts +13 -259
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sanity/sdk",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Sanity SDK",
|
|
6
6
|
"keywords": [
|
|
@@ -46,6 +46,7 @@
|
|
|
46
46
|
"@sanity/comlink": "^3.0.4",
|
|
47
47
|
"@sanity/diff-match-patch": "^3.2.0",
|
|
48
48
|
"@sanity/diff-patch": "^6.0.0",
|
|
49
|
+
"@sanity/json-match": "^1.0.5",
|
|
49
50
|
"@sanity/message-protocol": "^0.12.0",
|
|
50
51
|
"@sanity/mutate": "^0.12.4",
|
|
51
52
|
"@sanity/types": "^3.83.0",
|
|
@@ -68,11 +69,11 @@
|
|
|
68
69
|
"typescript": "^5.8.3",
|
|
69
70
|
"vite": "^6.3.4",
|
|
70
71
|
"vitest": "^3.1.2",
|
|
71
|
-
"@repo/package.bundle": "3.82.0",
|
|
72
72
|
"@repo/config-eslint": "0.0.0",
|
|
73
|
+
"@repo/config-test": "0.0.1",
|
|
73
74
|
"@repo/package.config": "0.0.1",
|
|
74
75
|
"@repo/tsconfig": "0.0.1",
|
|
75
|
-
"@repo/
|
|
76
|
+
"@repo/package.bundle": "3.82.0"
|
|
76
77
|
},
|
|
77
78
|
"engines": {
|
|
78
79
|
"node": ">=20.0.0"
|
package/src/_exports/index.ts
CHANGED
|
@@ -100,7 +100,7 @@ export {
|
|
|
100
100
|
type TransactionAcceptedEvent,
|
|
101
101
|
type TransactionRevertedEvent,
|
|
102
102
|
} from '../document/events'
|
|
103
|
-
export {type JsonMatch
|
|
103
|
+
export {type JsonMatch} from '../document/patchOperations'
|
|
104
104
|
export {type DocumentPermissionsResult, type PermissionDeniedReason} from '../document/permissions'
|
|
105
105
|
export type {FavoriteStatusResponse} from '../favorites/favorites'
|
|
106
106
|
export {getFavoritesState, resolveFavoritesState} from '../favorites/favorites'
|
|
@@ -137,4 +137,12 @@ export {getUsersState, loadMoreUsers, resolveUsers} from '../users/usersStore'
|
|
|
137
137
|
export {type FetcherStore, type FetcherStoreState} from '../utils/createFetcherStore'
|
|
138
138
|
export {createGroqSearchFilter} from '../utils/createGroqSearchFilter'
|
|
139
139
|
export {CORE_SDK_VERSION} from '../version'
|
|
140
|
+
export {
|
|
141
|
+
getIndexForKey,
|
|
142
|
+
getPathDepth,
|
|
143
|
+
joinPaths,
|
|
144
|
+
jsonMatch,
|
|
145
|
+
slicePath,
|
|
146
|
+
stringifyPath,
|
|
147
|
+
} from '@sanity/json-match'
|
|
140
148
|
export type {CurrentUser, Role, SanityDocument} from '@sanity/types'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {type Action} from '@sanity/client'
|
|
2
2
|
import {getPublishedId} from '@sanity/client/csm'
|
|
3
|
+
import {jsonMatch} from '@sanity/json-match'
|
|
3
4
|
import {type SanityDocument} from 'groq'
|
|
4
5
|
import {type ExprNode} from 'groq-js'
|
|
5
6
|
import {
|
|
@@ -36,7 +37,7 @@ import {type DocumentAction} from './actions'
|
|
|
36
37
|
import {API_VERSION, INITIAL_OUTGOING_THROTTLE_TIME} from './documentConstants'
|
|
37
38
|
import {type DocumentEvent, getDocumentEvents} from './events'
|
|
38
39
|
import {listen, OutOfSyncError} from './listen'
|
|
39
|
-
import {type JsonMatch
|
|
40
|
+
import {type JsonMatch} from './patchOperations'
|
|
40
41
|
import {calculatePermissions, createGrantsLookup, type DatasetAcl, type Grant} from './permissions'
|
|
41
42
|
import {ActionError} from './processActions'
|
|
42
43
|
import {
|
|
@@ -192,8 +193,11 @@ const _getDocumentState = bindActionByDataset(
|
|
|
192
193
|
// wait for draft and published to be loaded before returning a value
|
|
193
194
|
if (draft === undefined || published === undefined) return undefined
|
|
194
195
|
const document = draft ?? published
|
|
195
|
-
if (path) return
|
|
196
|
-
|
|
196
|
+
if (!path) return document
|
|
197
|
+
const result = jsonMatch(document, path).next()
|
|
198
|
+
if (result.done) return undefined
|
|
199
|
+
const {value} = result.value
|
|
200
|
+
return value
|
|
197
201
|
},
|
|
198
202
|
onSubscribe: (context, options: DocumentOptions<string | undefined>) =>
|
|
199
203
|
manageSubscriberIds(context, options.documentId),
|
|
@@ -5,356 +5,16 @@ import {
|
|
|
5
5
|
diffMatchPatch,
|
|
6
6
|
ensureArrayKeysDeep,
|
|
7
7
|
getDeep,
|
|
8
|
-
getIndexForKey,
|
|
9
8
|
ifRevisionID,
|
|
10
9
|
inc,
|
|
11
10
|
insert,
|
|
12
|
-
jsonMatch,
|
|
13
|
-
parsePath,
|
|
14
11
|
set,
|
|
15
12
|
setDeep,
|
|
16
13
|
setIfMissing,
|
|
17
|
-
stringifyPath,
|
|
18
14
|
unset,
|
|
19
15
|
unsetDeep,
|
|
20
16
|
} from './patchOperations'
|
|
21
17
|
|
|
22
|
-
describe('parsePath', () => {
|
|
23
|
-
it('parses an empty string into an empty path', () => {
|
|
24
|
-
expect(parsePath('')).toEqual([])
|
|
25
|
-
})
|
|
26
|
-
|
|
27
|
-
it('parses simple dot notation (simple descent)', () => {
|
|
28
|
-
expect(parsePath('friend.name')).toEqual(['friend', 'name'])
|
|
29
|
-
})
|
|
30
|
-
|
|
31
|
-
it('parses a single property', () => {
|
|
32
|
-
expect(parsePath('foo')).toEqual(['foo'])
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('parses a property with a single numeric index', () => {
|
|
36
|
-
expect(parsePath('items[0]')).toEqual(['items', 0])
|
|
37
|
-
})
|
|
38
|
-
|
|
39
|
-
it('parses a property with a negative index', () => {
|
|
40
|
-
expect(parsePath('items[-1]')).toEqual(['items', -1])
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('parses a property with a keyed segment using double quotes', () => {
|
|
44
|
-
expect(parsePath('items[_key=="value"]')).toEqual(['items', {_key: 'value'}])
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('parses a property with a keyed segment using single quotes', () => {
|
|
48
|
-
expect(parsePath("items[_key=='value']")).toEqual(['items', {_key: 'value'}])
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('parses an array range that selects the whole thing (e.g. [:])', () => {
|
|
52
|
-
expect(parsePath('array[:]')).toEqual(['array', ['', '']])
|
|
53
|
-
})
|
|
54
|
-
|
|
55
|
-
it('parses an array range with both start and end (e.g. [1:9])', () => {
|
|
56
|
-
expect(parsePath('array[1:9]')).toEqual(['array', [1, 9]])
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('parses an array range with missing end (e.g. [4:])', () => {
|
|
60
|
-
expect(parsePath('array[4:]')).toEqual(['array', [4, '']])
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
it('parses an array range with missing start (e.g. [:4])', () => {
|
|
64
|
-
expect(parsePath('array[:4]')).toEqual(['array', ['', 4]])
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
it('parses multiple bracket expressions in one segment', () => {
|
|
68
|
-
expect(parsePath('foo[1][_key=="bar"][2:9]')).toEqual(['foo', 1, {_key: 'bar'}, [2, 9]])
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('parses segments that mix dot and bracket notation', () => {
|
|
72
|
-
// "a.b[0].c" splits into: ["a"] + parseSegment("b[0]") + ["c"] → ["a", "b", 0, "c"]
|
|
73
|
-
expect(parsePath('a.b[0].c')).toEqual(['a', 'b', 0, 'c'])
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
it('parses a segment with text before and after a bracket expression', () => {
|
|
77
|
-
// "foo[1]bar" → ["foo", 1, "bar"]
|
|
78
|
-
expect(parsePath('foo[1]bar')).toEqual(['foo', 1, 'bar'])
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('parses a segment that is only a bracket expression', () => {
|
|
82
|
-
expect(parsePath('[0]')).toEqual([0])
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
it('parses a segment that consists solely of bracket expressions', () => {
|
|
86
|
-
// "[1][_key=="a"]" → [1, { _key: "a" }]
|
|
87
|
-
expect(parsePath('[1][_key=="a"]')).toEqual([1, {_key: 'a'}])
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('ignores a trailing dot', () => {
|
|
91
|
-
// "foo." → ["foo"]
|
|
92
|
-
expect(parsePath('foo.')).toEqual(['foo'])
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('ignores a leading dot', () => {
|
|
96
|
-
// ".foo" → ["foo"]
|
|
97
|
-
expect(parsePath('.foo')).toEqual(['foo'])
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('throws an error when a bracket is not closed', () => {
|
|
101
|
-
expect(() => parsePath('foo[1')).toThrowError('Unmatched "[" in segment: "foo[1"')
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
it('throws an error when bracket content is invalid', () => {
|
|
105
|
-
expect(() => parsePath('a[invalid]')).toThrowError('Invalid bracket content: "[invalid]"')
|
|
106
|
-
})
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
describe('stringifyPath', () => {
|
|
110
|
-
it('stringifies a single string segment', () => {
|
|
111
|
-
expect(stringifyPath(['foo'])).toBe('foo')
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it('stringifies multiple string segments using dot notation', () => {
|
|
115
|
-
expect(stringifyPath(['friend', 'name'])).toBe('friend.name')
|
|
116
|
-
})
|
|
117
|
-
|
|
118
|
-
it('stringifies a path with a single numeric index', () => {
|
|
119
|
-
expect(stringifyPath(['items', 0])).toBe('items[0]')
|
|
120
|
-
})
|
|
121
|
-
|
|
122
|
-
it('stringifies a path with a negative index', () => {
|
|
123
|
-
expect(stringifyPath(['items', -1])).toBe('items[-1]')
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('stringifies a keyed segment', () => {
|
|
127
|
-
expect(stringifyPath(['items', {_key: 'value'}])).toBe('items[_key=="value"]')
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
it('stringifies an array range with both start and end', () => {
|
|
131
|
-
expect(stringifyPath(['array', [1, 9]])).toBe('array[1:9]')
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
it('stringifies an array range with missing end', () => {
|
|
135
|
-
expect(stringifyPath(['array', [4, '']])).toBe('array[4:]')
|
|
136
|
-
})
|
|
137
|
-
|
|
138
|
-
it('stringifies an array range with missing start', () => {
|
|
139
|
-
expect(stringifyPath(['array', ['', 4]])).toBe('array[:4]')
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
it('stringifies an array range with both start and end missing (i.e. ":")', () => {
|
|
143
|
-
expect(stringifyPath(['array', ['', '']])).toBe('array[:]')
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('stringifies a complex combination of segments', () => {
|
|
147
|
-
// ["foo", 1, { _key: "bar" }, [2, 9]] → "foo[1][_key=="bar"][2:9]"
|
|
148
|
-
expect(stringifyPath(['foo', 1, {_key: 'bar'}, [2, 9]])).toBe('foo[1][_key=="bar"][2:9]')
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it('stringifies a path that starts with a non-string segment', () => {
|
|
152
|
-
// [0, "foo"] → "[0].foo"
|
|
153
|
-
expect(stringifyPath([0, 'foo'])).toBe('[0].foo')
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('stringifies consecutive non-string segments without adding extra dots', () => {
|
|
157
|
-
// [0, -1] → "[0][-1]"
|
|
158
|
-
expect(stringifyPath([0, -1])).toBe('[0][-1]')
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('stringifies an empty path as an empty string', () => {
|
|
162
|
-
expect(stringifyPath([])).toBe('')
|
|
163
|
-
})
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
describe('getIndexForKey', () => {
|
|
167
|
-
it('returns undefined when input is not an array', () => {
|
|
168
|
-
expect(getIndexForKey(null, 'a')).toBeUndefined()
|
|
169
|
-
expect(getIndexForKey(123, 'a')).toBeUndefined()
|
|
170
|
-
expect(getIndexForKey('string', 'a')).toBeUndefined()
|
|
171
|
-
expect(getIndexForKey({}, 'a')).toBeUndefined()
|
|
172
|
-
})
|
|
173
|
-
|
|
174
|
-
it('returns undefined when given an empty array', () => {
|
|
175
|
-
const arr: unknown[] = []
|
|
176
|
-
expect(getIndexForKey(arr, 'any')).toBeUndefined()
|
|
177
|
-
})
|
|
178
|
-
|
|
179
|
-
it('returns undefined when array items do not have a _key property', () => {
|
|
180
|
-
const arr = [{notKey: 'a'}, {foo: 'bar'}]
|
|
181
|
-
expect(getIndexForKey(arr, 'a')).toBeUndefined()
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
it('returns the correct index for keyed segments', () => {
|
|
185
|
-
const arr = [{_key: 'a'}, {_key: 'b'}, {_key: 'c'}]
|
|
186
|
-
expect(getIndexForKey(arr, 'a')).toBe(0)
|
|
187
|
-
expect(getIndexForKey(arr, 'b')).toBe(1)
|
|
188
|
-
expect(getIndexForKey(arr, 'c')).toBe(2)
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('returns undefined when the key is not found in the array', () => {
|
|
192
|
-
const arr = [{_key: 'a'}, {_key: 'b'}]
|
|
193
|
-
expect(getIndexForKey(arr, 'c')).toBeUndefined()
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
it('returns the index for the last occurrence when duplicate keys exist', () => {
|
|
197
|
-
const arr = [{_key: 'dup'}, {_key: 'dup'}]
|
|
198
|
-
// Because the lookup overwrites earlier values,
|
|
199
|
-
// the index for "dup" should be the index of the last occurrence.
|
|
200
|
-
expect(getIndexForKey(arr, 'dup')).toBe(1)
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
it('ignores items that are not objects or that lack a _key property', () => {
|
|
204
|
-
const arr = [{_key: 'a'}, 42, 'hello', {_key: 'b'}]
|
|
205
|
-
expect(getIndexForKey(arr, 'a')).toBe(0)
|
|
206
|
-
expect(getIndexForKey(arr, 'b')).toBe(3)
|
|
207
|
-
// Even though 42 and "hello" are in the array, they are ignored.
|
|
208
|
-
expect(getIndexForKey(arr, '42')).toBeUndefined()
|
|
209
|
-
})
|
|
210
|
-
|
|
211
|
-
it('caches the index lookup even if the array is mutated after the first call', () => {
|
|
212
|
-
const arr = [{_key: 'a'}, {_key: 'b'}]
|
|
213
|
-
// First call creates the cache.
|
|
214
|
-
expect(getIndexForKey(arr, 'a')).toBe(0)
|
|
215
|
-
// Mutate the array element _key.
|
|
216
|
-
arr[0]._key = 'changed'
|
|
217
|
-
// The cache still returns the original index for "a"
|
|
218
|
-
expect(getIndexForKey(arr, 'a')).toBe(0)
|
|
219
|
-
// Lookup for the new key is undefined because it was not in the original lookup.
|
|
220
|
-
expect(getIndexForKey(arr, 'changed')).toBeUndefined()
|
|
221
|
-
})
|
|
222
|
-
})
|
|
223
|
-
|
|
224
|
-
describe('jsonMatch', () => {
|
|
225
|
-
it('returns the input when the path expression is empty', () => {
|
|
226
|
-
const input = 42
|
|
227
|
-
const result = jsonMatch(input, '')
|
|
228
|
-
expect(result).toEqual([{value: 42, path: []}])
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
it('matches object properties (simple descent)', () => {
|
|
232
|
-
const input = {friend: {name: 'Alice'}}
|
|
233
|
-
const result = jsonMatch(input, 'friend.name')
|
|
234
|
-
expect(result).toEqual([{value: 'Alice', path: ['friend', 'name']}])
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
it('returns a match with undefined for a missing property', () => {
|
|
238
|
-
const input = {friend: {}}
|
|
239
|
-
const result = jsonMatch(input, 'friend.name')
|
|
240
|
-
// Even though friend.name does not exist, the match is returned with value undefined.
|
|
241
|
-
expect(result).toEqual([{value: undefined, path: ['friend', 'name']}])
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
it('matches an array element by a positive numeric index', () => {
|
|
245
|
-
const input = [10, 20, 30]
|
|
246
|
-
const result = jsonMatch(input, '[1]')
|
|
247
|
-
expect(result).toEqual([{value: 20, path: [1]}])
|
|
248
|
-
})
|
|
249
|
-
|
|
250
|
-
it('matches an array element by a negative numeric index', () => {
|
|
251
|
-
const input = [10, 20, 30]
|
|
252
|
-
const result = jsonMatch(input, '[-1]')
|
|
253
|
-
expect(result).toEqual([{value: 30, path: [-1]}])
|
|
254
|
-
})
|
|
255
|
-
|
|
256
|
-
it('returns no match when a numeric index is used on a non-array', () => {
|
|
257
|
-
const input = {not: 'an array'}
|
|
258
|
-
const result = jsonMatch(input, '[1]')
|
|
259
|
-
expect(result).toEqual([])
|
|
260
|
-
})
|
|
261
|
-
|
|
262
|
-
it('matches multiple elements using an index tuple (range) with both start and end', () => {
|
|
263
|
-
const input = ['a', 'b', 'c', 'd', 'e']
|
|
264
|
-
const result = jsonMatch(input, '[1:4]')
|
|
265
|
-
// The range [1:4] will match indices 1, 2, and 3.
|
|
266
|
-
expect(result).toEqual([
|
|
267
|
-
{value: 'b', path: [1]},
|
|
268
|
-
{value: 'c', path: [2]},
|
|
269
|
-
{value: 'd', path: [3]},
|
|
270
|
-
])
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
it('matches a range with a missing start ([:3])', () => {
|
|
274
|
-
const input = ['a', 'b', 'c', 'd']
|
|
275
|
-
const result = jsonMatch(input, '[:3]')
|
|
276
|
-
// Missing start means start at index 0.
|
|
277
|
-
expect(result).toEqual([
|
|
278
|
-
{value: 'a', path: [0]},
|
|
279
|
-
{value: 'b', path: [1]},
|
|
280
|
-
{value: 'c', path: [2]},
|
|
281
|
-
])
|
|
282
|
-
})
|
|
283
|
-
|
|
284
|
-
it('matches a range with a missing end ([2:])', () => {
|
|
285
|
-
const input = ['a', 'b', 'c', 'd', 'e']
|
|
286
|
-
const result = jsonMatch(input, '[2:]')
|
|
287
|
-
// Missing end means go until the end of the array.
|
|
288
|
-
expect(result).toEqual([
|
|
289
|
-
{value: 'c', path: [2]},
|
|
290
|
-
{value: 'd', path: [3]},
|
|
291
|
-
{value: 'e', path: [4]},
|
|
292
|
-
])
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
it('returns no match for an index tuple when the input is not an array', () => {
|
|
296
|
-
const input = {not: 'an array'}
|
|
297
|
-
const result = jsonMatch(input, '[1:3]')
|
|
298
|
-
expect(result).toEqual([])
|
|
299
|
-
})
|
|
300
|
-
|
|
301
|
-
it('matches an element in an array by keyed segment', () => {
|
|
302
|
-
const input = [{_key: 'bar'}, {_key: 'foo'}, {_key: 'baz'}]
|
|
303
|
-
const result = jsonMatch(input, '[_key=="foo"]')
|
|
304
|
-
expect(result).toEqual([{value: {_key: 'foo'}, path: [{_key: 'foo'}]}])
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
it('returns no match for a keyed segment when the input is not an array', () => {
|
|
308
|
-
const input = {_key: 'foo'}
|
|
309
|
-
const result = jsonMatch(input, '[_key=="foo"]')
|
|
310
|
-
expect(result).toEqual([])
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
it('returns no match for a keyed segment when the key is not found', () => {
|
|
314
|
-
const input = [{_key: 'a'}]
|
|
315
|
-
const result = jsonMatch(input, '[_key=="b"]')
|
|
316
|
-
expect(result).toEqual([])
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
it('matches a complex nested structure with a range and negative index', () => {
|
|
320
|
-
const input = {
|
|
321
|
-
friends: [
|
|
322
|
-
{name: 'Alice', scores: [10, 20, 30]},
|
|
323
|
-
{name: 'Bob', scores: [15, 25, 35]},
|
|
324
|
-
{name: 'Charlie', scores: [20, 30, 40]},
|
|
325
|
-
],
|
|
326
|
-
}
|
|
327
|
-
const result = jsonMatch(input, 'friends[0:2].scores[-1]')
|
|
328
|
-
// For friends[0:2], the range selects friend indices 0 and 1.
|
|
329
|
-
// For each friend, scores[-1] picks the last element in the scores array.
|
|
330
|
-
expect(result).toEqual([
|
|
331
|
-
{value: 30, path: ['friends', 0, 'scores', -1]},
|
|
332
|
-
{value: 35, path: ['friends', 1, 'scores', -1]},
|
|
333
|
-
])
|
|
334
|
-
})
|
|
335
|
-
|
|
336
|
-
it('returns no match when trying to access a property on a non-object', () => {
|
|
337
|
-
const input = {name: 'John'}
|
|
338
|
-
// Here, "name" is a string so it does not have a property "first"
|
|
339
|
-
const result = jsonMatch(input, 'name.first')
|
|
340
|
-
expect(result).toEqual([])
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
it('returns a match with undefined for an out-of-bound numeric index', () => {
|
|
344
|
-
const input = [1, 2, 3]
|
|
345
|
-
// Index 5 is out of bounds; .at(5) returns undefined.
|
|
346
|
-
const result = jsonMatch(input, '[5]')
|
|
347
|
-
expect(result).toEqual([{value: undefined, path: [5]}])
|
|
348
|
-
})
|
|
349
|
-
|
|
350
|
-
it('returns an empty match for an index tuple with an empty range', () => {
|
|
351
|
-
const input = ['a', 'b']
|
|
352
|
-
// A range like [2:2] selects no indices.
|
|
353
|
-
const result = jsonMatch(input, '[2:2]')
|
|
354
|
-
expect(result).toEqual([])
|
|
355
|
-
})
|
|
356
|
-
})
|
|
357
|
-
|
|
358
18
|
describe('getDeep', () => {
|
|
359
19
|
it('returns the input when the path is empty', () => {
|
|
360
20
|
const input = {a: 1, b: 2}
|
|
@@ -608,10 +268,16 @@ describe('set', () => {
|
|
|
608
268
|
})
|
|
609
269
|
})
|
|
610
270
|
|
|
611
|
-
it('
|
|
271
|
+
it('allows setting deeper even if the path expression matches nothing currently', () => {
|
|
612
272
|
const input = {a: 1}
|
|
613
273
|
const output = set(input, {'nonexistent.path': 999})
|
|
614
|
-
expect(output).toEqual({a: 1})
|
|
274
|
+
expect(output).toEqual({a: 1, nonexistent: {path: 999}})
|
|
275
|
+
})
|
|
276
|
+
|
|
277
|
+
it('creates an item from a key constraint if the key is not present', () => {
|
|
278
|
+
const input = {items: [{_key: 'item1'}]}
|
|
279
|
+
const output = set(input, {'items[_key=="item2"]': {_key: 'item2'}})
|
|
280
|
+
expect(output).toEqual({items: [{_key: 'item1'}, {_key: 'item2'}]})
|
|
615
281
|
})
|
|
616
282
|
})
|
|
617
283
|
|