@osdk/client 2.2.0-beta.3 → 2.2.0-beta.4
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/CHANGELOG.md +12 -0
- package/build/browser/Logger.js.map +1 -1
- package/build/browser/observable/ObservableClient.js.map +1 -1
- package/build/browser/observable/internal/ActionApplication.js +102 -0
- package/build/browser/observable/internal/ActionApplication.js.map +1 -0
- package/build/browser/observable/internal/CacheKey.js +38 -1
- package/build/browser/observable/internal/CacheKey.js.map +1 -1
- package/build/browser/observable/internal/CacheKeys.js +4 -4
- package/build/browser/observable/internal/CacheKeys.js.map +1 -1
- package/build/browser/observable/internal/ChangedObjects.js +24 -1
- package/build/browser/observable/internal/ChangedObjects.js.map +1 -1
- package/build/browser/observable/internal/Layer.js +3 -3
- package/build/browser/observable/internal/Layer.js.map +1 -1
- package/build/browser/observable/internal/ListQuery.js +188 -66
- package/build/browser/observable/internal/ListQuery.js.map +1 -1
- package/build/browser/observable/internal/ObjectQuery.js +16 -3
- package/build/browser/observable/internal/ObjectQuery.js.map +1 -1
- package/build/browser/observable/internal/ObservableClientImpl.js +2 -2
- package/build/browser/observable/internal/ObservableClientImpl.js.map +1 -1
- package/build/browser/observable/internal/OptimisticJob.js +30 -29
- package/build/browser/observable/internal/OptimisticJob.js.map +1 -1
- package/build/browser/observable/internal/Query.js +42 -2
- package/build/browser/observable/internal/Query.js.map +1 -1
- package/build/browser/observable/internal/Store.js +259 -126
- package/build/browser/observable/internal/Store.js.map +1 -1
- package/build/browser/observable/internal/Store.test.js +416 -76
- package/build/browser/observable/internal/Store.test.js.map +1 -1
- package/build/browser/observable/internal/testUtils.js +142 -6
- package/build/browser/observable/internal/testUtils.js.map +1 -1
- package/build/browser/util/UserAgent.js +1 -1
- package/build/cjs/index.cjs +1 -1
- package/build/cjs/public/unstable-do-not-use.cjs +657 -268
- package/build/cjs/public/unstable-do-not-use.cjs.map +1 -1
- package/build/cjs/public/unstable-do-not-use.d.cts +13 -4
- package/build/esm/Logger.js.map +1 -1
- package/build/esm/observable/ObservableClient.js.map +1 -1
- package/build/esm/observable/internal/ActionApplication.js +102 -0
- package/build/esm/observable/internal/ActionApplication.js.map +1 -0
- package/build/esm/observable/internal/CacheKey.js +38 -1
- package/build/esm/observable/internal/CacheKey.js.map +1 -1
- package/build/esm/observable/internal/CacheKeys.js +4 -4
- package/build/esm/observable/internal/CacheKeys.js.map +1 -1
- package/build/esm/observable/internal/ChangedObjects.js +24 -1
- package/build/esm/observable/internal/ChangedObjects.js.map +1 -1
- package/build/esm/observable/internal/Layer.js +3 -3
- package/build/esm/observable/internal/Layer.js.map +1 -1
- package/build/esm/observable/internal/ListQuery.js +188 -66
- package/build/esm/observable/internal/ListQuery.js.map +1 -1
- package/build/esm/observable/internal/ObjectQuery.js +16 -3
- package/build/esm/observable/internal/ObjectQuery.js.map +1 -1
- package/build/esm/observable/internal/ObservableClientImpl.js +2 -2
- package/build/esm/observable/internal/ObservableClientImpl.js.map +1 -1
- package/build/esm/observable/internal/OptimisticJob.js +30 -29
- package/build/esm/observable/internal/OptimisticJob.js.map +1 -1
- package/build/esm/observable/internal/Query.js +42 -2
- package/build/esm/observable/internal/Query.js.map +1 -1
- package/build/esm/observable/internal/Store.js +259 -126
- package/build/esm/observable/internal/Store.js.map +1 -1
- package/build/esm/observable/internal/Store.test.js +416 -76
- package/build/esm/observable/internal/Store.test.js.map +1 -1
- package/build/esm/observable/internal/testUtils.js +142 -6
- package/build/esm/observable/internal/testUtils.js.map +1 -1
- package/build/esm/util/UserAgent.js +1 -1
- package/build/types/Logger.d.ts +1 -2
- package/build/types/Logger.d.ts.map +1 -1
- package/build/types/observable/ObservableClient.d.ts +10 -3
- package/build/types/observable/ObservableClient.d.ts.map +1 -1
- package/build/types/observable/internal/ActionApplication.d.ts +9 -0
- package/build/types/observable/internal/ActionApplication.d.ts.map +1 -0
- package/build/types/observable/internal/CacheKeys.d.ts +2 -1
- package/build/types/observable/internal/CacheKeys.d.ts.map +1 -1
- package/build/types/observable/internal/ChangedObjects.d.ts +6 -2
- package/build/types/observable/internal/ChangedObjects.d.ts.map +1 -1
- package/build/types/observable/internal/Layer.d.ts +1 -1
- package/build/types/observable/internal/Layer.d.ts.map +1 -1
- package/build/types/observable/internal/ListQuery.d.ts +18 -17
- package/build/types/observable/internal/ListQuery.d.ts.map +1 -1
- package/build/types/observable/internal/ObjectQuery.d.ts +3 -3
- package/build/types/observable/internal/ObjectQuery.d.ts.map +1 -1
- package/build/types/observable/internal/OptimisticJob.d.ts +2 -2
- package/build/types/observable/internal/OptimisticJob.d.ts.map +1 -1
- package/build/types/observable/internal/Query.d.ts +6 -5
- package/build/types/observable/internal/Query.d.ts.map +1 -1
- package/build/types/observable/internal/Store.d.ts +47 -19
- package/build/types/observable/internal/Store.d.ts.map +1 -1
- package/build/types/observable/internal/testUtils.d.ts +13 -3
- package/build/types/observable/internal/testUtils.d.ts.map +1 -1
- package/package.json +8 -7
|
@@ -16,11 +16,13 @@
|
|
|
16
16
|
|
|
17
17
|
import { $ontologyRid, createOffice, Employee, Todo } from "@osdk/client.test.ontology";
|
|
18
18
|
import { apiServer } from "@osdk/shared.test";
|
|
19
|
+
import chalk from "chalk";
|
|
19
20
|
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi, vitest } from "vitest";
|
|
20
21
|
import { createClient } from "../../createClient.js";
|
|
21
22
|
import { createOptimisticId } from "./OptimisticId.js";
|
|
23
|
+
import { runOptimisticJob } from "./OptimisticJob.js";
|
|
22
24
|
import { Store } from "./Store.js";
|
|
23
|
-
import { applyCustomMatchers, createClientMockHelper, createDefer, expectSingleListCallAndClear, expectSingleObjectCallAndClear, listPayloadContaining, mockListSubCallback, mockSingleSubCallback, objectPayloadContaining, waitForCall } from "./testUtils.js";
|
|
25
|
+
import { applyCustomMatchers, createClientMockHelper, createDefer, createTestLogger, expectSingleListCallAndClear, expectSingleObjectCallAndClear, listPayloadContaining, mockListSubCallback, mockSingleSubCallback, objectPayloadContaining, waitForCall } from "./testUtils.js";
|
|
24
26
|
const defer = createDefer();
|
|
25
27
|
beforeAll(() => {
|
|
26
28
|
vi.setConfig({
|
|
@@ -38,6 +40,19 @@ const ANY_INIT_ENTRY = {
|
|
|
38
40
|
lastUpdated: 0,
|
|
39
41
|
status: "init"
|
|
40
42
|
};
|
|
43
|
+
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-deprecated
|
|
45
|
+
function fullTaskName(task) {
|
|
46
|
+
return task ? `${fullTaskName(task.suite)} > ${task.name}` : "";
|
|
47
|
+
}
|
|
48
|
+
beforeEach(x => {
|
|
49
|
+
console.log(chalk.bgRed(chalk.white(fullTaskName(x.task))));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// helper method to make debugging tests easier
|
|
53
|
+
function testStage(s) {
|
|
54
|
+
console.log(chalk.bgYellow(`Test Stage: ${s}`));
|
|
55
|
+
}
|
|
41
56
|
applyCustomMatchers();
|
|
42
57
|
describe(Store, () => {
|
|
43
58
|
describe("with mock server", () => {
|
|
@@ -47,7 +62,9 @@ describe(Store, () => {
|
|
|
47
62
|
let mutatedEmployees;
|
|
48
63
|
beforeAll(async () => {
|
|
49
64
|
apiServer.listen();
|
|
50
|
-
client = createClient("https://stack.palantir.com", $ontologyRid, async () => "myAccessToken"
|
|
65
|
+
client = createClient("https://stack.palantir.com", $ontologyRid, async () => "myAccessToken", {
|
|
66
|
+
logger: createTestLogger({})
|
|
67
|
+
});
|
|
51
68
|
employeesAsServerReturns = (await client(Employee).fetchPage()).data;
|
|
52
69
|
mutatedEmployees = [employeesAsServerReturns[0], employeesAsServerReturns[1].$clone({
|
|
53
70
|
fullName: "foo"
|
|
@@ -106,10 +123,12 @@ describe(Store, () => {
|
|
|
106
123
|
expectSingleObjectCallAndClear(subFn, emp, "loaded");
|
|
107
124
|
});
|
|
108
125
|
it("rolls back to an updated real value", async () => {
|
|
109
|
-
vi.useFakeTimers();
|
|
110
|
-
|
|
111
126
|
// pre-seed the cache with the "real" value
|
|
112
|
-
cache.updateList(
|
|
127
|
+
cache.updateList({
|
|
128
|
+
objectType: Employee,
|
|
129
|
+
where: {},
|
|
130
|
+
orderBy: {}
|
|
131
|
+
}, employeesAsServerReturns);
|
|
113
132
|
const emp = employeesAsServerReturns[0];
|
|
114
133
|
const empSubFn = mockSingleSubCallback();
|
|
115
134
|
defer(cache.observeObject(Employee, emp.$primaryKey, {
|
|
@@ -117,47 +136,58 @@ describe(Store, () => {
|
|
|
117
136
|
}, empSubFn));
|
|
118
137
|
expectSingleObjectCallAndClear(empSubFn, emp, "loaded");
|
|
119
138
|
const listSubFn = mockListSubCallback();
|
|
120
|
-
defer(cache.observeList(
|
|
139
|
+
defer(cache.observeList({
|
|
140
|
+
objectType: Employee,
|
|
121
141
|
mode: "offline"
|
|
122
142
|
}, listSubFn));
|
|
143
|
+
await waitForCall(listSubFn, 1);
|
|
123
144
|
expectSingleListCallAndClear(listSubFn, employeesAsServerReturns);
|
|
124
145
|
const optimisticEmployee = emp.$clone({
|
|
125
146
|
fullName: "new name"
|
|
126
147
|
});
|
|
127
148
|
const optimisticId = createOptimisticId();
|
|
149
|
+
testStage("optimistic update");
|
|
150
|
+
expect(listSubFn).not.toHaveBeenCalled();
|
|
128
151
|
|
|
129
152
|
// update with an optimistic write
|
|
130
153
|
cache.updateObject(Employee, optimisticEmployee, {
|
|
131
154
|
optimisticId
|
|
132
155
|
});
|
|
156
|
+
testStage("after optimistic update");
|
|
133
157
|
|
|
134
158
|
// expect optimistic write
|
|
135
159
|
expectSingleObjectCallAndClear(empSubFn, optimisticEmployee);
|
|
136
160
|
|
|
137
161
|
// expect optimistic write to the list
|
|
138
|
-
|
|
162
|
+
await waitForCall(listSubFn, 1);
|
|
163
|
+
expectSingleListCallAndClear(listSubFn, [optimisticEmployee, ...employeesAsServerReturns.slice(1)], {
|
|
164
|
+
isOptimistic: true,
|
|
165
|
+
status: "loading"
|
|
166
|
+
});
|
|
139
167
|
|
|
140
168
|
// write the real update, via the earlier list definition
|
|
141
169
|
const truthUpdatedEmployee = emp.$clone({
|
|
142
170
|
fullName: "real update"
|
|
143
171
|
});
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
// for the object
|
|
151
|
-
expectSingleListCallAndClear(listSubFn, [optimisticEmployee]);
|
|
172
|
+
testStage("write real update");
|
|
173
|
+
cache.updateList({
|
|
174
|
+
objectType: Employee,
|
|
175
|
+
where: {},
|
|
176
|
+
orderBy: {}
|
|
177
|
+
}, [truthUpdatedEmployee]);
|
|
152
178
|
|
|
153
179
|
// remove the optimistic write
|
|
154
180
|
cache.removeLayer(optimisticId);
|
|
155
181
|
|
|
156
182
|
// see the object observation get updated
|
|
157
|
-
expectSingleObjectCallAndClear(empSubFn, truthUpdatedEmployee);
|
|
183
|
+
expectSingleObjectCallAndClear(empSubFn, truthUpdatedEmployee, "loaded");
|
|
158
184
|
|
|
159
185
|
// see the list get updated
|
|
160
|
-
|
|
186
|
+
await waitForCall(listSubFn, 1);
|
|
187
|
+
expectSingleListCallAndClear(listSubFn, [truthUpdatedEmployee], {
|
|
188
|
+
status: "loaded",
|
|
189
|
+
isOptimistic: false
|
|
190
|
+
});
|
|
161
191
|
vi.useRealTimers();
|
|
162
192
|
});
|
|
163
193
|
it("rolls back to an updated real value via list", async () => {
|
|
@@ -244,39 +274,40 @@ describe(Store, () => {
|
|
|
244
274
|
const staleEmp = emp.$clone({
|
|
245
275
|
fullName: "stale"
|
|
246
276
|
});
|
|
247
|
-
cache.updateList(
|
|
277
|
+
cache.updateList({
|
|
278
|
+
objectType: Employee,
|
|
279
|
+
where: {},
|
|
280
|
+
orderBy: {}
|
|
281
|
+
}, [staleEmp]);
|
|
248
282
|
const subFn = mockSingleSubCallback();
|
|
249
283
|
defer(cache.observeObject(Employee, emp.$primaryKey, {
|
|
250
284
|
mode: "offline"
|
|
251
285
|
}, subFn));
|
|
252
286
|
expectSingleObjectCallAndClear(subFn, staleEmp);
|
|
253
287
|
const subListFn = mockListSubCallback();
|
|
254
|
-
defer(cache.observeList(
|
|
288
|
+
defer(cache.observeList({
|
|
289
|
+
objectType: Employee,
|
|
255
290
|
mode: "offline"
|
|
256
291
|
}, subListFn));
|
|
257
|
-
await
|
|
258
|
-
|
|
259
|
-
resolvedList: [staleEmp],
|
|
292
|
+
await waitForCall(subListFn, 1);
|
|
293
|
+
expectSingleListCallAndClear(subListFn, [staleEmp], {
|
|
260
294
|
status: "loaded"
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
cache.invalidateList(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
295
|
+
});
|
|
296
|
+
testStage("invalidate");
|
|
297
|
+
cache.invalidateList({
|
|
298
|
+
objectType: Employee
|
|
299
|
+
});
|
|
300
|
+
testStage("check invalidate");
|
|
301
|
+
await waitForCall(subListFn, 1);
|
|
302
|
+
expectSingleListCallAndClear(subListFn, [staleEmp], {
|
|
267
303
|
status: "loading"
|
|
268
|
-
})
|
|
269
|
-
subListFn
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
await vi.waitFor(() => expect(subFn).toHaveBeenCalled());
|
|
276
|
-
expect(subFn).toHaveBeenCalledExactlyOnceWith(objectPayloadContaining({
|
|
277
|
-
status: "loaded",
|
|
278
|
-
object: emp
|
|
279
|
-
}));
|
|
304
|
+
});
|
|
305
|
+
await waitForCall(subListFn, 1);
|
|
306
|
+
expectSingleListCallAndClear(subListFn, employeesAsServerReturns, {
|
|
307
|
+
status: "loaded"
|
|
308
|
+
});
|
|
309
|
+
await waitForCall(subFn, 1);
|
|
310
|
+
expectSingleObjectCallAndClear(subFn, emp, "loaded");
|
|
280
311
|
});
|
|
281
312
|
});
|
|
282
313
|
describe(".invalidateObjectType", () => {
|
|
@@ -291,24 +322,30 @@ describe(Store, () => {
|
|
|
291
322
|
const staleEmp = emp.$clone({
|
|
292
323
|
fullName: "stale"
|
|
293
324
|
});
|
|
294
|
-
cache.updateList(
|
|
325
|
+
cache.updateList({
|
|
326
|
+
objectType: Employee,
|
|
327
|
+
where: {},
|
|
328
|
+
orderBy: {}
|
|
329
|
+
}, [staleEmp]);
|
|
295
330
|
const subFn = mockSingleSubCallback();
|
|
296
331
|
defer(cache.observeObject(Employee, emp.$primaryKey, {
|
|
297
332
|
mode: "offline"
|
|
298
333
|
}, subFn));
|
|
299
334
|
expectSingleObjectCallAndClear(subFn, staleEmp);
|
|
300
335
|
const subListFn = mockListSubCallback();
|
|
301
|
-
defer(cache.observeList(
|
|
336
|
+
defer(cache.observeList({
|
|
337
|
+
objectType: Employee,
|
|
338
|
+
where: {},
|
|
339
|
+
orderBy: {},
|
|
302
340
|
mode: "offline"
|
|
303
341
|
}, subListFn));
|
|
304
|
-
await
|
|
305
|
-
|
|
306
|
-
resolvedList: [staleEmp],
|
|
342
|
+
await waitForCall(subListFn, 1);
|
|
343
|
+
expectSingleListCallAndClear(subListFn, [staleEmp], {
|
|
307
344
|
status: "loaded"
|
|
308
|
-
})
|
|
309
|
-
|
|
310
|
-
cache.invalidateObjectType(Employee);
|
|
311
|
-
await
|
|
345
|
+
});
|
|
346
|
+
testStage("invalidate");
|
|
347
|
+
const pInvalidateComplete = cache.invalidateObjectType(Employee, undefined);
|
|
348
|
+
await waitForCall(subListFn, 1);
|
|
312
349
|
expect(subListFn).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
|
|
313
350
|
resolvedList: [staleEmp],
|
|
314
351
|
status: "loading"
|
|
@@ -324,6 +361,10 @@ describe(Store, () => {
|
|
|
324
361
|
status: "loaded",
|
|
325
362
|
object: emp
|
|
326
363
|
}));
|
|
364
|
+
|
|
365
|
+
// we don't need this value to control the test but we want to make sure we don't have
|
|
366
|
+
// any unhandled exceptions upon test completion
|
|
367
|
+
await pInvalidateComplete;
|
|
327
368
|
});
|
|
328
369
|
});
|
|
329
370
|
describe(".observeObject (force)", () => {
|
|
@@ -430,7 +471,11 @@ describe(Store, () => {
|
|
|
430
471
|
fullName: "not the name"
|
|
431
472
|
}));
|
|
432
473
|
expect(subFn).toHaveBeenCalledTimes(1);
|
|
433
|
-
cache.updateList(
|
|
474
|
+
cache.updateList({
|
|
475
|
+
objectType: Employee,
|
|
476
|
+
where: {},
|
|
477
|
+
orderBy: {}
|
|
478
|
+
}, employeesAsServerReturns);
|
|
434
479
|
expect(subFn).toHaveBeenCalledTimes(2);
|
|
435
480
|
expect(subFn.mock.calls[1][0]).toEqual(objectPayloadContaining({
|
|
436
481
|
object: emp
|
|
@@ -448,7 +493,10 @@ describe(Store, () => {
|
|
|
448
493
|
});
|
|
449
494
|
describe("mode=force", () => {
|
|
450
495
|
it("initial load", async () => {
|
|
451
|
-
defer(cache.observeList(
|
|
496
|
+
defer(cache.observeList({
|
|
497
|
+
objectType: Employee,
|
|
498
|
+
where: {},
|
|
499
|
+
orderBy: {},
|
|
452
500
|
mode: "force"
|
|
453
501
|
}, listSub1));
|
|
454
502
|
vitest.runOnlyPendingTimers();
|
|
@@ -466,17 +514,20 @@ describe(Store, () => {
|
|
|
466
514
|
});
|
|
467
515
|
it("subsequent load", async () => {
|
|
468
516
|
// Pre-seed with data the server doesn't return
|
|
469
|
-
cache.updateList(
|
|
470
|
-
|
|
517
|
+
cache.updateList({
|
|
518
|
+
objectType: Employee,
|
|
519
|
+
where: {},
|
|
520
|
+
orderBy: {}
|
|
521
|
+
}, mutatedEmployees);
|
|
522
|
+
defer(cache.observeList({
|
|
523
|
+
objectType: Employee,
|
|
471
524
|
mode: "force"
|
|
472
525
|
}, listSub1));
|
|
473
|
-
await
|
|
474
|
-
expect(listSub1).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
|
|
475
|
-
resolvedList: mutatedEmployees,
|
|
476
|
-
status: "loading"
|
|
477
|
-
}));
|
|
526
|
+
await waitForCall(listSub1, 1);
|
|
478
527
|
const firstLoad = listSub1.mock.calls[0][0];
|
|
479
|
-
listSub1
|
|
528
|
+
expectSingleListCallAndClear(listSub1, mutatedEmployees, {
|
|
529
|
+
status: "loading"
|
|
530
|
+
});
|
|
480
531
|
await vi.waitFor(() => expect(listSub1).toHaveBeenCalled());
|
|
481
532
|
expect(listSub1).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
|
|
482
533
|
resolvedList: employeesAsServerReturns,
|
|
@@ -488,11 +539,18 @@ describe(Store, () => {
|
|
|
488
539
|
});
|
|
489
540
|
describe("mode = offline", () => {
|
|
490
541
|
it("updates with list updates", async () => {
|
|
491
|
-
defer(cache.observeList(
|
|
542
|
+
defer(cache.observeList({
|
|
543
|
+
objectType: Employee,
|
|
544
|
+
where: {},
|
|
545
|
+
orderBy: {},
|
|
492
546
|
mode: "offline"
|
|
493
547
|
}, listSub1));
|
|
494
548
|
expect(listSub1).toHaveBeenCalledTimes(0);
|
|
495
|
-
cache.updateList(
|
|
549
|
+
cache.updateList({
|
|
550
|
+
objectType: Employee,
|
|
551
|
+
where: {},
|
|
552
|
+
orderBy: {}
|
|
553
|
+
}, employeesAsServerReturns);
|
|
496
554
|
vitest.runOnlyPendingTimers();
|
|
497
555
|
expect(listSub1).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
|
|
498
556
|
resolvedList: employeesAsServerReturns
|
|
@@ -500,18 +558,29 @@ describe(Store, () => {
|
|
|
500
558
|
listSub1.mockClear();
|
|
501
559
|
|
|
502
560
|
// list is just now one object
|
|
503
|
-
cache.updateList(
|
|
561
|
+
cache.updateList({
|
|
562
|
+
objectType: Employee,
|
|
563
|
+
where: {},
|
|
564
|
+
orderBy: {}
|
|
565
|
+
}, [employeesAsServerReturns[0]]);
|
|
504
566
|
vitest.runOnlyPendingTimers();
|
|
505
567
|
expect(listSub1).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
|
|
506
568
|
resolvedList: [employeesAsServerReturns[0]]
|
|
507
569
|
}));
|
|
508
570
|
});
|
|
509
571
|
it("updates with different list updates", async () => {
|
|
510
|
-
defer(cache.observeList(
|
|
572
|
+
defer(cache.observeList({
|
|
573
|
+
objectType: Employee,
|
|
574
|
+
where: {},
|
|
575
|
+
orderBy: {},
|
|
511
576
|
mode: "offline"
|
|
512
577
|
}, listSub1));
|
|
513
578
|
expect(listSub1).toHaveBeenCalledTimes(0);
|
|
514
|
-
cache.updateList(
|
|
579
|
+
cache.updateList({
|
|
580
|
+
objectType: Employee,
|
|
581
|
+
where: {},
|
|
582
|
+
orderBy: {}
|
|
583
|
+
}, employeesAsServerReturns);
|
|
515
584
|
vitest.runOnlyPendingTimers();
|
|
516
585
|
expect(listSub1).toHaveBeenCalledExactlyOnceWith(listPayloadContaining({
|
|
517
586
|
resolvedList: employeesAsServerReturns
|
|
@@ -519,10 +588,14 @@ describe(Store, () => {
|
|
|
519
588
|
listSub1.mockClear();
|
|
520
589
|
|
|
521
590
|
// new where === different list
|
|
522
|
-
cache.updateList(
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
591
|
+
cache.updateList({
|
|
592
|
+
objectType: Employee,
|
|
593
|
+
where: {
|
|
594
|
+
employeeId: {
|
|
595
|
+
$gt: 0
|
|
596
|
+
}
|
|
597
|
+
},
|
|
598
|
+
orderBy: {}
|
|
526
599
|
}, mutatedEmployees);
|
|
527
600
|
vitest.runOnlyPendingTimers();
|
|
528
601
|
|
|
@@ -542,7 +615,10 @@ describe(Store, () => {
|
|
|
542
615
|
});
|
|
543
616
|
it("works in the solo case", async () => {
|
|
544
617
|
const listSub = mockListSubCallback();
|
|
545
|
-
defer(cache.observeList(
|
|
618
|
+
defer(cache.observeList({
|
|
619
|
+
objectType: Employee,
|
|
620
|
+
where: {},
|
|
621
|
+
orderBy: {},
|
|
546
622
|
mode: "force",
|
|
547
623
|
pageSize: 1
|
|
548
624
|
}, listSub));
|
|
@@ -579,11 +655,11 @@ describe(Store, () => {
|
|
|
579
655
|
describe("with mock client", () => {
|
|
580
656
|
let client;
|
|
581
657
|
let mockClient;
|
|
582
|
-
let
|
|
658
|
+
let store;
|
|
583
659
|
beforeEach(async () => {
|
|
584
660
|
mockClient = createClientMockHelper();
|
|
585
661
|
client = mockClient.client;
|
|
586
|
-
|
|
662
|
+
store = new Store(client);
|
|
587
663
|
});
|
|
588
664
|
describe("actions", () => {
|
|
589
665
|
it("properly invalidates objects", async () => {
|
|
@@ -592,7 +668,7 @@ describe(Store, () => {
|
|
|
592
668
|
$apiName: "Todo"
|
|
593
669
|
});
|
|
594
670
|
const todoSubFn = mockSingleSubCallback();
|
|
595
|
-
defer(
|
|
671
|
+
defer(store.observeObject(Todo, 0, {}, todoSubFn));
|
|
596
672
|
await todoSubFn.expectLoadingAndLoaded({
|
|
597
673
|
loading: objectPayloadContaining({
|
|
598
674
|
status: "loading",
|
|
@@ -619,7 +695,7 @@ describe(Store, () => {
|
|
|
619
695
|
$apiName: "Todo",
|
|
620
696
|
text: "hello there kind sir"
|
|
621
697
|
});
|
|
622
|
-
const actionPromise =
|
|
698
|
+
const actionPromise = store.applyAction(createOffice, {
|
|
623
699
|
officeId: "whatever"
|
|
624
700
|
});
|
|
625
701
|
await actionPromise;
|
|
@@ -645,7 +721,7 @@ describe(Store, () => {
|
|
|
645
721
|
// after the below `observeObject`, the cache will need to load from the server
|
|
646
722
|
mockClient.mockFetchOneOnce().resolve(fauxObject);
|
|
647
723
|
const todoSubFn = mockSingleSubCallback();
|
|
648
|
-
defer(
|
|
724
|
+
defer(store.observeObject(Todo, 0, {}, todoSubFn));
|
|
649
725
|
await todoSubFn.expectLoadingAndLoaded({
|
|
650
726
|
loading: objectPayloadContaining({
|
|
651
727
|
status: "loading",
|
|
@@ -661,7 +737,7 @@ describe(Store, () => {
|
|
|
661
737
|
|
|
662
738
|
// at this point we have an observation properly set up
|
|
663
739
|
const applyActionResult = mockClient.mockApplyActionOnce();
|
|
664
|
-
const actionPromise =
|
|
740
|
+
const actionPromise = store.applyAction(createOffice, {
|
|
665
741
|
officeId: "whatever"
|
|
666
742
|
}, {
|
|
667
743
|
optimisticUpdate: ctx => {
|
|
@@ -695,10 +771,274 @@ describe(Store, () => {
|
|
|
695
771
|
}));
|
|
696
772
|
});
|
|
697
773
|
});
|
|
774
|
+
describe("orderBy", () => {
|
|
775
|
+
let nextPk = 0;
|
|
776
|
+
const fauxObjectA = {
|
|
777
|
+
$apiName: "Todo",
|
|
778
|
+
$objectType: "Todo",
|
|
779
|
+
$primaryKey: nextPk++,
|
|
780
|
+
$title: "a",
|
|
781
|
+
text: "a"
|
|
782
|
+
};
|
|
783
|
+
const fauxObjectB = {
|
|
784
|
+
$apiName: "Todo",
|
|
785
|
+
$objectType: "Todo",
|
|
786
|
+
$primaryKey: nextPk++,
|
|
787
|
+
$title: "b",
|
|
788
|
+
text: "b"
|
|
789
|
+
};
|
|
790
|
+
const fauxObjectC = {
|
|
791
|
+
$apiName: "Todo",
|
|
792
|
+
$objectType: "Todo",
|
|
793
|
+
$primaryKey: nextPk++,
|
|
794
|
+
$title: "c",
|
|
795
|
+
text: "c"
|
|
796
|
+
};
|
|
797
|
+
const noWhereNoOrderBy = {
|
|
798
|
+
objectType: Todo,
|
|
799
|
+
where: {},
|
|
800
|
+
orderBy: {}
|
|
801
|
+
};
|
|
802
|
+
const noWhereOrderByText = {
|
|
803
|
+
objectType: Todo,
|
|
804
|
+
where: {},
|
|
805
|
+
orderBy: {
|
|
806
|
+
text: "asc"
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
const subListUnordered = mockListSubCallback();
|
|
810
|
+
const subListOrdered = mockListSubCallback();
|
|
811
|
+
beforeEach(() => {
|
|
812
|
+
defer(store.observeList({
|
|
813
|
+
...noWhereNoOrderBy,
|
|
814
|
+
mode: "offline"
|
|
815
|
+
}, subListUnordered));
|
|
816
|
+
expect(subListUnordered).toHaveBeenCalledTimes(0);
|
|
817
|
+
defer(store.observeList({
|
|
818
|
+
...noWhereOrderByText,
|
|
819
|
+
mode: "offline"
|
|
820
|
+
}, subListOrdered));
|
|
821
|
+
expect(subListOrdered).toHaveBeenCalledTimes(0);
|
|
822
|
+
});
|
|
823
|
+
it("invalidates the correct lists", async () => {
|
|
824
|
+
// for whatever reason, the first list is loaded as [B, A]
|
|
825
|
+
store.updateList(noWhereNoOrderBy, [fauxObjectB, fauxObjectA]);
|
|
826
|
+
await waitForCall(subListUnordered, 1);
|
|
827
|
+
expectSingleListCallAndClear(subListUnordered, [fauxObjectB, fauxObjectA]);
|
|
828
|
+
|
|
829
|
+
// The other list definitely matches on the where clause and we can insert
|
|
830
|
+
// orderBy properly. So this is [A, B]
|
|
831
|
+
await waitForCall(subListOrdered, 1);
|
|
832
|
+
expectSingleListCallAndClear(subListOrdered, [fauxObjectA, fauxObjectB]);
|
|
833
|
+
|
|
834
|
+
// For whatever reason, object B is no longer in the first set (use your imagination)
|
|
835
|
+
// but we have added a C before A. So the first list is [C, A]
|
|
836
|
+
store.updateList({
|
|
837
|
+
objectType: Todo,
|
|
838
|
+
where: {},
|
|
839
|
+
orderBy: {}
|
|
840
|
+
}, [fauxObjectC, fauxObjectA]);
|
|
841
|
+
await waitForCall(subListUnordered, 1);
|
|
842
|
+
expectSingleListCallAndClear(subListUnordered, [fauxObjectC, fauxObjectA]);
|
|
843
|
+
|
|
844
|
+
// Nothing told the system that B was deleted so we can presume it still exists
|
|
845
|
+
// and therefore the second list is now [A, B, C]
|
|
846
|
+
await waitForCall(subListOrdered, 1);
|
|
847
|
+
expectSingleListCallAndClear(subListOrdered, [fauxObjectA, fauxObjectB, fauxObjectC]);
|
|
848
|
+
});
|
|
849
|
+
it("produces proper results with optimistic updates and successful action", async () => {
|
|
850
|
+
const optimisticallyMutatedA = {
|
|
851
|
+
...fauxObjectA,
|
|
852
|
+
text: "optimistic"
|
|
853
|
+
};
|
|
854
|
+
const pkForOptimistic = nextPk++;
|
|
855
|
+
const optimisticallyCreatedObjectD = {
|
|
856
|
+
"$apiName": "Todo",
|
|
857
|
+
"$objectType": "Todo",
|
|
858
|
+
"$primaryKey": pkForOptimistic,
|
|
859
|
+
"$title": "d",
|
|
860
|
+
"text": "d",
|
|
861
|
+
id: pkForOptimistic
|
|
862
|
+
};
|
|
863
|
+
|
|
864
|
+
// for whatever reason, the first list is loaded as [B, A]
|
|
865
|
+
store.updateList(noWhereNoOrderBy, [fauxObjectB, fauxObjectA]);
|
|
866
|
+
await waitForCall(subListUnordered, 1);
|
|
867
|
+
expectSingleListCallAndClear(subListUnordered, [fauxObjectB, fauxObjectA]);
|
|
868
|
+
|
|
869
|
+
// The other list definitely matches on the where clause and we can insert
|
|
870
|
+
// orderBy properly. So this is [A, B]
|
|
871
|
+
await waitForCall(subListOrdered, 1);
|
|
872
|
+
expectSingleListCallAndClear(subListOrdered, [fauxObjectA, fauxObjectB]);
|
|
873
|
+
testStage("Start");
|
|
874
|
+
|
|
875
|
+
// the optimistic job will call createObject which triggers the `objectFactory2` of the
|
|
876
|
+
// cache context.
|
|
877
|
+
mockClient.mockObjectFactory2Once().resolve([optimisticallyCreatedObjectD]);
|
|
878
|
+
|
|
879
|
+
// Perform something optimistic.
|
|
880
|
+
const removeOptimisticResult = runOptimisticJob(store, b => {
|
|
881
|
+
b.createObject(Todo, pkForOptimistic, {
|
|
882
|
+
id: pkForOptimistic,
|
|
883
|
+
text: "d"
|
|
884
|
+
});
|
|
885
|
+
b.updateObject(optimisticallyMutatedA);
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// The first list is now [B, A, optimistic]
|
|
889
|
+
await waitForCall(subListUnordered, 1);
|
|
890
|
+
expectSingleListCallAndClear(subListUnordered, [fauxObjectB, optimisticallyMutatedA,
|
|
891
|
+
// same position, new values
|
|
892
|
+
optimisticallyCreatedObjectD], {
|
|
893
|
+
isOptimistic: true
|
|
894
|
+
});
|
|
895
|
+
|
|
896
|
+
// the second list is now [A, B, optimistic]
|
|
897
|
+
await waitForCall(subListOrdered, 1);
|
|
898
|
+
expectSingleListCallAndClear(subListOrdered, [fauxObjectB, optimisticallyCreatedObjectD, optimisticallyMutatedA], {
|
|
899
|
+
isOptimistic: true
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
// Roll back the optimistic update
|
|
903
|
+
await removeOptimisticResult();
|
|
904
|
+
|
|
905
|
+
// The first list is now [B, A]
|
|
906
|
+
await waitForCall(subListUnordered, 1);
|
|
907
|
+
expectSingleListCallAndClear(subListUnordered, [fauxObjectB, fauxObjectA], {
|
|
908
|
+
isOptimistic: false
|
|
909
|
+
});
|
|
910
|
+
|
|
911
|
+
// the second list is now [A, B]
|
|
912
|
+
await waitForCall(subListOrdered, 1);
|
|
913
|
+
expectSingleListCallAndClear(subListOrdered, [fauxObjectA, fauxObjectB], {
|
|
914
|
+
isOptimistic: false
|
|
915
|
+
});
|
|
916
|
+
});
|
|
917
|
+
// I think these are named backwards
|
|
918
|
+
it("produces proper results with optimistic updates and rollback", async () => {
|
|
919
|
+
const pkForOptimistic = nextPk++;
|
|
920
|
+
const optimisticallyCreatedObjectD = {
|
|
921
|
+
"$apiName": "Todo",
|
|
922
|
+
"$objectType": "Todo",
|
|
923
|
+
"$primaryKey": pkForOptimistic,
|
|
924
|
+
"$title": "d",
|
|
925
|
+
"text": "d",
|
|
926
|
+
id: pkForOptimistic
|
|
927
|
+
};
|
|
928
|
+
const optimisticallyMutatedA = {
|
|
929
|
+
...fauxObjectA,
|
|
930
|
+
text: "optimistic"
|
|
931
|
+
};
|
|
932
|
+
testStage("Initial Setup");
|
|
933
|
+
|
|
934
|
+
// later we will "create" this object
|
|
935
|
+
const createdObjectD = {
|
|
936
|
+
"$apiName": "Todo",
|
|
937
|
+
"$objectType": "Todo",
|
|
938
|
+
"$primaryKey": 9000,
|
|
939
|
+
"$title": "d prime",
|
|
940
|
+
"text": "d prime",
|
|
941
|
+
id: 9000
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
// for whatever reason, the first list is loaded as [B, A]
|
|
945
|
+
store.updateList(noWhereNoOrderBy, [fauxObjectB, fauxObjectA]);
|
|
946
|
+
await waitForCall(subListUnordered, 1);
|
|
947
|
+
expectSingleListCallAndClear(subListUnordered, [fauxObjectB, fauxObjectA]);
|
|
948
|
+
|
|
949
|
+
// The other list definitely matches on the where clause and we can insert
|
|
950
|
+
// orderBy properly. So this is [A, B]
|
|
951
|
+
expectSingleListCallAndClear(subListOrdered, [fauxObjectA, fauxObjectB]);
|
|
952
|
+
testStage("Optimistic Creation");
|
|
953
|
+
|
|
954
|
+
// the optimistic job will call createObject which triggers the `objectFactory2` of the
|
|
955
|
+
// cache context.
|
|
956
|
+
|
|
957
|
+
mockClient.mockObjectFactory2Once().resolve([optimisticallyCreatedObjectD]);
|
|
958
|
+
const mockedApplyAction = mockClient.mockApplyActionOnce();
|
|
959
|
+
testStage("Apply Action");
|
|
960
|
+
const actionPromise = store.applyAction(createOffice, {
|
|
961
|
+
officeId: "5"
|
|
962
|
+
}, {
|
|
963
|
+
optimisticUpdate: b => {
|
|
964
|
+
b.createObject(Todo, pkForOptimistic, {
|
|
965
|
+
id: pkForOptimistic,
|
|
966
|
+
text: "d"
|
|
967
|
+
});
|
|
968
|
+
b.updateObject(optimisticallyMutatedA);
|
|
969
|
+
}
|
|
970
|
+
});
|
|
971
|
+
testStage("Optimistic Updates");
|
|
972
|
+
|
|
973
|
+
// The first list is now [B, A, optimistic]
|
|
974
|
+
await waitForCall(subListUnordered, 1);
|
|
975
|
+
expectSingleListCallAndClear(subListUnordered, [fauxObjectB, optimisticallyMutatedA,
|
|
976
|
+
// same position, new values
|
|
977
|
+
optimisticallyCreatedObjectD], {
|
|
978
|
+
isOptimistic: true
|
|
979
|
+
});
|
|
980
|
+
|
|
981
|
+
// the second list is now [A, B, optimistic]
|
|
982
|
+
await waitForCall(subListOrdered, 1);
|
|
983
|
+
expectSingleListCallAndClear(subListOrdered, [fauxObjectB, optimisticallyCreatedObjectD, optimisticallyMutatedA], {
|
|
984
|
+
isOptimistic: true
|
|
985
|
+
});
|
|
986
|
+
testStage("Resolve Action");
|
|
987
|
+
const modifiedObjectA = {
|
|
988
|
+
...fauxObjectA,
|
|
989
|
+
text: "a prime"
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
// The action will complete and then revalidate in order...
|
|
993
|
+
mockClient.mockFetchOneOnce(modifiedObjectA.$primaryKey).resolve(modifiedObjectA);
|
|
994
|
+
mockClient.mockFetchOneOnce(createdObjectD.$primaryKey).resolve(createdObjectD);
|
|
995
|
+
|
|
996
|
+
// this order matters!
|
|
997
|
+
// but now we don't need them because we just update lists instead of revalidate when we can
|
|
998
|
+
// const plainList = mockClient.mockFetchPageOnce<Todo>();
|
|
999
|
+
// const orderedList = mockClient.mockFetchPageOnce<Todo>();
|
|
1000
|
+
|
|
1001
|
+
mockedApplyAction.resolve({
|
|
1002
|
+
addedObjects: [{
|
|
1003
|
+
objectType: "Todo",
|
|
1004
|
+
primaryKey: createdObjectD.id
|
|
1005
|
+
}],
|
|
1006
|
+
modifiedObjects: [{
|
|
1007
|
+
objectType: "Todo",
|
|
1008
|
+
primaryKey: fauxObjectA.$primaryKey
|
|
1009
|
+
}]
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
// plainList.resolve({
|
|
1013
|
+
// nextPageToken: undefined,
|
|
1014
|
+
// totalCount: "4",
|
|
1015
|
+
// data: [fauxObjectB, fauxObjectC, modifiedObjectA, createdObjectD],
|
|
1016
|
+
// });
|
|
1017
|
+
|
|
1018
|
+
// orderedList.resolve({
|
|
1019
|
+
// nextPageToken: undefined,
|
|
1020
|
+
// totalCount: "4",
|
|
1021
|
+
// data: [modifiedObjectA, fauxObjectC, createdObjectD],
|
|
1022
|
+
// });
|
|
1023
|
+
|
|
1024
|
+
await actionPromise;
|
|
1025
|
+
await waitForCall(subListUnordered, 1);
|
|
1026
|
+
expectSingleListCallAndClear(subListUnordered, [fauxObjectB,
|
|
1027
|
+
// fauxObjectC,
|
|
1028
|
+
modifiedObjectA, createdObjectD], {
|
|
1029
|
+
isOptimistic: false
|
|
1030
|
+
});
|
|
1031
|
+
await waitForCall(subListOrdered, 1);
|
|
1032
|
+
expectSingleListCallAndClear(subListOrdered, [modifiedObjectA, fauxObjectB, createdObjectD], {
|
|
1033
|
+
isOptimistic: false
|
|
1034
|
+
});
|
|
1035
|
+
});
|
|
1036
|
+
});
|
|
698
1037
|
});
|
|
699
1038
|
describe("layers", () => {
|
|
700
1039
|
it("properly remove", () => {
|
|
701
|
-
const
|
|
1040
|
+
const clientHelper = createClientMockHelper();
|
|
1041
|
+
const store = new Store(clientHelper.client);
|
|
702
1042
|
const baseObjects = [1, 2].map(i => ({
|
|
703
1043
|
$primaryKey: i,
|
|
704
1044
|
$objectType: "Employee",
|