@sasjs/cli 6.30.0 → 6.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/commands/build/internal/mockSite/assets/boot.js +23 -1
- package/build/commands/build/internal/mockSite/assets/persist.js +76 -5
- package/build/commands/build/internal/mockSite/assets/runtime-core.js +413 -50
- package/build/commands/build/internal/mockSite/assets/sw.js +128 -11
- package/build/commands/build/internal/mockSite/mockSite.d.ts +23 -0
- package/build/commands/build/internal/mockSite/mockSite.js +238 -23
- package/build/commands/build/internal/mockSite/spec/boot.spec.js +50 -3
- package/build/commands/build/internal/mockSite/spec/mockSite.spec.js +131 -3
- package/build/commands/build/internal/mockSite/spec/persist.spec.js +179 -1
- package/build/commands/build/internal/mockSite/spec/runtime-core.spec.js +546 -0
- package/build/commands/build/internal/mockSite/spec/sw.spec.js +308 -4
- package/package.json +1 -1
|
@@ -100,9 +100,11 @@ var loadBoot = function (specs, options) {
|
|
|
100
100
|
};
|
|
101
101
|
var listeners = {};
|
|
102
102
|
var controller = options.controller || 'present';
|
|
103
|
+
var timelineAtRegister = [];
|
|
103
104
|
var navigator = {
|
|
104
105
|
serviceWorker: {
|
|
105
106
|
register: function (url, init) {
|
|
107
|
+
timelineAtRegister = timeline.slice();
|
|
106
108
|
registered.push(url + ' ' + JSON.stringify(init));
|
|
107
109
|
return options.registerFails
|
|
108
110
|
? Promise.reject(new Error('nope'))
|
|
@@ -127,7 +129,11 @@ var loadBoot = function (specs, options) {
|
|
|
127
129
|
global.navigator = navigator;
|
|
128
130
|
global.window = options.bootError
|
|
129
131
|
? { __sasjsMockReady: Promise.resolve({ error: options.bootError }) }
|
|
130
|
-
:
|
|
132
|
+
: options.driveError
|
|
133
|
+
? {
|
|
134
|
+
__sasjsMockReady: Promise.resolve({ driveError: options.driveError })
|
|
135
|
+
}
|
|
136
|
+
: {};
|
|
131
137
|
global.sessionStorage = {
|
|
132
138
|
getItem: function (key) { return (key in stored ? stored[key] : null); },
|
|
133
139
|
setItem: function (key, value) {
|
|
@@ -146,7 +152,7 @@ var loadBoot = function (specs, options) {
|
|
|
146
152
|
late = true;
|
|
147
153
|
(listeners['controllerchange'] || []).forEach(function (fn) { return fn(); });
|
|
148
154
|
};
|
|
149
|
-
return { appended: appended, timeline: timeline, registered: registered, stored: stored, claim: claim };
|
|
155
|
+
return { appended: appended, timeline: timeline, registered: registered, stored: stored, claim: claim, timelineAtRegister: timelineAtRegister };
|
|
150
156
|
};
|
|
151
157
|
var settle = function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
152
158
|
var i;
|
|
@@ -336,6 +342,29 @@ describe('the boot loader', function () {
|
|
|
336
342
|
}
|
|
337
343
|
});
|
|
338
344
|
}); });
|
|
345
|
+
it('starts the drive restore before the worker is registered', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
346
|
+
var timelineAtRegister;
|
|
347
|
+
return __generator(this, function (_a) {
|
|
348
|
+
switch (_a.label) {
|
|
349
|
+
case 0:
|
|
350
|
+
timelineAtRegister = loadBoot([{ src: 'sasjs.js' }]).timelineAtRegister;
|
|
351
|
+
return [4 /*yield*/, settle()
|
|
352
|
+
// persist.js reads the page's own IndexedDB and hands the drive to the
|
|
353
|
+
// worker as soon as there is one, so nothing about it needs the worker to be
|
|
354
|
+
// ready: sequenced after the registration it is another download plus a
|
|
355
|
+
// store read on top of the install, which is what made a first visit slow
|
|
356
|
+
];
|
|
357
|
+
case 1:
|
|
358
|
+
_a.sent();
|
|
359
|
+
// persist.js reads the page's own IndexedDB and hands the drive to the
|
|
360
|
+
// worker as soon as there is one, so nothing about it needs the worker to be
|
|
361
|
+
// ready: sequenced after the registration it is another download plus a
|
|
362
|
+
// store read on top of the install, which is what made a first visit slow
|
|
363
|
+
expect(timelineAtRegister).toEqual(['host/persist.js']);
|
|
364
|
+
return [2 /*return*/];
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
}); });
|
|
339
368
|
it('keeps the attributes an app relies on', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
340
369
|
var appended, app;
|
|
341
370
|
return __generator(this, function (_a) {
|
|
@@ -430,6 +459,21 @@ describe('the boot loader', function () {
|
|
|
430
459
|
}
|
|
431
460
|
});
|
|
432
461
|
}); });
|
|
462
|
+
it('reports a drive that was restored from less than the whole capture', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
463
|
+
return __generator(this, function (_a) {
|
|
464
|
+
switch (_a.label) {
|
|
465
|
+
case 0:
|
|
466
|
+
// the page still runs - a missing piece only fails the file that needed it -
|
|
467
|
+
// so the error is the only thing that says the drive is incomplete
|
|
468
|
+
loadBoot([], { driveError: 'the drive index answered 500' });
|
|
469
|
+
return [4 /*yield*/, settle()];
|
|
470
|
+
case 1:
|
|
471
|
+
_a.sent();
|
|
472
|
+
expect(console.error).toHaveBeenCalledWith(expect.stringContaining('the drive index answered 500'));
|
|
473
|
+
return [2 /*return*/];
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}); });
|
|
433
477
|
it('refuses a boot config it cannot read instead of guessing', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
434
478
|
var timeline;
|
|
435
479
|
return __generator(this, function (_a) {
|
|
@@ -527,7 +571,10 @@ describe('the boot loader', function () {
|
|
|
527
571
|
// into the next test's globals
|
|
528
572
|
_b.sent();
|
|
529
573
|
expect(stored['mock-sw-reloaded']).toEqual('1');
|
|
530
|
-
|
|
574
|
+
// the drive restore is requested before the registration (it overlaps the
|
|
575
|
+
// install), so it is already in flight when the page decides to reload; the
|
|
576
|
+
// reload discards it, which costs nothing the cache does not already hold
|
|
577
|
+
expect(timeline).toEqual(['host/persist.js', 'reload']);
|
|
531
578
|
return [2 /*return*/];
|
|
532
579
|
}
|
|
533
580
|
});
|
|
@@ -140,6 +140,12 @@ describe('browser-hosted build', function () {
|
|
|
140
140
|
var readConfig = function () {
|
|
141
141
|
return JSON.parse(fs_1.default.readFileSync(path_1.default.join(out(), 'host', 'mock-config.json'), 'utf8'));
|
|
142
142
|
};
|
|
143
|
+
var readIndex = function () {
|
|
144
|
+
return JSON.parse(fs_1.default.readFileSync(path_1.default.join(out(), 'host', 'vfs-snapshot.json'), 'utf8'));
|
|
145
|
+
};
|
|
146
|
+
var readShard = function (id) {
|
|
147
|
+
return JSON.parse(fs_1.default.readFileSync(path_1.default.join(out(), 'host', "vfs-snapshot-".concat(id, ".json")), 'utf8')).files;
|
|
148
|
+
};
|
|
143
149
|
beforeEach(function () {
|
|
144
150
|
root = fs_1.default.mkdtempSync(path_1.default.join(require('os').tmpdir(), 'sasjs-mock-'));
|
|
145
151
|
createProject(root);
|
|
@@ -558,6 +564,66 @@ describe('browser-hosted build', function () {
|
|
|
558
564
|
}
|
|
559
565
|
});
|
|
560
566
|
}); });
|
|
567
|
+
/**
|
|
568
|
+
* The loader replays the app's tags only once the worker controls the page.
|
|
569
|
+
* Right for answerability, but it also puts the largest asset on the site
|
|
570
|
+
* behind the largest prerequisite - so a classic script is hinted in the head,
|
|
571
|
+
* where the parser reaches it before the loader runs.
|
|
572
|
+
*/
|
|
573
|
+
it('hints a classic app script so it downloads while the worker installs', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
574
|
+
var html, hint;
|
|
575
|
+
return __generator(this, function (_a) {
|
|
576
|
+
switch (_a.label) {
|
|
577
|
+
case 0:
|
|
578
|
+
write(path_1.default.join(root, 'client', 'dist', 'index.html'), [
|
|
579
|
+
'<!doctype html><html><head><title>App</title></head><body>',
|
|
580
|
+
"<sasjs serverurl=\"https://sas.example.com\" apploc=\"".concat(appLoc, "\" servertype=\"SASJS\"></sasjs>"),
|
|
581
|
+
'<script src="main.abc123.js"></script>',
|
|
582
|
+
'<script src="deferred.js" type="module"></script>',
|
|
583
|
+
'</body></html>'
|
|
584
|
+
].join('\n'));
|
|
585
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
586
|
+
case 1:
|
|
587
|
+
_a.sent();
|
|
588
|
+
html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
|
|
589
|
+
hint = html.match(/<link rel="preload" as="script" href="([^"]+)" \/>/);
|
|
590
|
+
expect(hint).not.toBeNull();
|
|
591
|
+
// the hint names the URL the replayed tag carries, version query included: a
|
|
592
|
+
// mismatch is a second download rather than a warm cache
|
|
593
|
+
expect(hint[1]).toMatch(/^main\.abc123\.js\?v=[0-9a-f]{10}$/);
|
|
594
|
+
expect(html).toContain("\"src\":\"".concat(hint[1], "\""));
|
|
595
|
+
// and it has to be in the head, ahead of the loader that replays the tag
|
|
596
|
+
expect(html.indexOf('rel="preload"')).toBeLessThan(html.indexOf('host/boot.js'));
|
|
597
|
+
// a module is fetched with different credentials semantics, so a script hint
|
|
598
|
+
// would not match its tag - it is replayed without one
|
|
599
|
+
expect(html).not.toMatch(/as="script" href="deferred/);
|
|
600
|
+
return [2 /*return*/];
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
}); });
|
|
604
|
+
it('leaves a script this build does not own unhinted', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
605
|
+
var html;
|
|
606
|
+
return __generator(this, function (_a) {
|
|
607
|
+
switch (_a.label) {
|
|
608
|
+
case 0:
|
|
609
|
+
// a page may load a script from somewhere else; a version guess or a
|
|
610
|
+
// cross-origin hint only breaks it
|
|
611
|
+
write(path_1.default.join(root, 'client', 'dist', 'index.html'), [
|
|
612
|
+
'<!doctype html><html><head><title>App</title></head><body>',
|
|
613
|
+
"<sasjs serverurl=\"https://sas.example.com\" apploc=\"".concat(appLoc, "\" servertype=\"SASJS\"></sasjs>"),
|
|
614
|
+
'<script src="https://cdn.example.com/lib.js"></script>',
|
|
615
|
+
'<script src="not-in-this-build.js"></script>',
|
|
616
|
+
'</body></html>'
|
|
617
|
+
].join('\n'));
|
|
618
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
619
|
+
case 1:
|
|
620
|
+
_a.sent();
|
|
621
|
+
html = fs_1.default.readFileSync(path_1.default.join(out(), 'index.html'), 'utf8');
|
|
622
|
+
expect(html).not.toContain('rel="preload"');
|
|
623
|
+
return [2 /*return*/];
|
|
624
|
+
}
|
|
625
|
+
});
|
|
626
|
+
}); });
|
|
561
627
|
it("leaves a stylesheet alone when the file is not this build's", function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
562
628
|
var html, specs;
|
|
563
629
|
return __generator(this, function (_a) {
|
|
@@ -889,7 +955,7 @@ describe('browser-hosted build', function () {
|
|
|
889
955
|
});
|
|
890
956
|
}); });
|
|
891
957
|
it('ships a captured drive snapshot when the project has one', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
892
|
-
var snapshot, result,
|
|
958
|
+
var snapshot, result, index;
|
|
893
959
|
var _a;
|
|
894
960
|
return __generator(this, function (_b) {
|
|
895
961
|
switch (_b.label) {
|
|
@@ -905,11 +971,73 @@ describe('browser-hosted build', function () {
|
|
|
905
971
|
case 1:
|
|
906
972
|
result = _b.sent();
|
|
907
973
|
expect(result.snapshot).toBe(true);
|
|
908
|
-
|
|
909
|
-
expect(
|
|
974
|
+
index = readIndex();
|
|
975
|
+
expect(index.sharded).toBe(true);
|
|
976
|
+
expect(Object.keys(index.entries).length).toBe(2);
|
|
977
|
+
// two small files under one folder: one boot shard, no request of their own
|
|
978
|
+
expect(result.snapshotShards).toBe(1);
|
|
979
|
+
expect(index.boot).toBe(0);
|
|
980
|
+
expect(readShard(0)).toEqual(snapshot.files);
|
|
910
981
|
// the runtime prefers a snapshot over the seed, so it must not be mistaken
|
|
911
982
|
// for one of the seeded folders
|
|
912
983
|
expect(readConfig().snapshot).toBe(true);
|
|
984
|
+
expect(readConfig().snapshotShards).toBe(1);
|
|
985
|
+
return [2 /*return*/];
|
|
986
|
+
}
|
|
987
|
+
});
|
|
988
|
+
}); });
|
|
989
|
+
/**
|
|
990
|
+
* A captured drive is not one download.
|
|
991
|
+
*
|
|
992
|
+
* The first page of a demonstration site reads a listing and a handful of
|
|
993
|
+
* logos; the bundles behind the tiles are opened by whoever clicks them. One
|
|
994
|
+
* file for the lot makes every visitor pay for all of it before the page
|
|
995
|
+
* renders, so the build splits it: small files in the boot shard, an app's
|
|
996
|
+
* bundle in its own.
|
|
997
|
+
*/
|
|
998
|
+
it('ships a large capture as a boot shard plus a piece per bundle', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
999
|
+
var bundle, capture, result, index, bundlePath, bundleShard, bootBytes, total;
|
|
1000
|
+
var _a;
|
|
1001
|
+
return __generator(this, function (_b) {
|
|
1002
|
+
switch (_b.label) {
|
|
1003
|
+
case 0:
|
|
1004
|
+
bundle = 'x'.repeat(mockSite_1.SHARD_FILE_BYTES + 1024);
|
|
1005
|
+
capture = {
|
|
1006
|
+
files: (_a = {
|
|
1007
|
+
'/sasjs/drive/files/.sasjs/apps.json': '{"apps":[]}'
|
|
1008
|
+
},
|
|
1009
|
+
_a["/sasjs/drive/files/Public/app/demo/logo.png"] = 'not really a png',
|
|
1010
|
+
_a["/sasjs/drive/files/Public/app/demo/services/web/index.bundle.js"] = bundle,
|
|
1011
|
+
_a)
|
|
1012
|
+
};
|
|
1013
|
+
write(path_1.default.join(root, 'sasjsbuild', 'mock-snapshot.json'), JSON.stringify(capture));
|
|
1014
|
+
return [4 /*yield*/, (0, mockSite_1.buildMockSite)(target())];
|
|
1015
|
+
case 1:
|
|
1016
|
+
result = _b.sent();
|
|
1017
|
+
index = readIndex();
|
|
1018
|
+
expect(result.snapshotShards).toBe(2);
|
|
1019
|
+
// what a first page reads is in the boot shard
|
|
1020
|
+
expect(index.entries['/sasjs/drive/files/.sasjs/apps.json'].shard).toBe(0);
|
|
1021
|
+
expect(index.entries["/sasjs/drive/files/Public/app/demo/logo.png"].shard).toBe(0);
|
|
1022
|
+
bundlePath = "/sasjs/drive/files/Public/app/demo/services/web/index.bundle.js";
|
|
1023
|
+
bundleShard = index.entries[bundlePath].shard;
|
|
1024
|
+
expect(bundleShard).not.toBe(0);
|
|
1025
|
+
expect(readShard(bundleShard)[bundlePath]).toBe(bundle);
|
|
1026
|
+
bootBytes = index.shards[0].bytes;
|
|
1027
|
+
total = Object.keys(index.entries).reduce(function (sum, key) { return sum + index.entries[key].bytes; }, 0);
|
|
1028
|
+
expect(bootBytes).toBeLessThan(total / 10);
|
|
1029
|
+
return [2 /*return*/];
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
}); });
|
|
1033
|
+
it('refuses a capture written by a runtime of another format', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
1034
|
+
return __generator(this, function (_a) {
|
|
1035
|
+
switch (_a.label) {
|
|
1036
|
+
case 0:
|
|
1037
|
+
write(path_1.default.join(root, 'sasjsbuild', 'mock-snapshot.json'), JSON.stringify({ format: mockSite_1.DRIVE_FORMAT + 1, files: {} }));
|
|
1038
|
+
return [4 /*yield*/, expect((0, mockSite_1.buildMockSite)(target())).rejects.toThrow(/format/)];
|
|
1039
|
+
case 1:
|
|
1040
|
+
_a.sent();
|
|
913
1041
|
return [2 /*return*/];
|
|
914
1042
|
}
|
|
915
1043
|
});
|
|
@@ -213,6 +213,10 @@ var loadPersist = function (options) {
|
|
|
213
213
|
// the identity row lives beside the drive, under its own key
|
|
214
214
|
if (options.seededIdentity)
|
|
215
215
|
fake.rows.set('current', options.seededIdentity);
|
|
216
|
+
// and the paths a visitor deleted, which a sharded site's index would
|
|
217
|
+
// otherwise restore
|
|
218
|
+
if (options.seededDeleted)
|
|
219
|
+
fake.rows.set('deleted', options.seededDeleted);
|
|
216
220
|
return fake;
|
|
217
221
|
})();
|
|
218
222
|
global.indexedDB = idb;
|
|
@@ -228,6 +232,40 @@ var loadPersist = function (options) {
|
|
|
228
232
|
stored: local
|
|
229
233
|
};
|
|
230
234
|
};
|
|
235
|
+
/**
|
|
236
|
+
* Waits for the store's own handshake to land.
|
|
237
|
+
*
|
|
238
|
+
* A persist message is answered by a chain of transactions - write the files,
|
|
239
|
+
* read the tombstone list, write it back - and the page does not expose the
|
|
240
|
+
* promise. A fixed sleep is a race on a slow runner (Windows was the one that
|
|
241
|
+
* failed), so the assertions poll for the change they expect instead.
|
|
242
|
+
*/
|
|
243
|
+
var settleUntil = function (check_1) {
|
|
244
|
+
var args_1 = [];
|
|
245
|
+
for (var _i = 1; _i < arguments.length; _i++) {
|
|
246
|
+
args_1[_i - 1] = arguments[_i];
|
|
247
|
+
}
|
|
248
|
+
return __awaiter(void 0, __spreadArray([check_1], __read(args_1), false), void 0, function (check, timeout) {
|
|
249
|
+
var started;
|
|
250
|
+
if (timeout === void 0) { timeout = 4000; }
|
|
251
|
+
return __generator(this, function (_a) {
|
|
252
|
+
switch (_a.label) {
|
|
253
|
+
case 0:
|
|
254
|
+
started = Date.now();
|
|
255
|
+
_a.label = 1;
|
|
256
|
+
case 1:
|
|
257
|
+
if (!(Date.now() - started < timeout)) return [3 /*break*/, 3];
|
|
258
|
+
if (check())
|
|
259
|
+
return [2 /*return*/];
|
|
260
|
+
return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 10); })];
|
|
261
|
+
case 2:
|
|
262
|
+
_a.sent();
|
|
263
|
+
return [3 /*break*/, 1];
|
|
264
|
+
case 3: return [2 /*return*/];
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
};
|
|
231
269
|
describe('page-side store', function () {
|
|
232
270
|
afterEach(function () {
|
|
233
271
|
jest.useRealTimers();
|
|
@@ -440,7 +478,7 @@ describe('page-side store', function () {
|
|
|
440
478
|
case 0:
|
|
441
479
|
_a = loadPersist(), window = _a.window, reply = _a.reply, rows = _a.rows;
|
|
442
480
|
reply({ type: 'sasjs-persist', files: { '/sasjs/drive/c.txt': 'ccc' } });
|
|
443
|
-
return [4 /*yield*/,
|
|
481
|
+
return [4 /*yield*/, settleUntil(function () { return rows.get('/sasjs/drive/c.txt') === 'ccc'; })];
|
|
444
482
|
case 1:
|
|
445
483
|
_b.sent();
|
|
446
484
|
expect(rows.get('/sasjs/drive/c.txt')).toBe('ccc');
|
|
@@ -448,6 +486,146 @@ describe('page-side store', function () {
|
|
|
448
486
|
}
|
|
449
487
|
});
|
|
450
488
|
}); });
|
|
489
|
+
/**
|
|
490
|
+
* A deletion has to outlive the worker, and the page is the only store that can
|
|
491
|
+
* keep it.
|
|
492
|
+
*
|
|
493
|
+
* The drive in a sharded build is not all in the page either: the index lists
|
|
494
|
+
* every file the build ships, so a file the visitor deleted comes back on the
|
|
495
|
+
* next load unless the path is remembered here and handed to the worker at
|
|
496
|
+
* boot.
|
|
497
|
+
*/
|
|
498
|
+
it('remembers a deleted path, and forgets it when the path is written again', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
499
|
+
var _a, window, reply, rows;
|
|
500
|
+
return __generator(this, function (_b) {
|
|
501
|
+
switch (_b.label) {
|
|
502
|
+
case 0:
|
|
503
|
+
_a = loadPersist({
|
|
504
|
+
seeded: { '/sasjs/drive/a.txt': 'a', '/sasjs/drive/b.txt': 'b' }
|
|
505
|
+
}), window = _a.window, reply = _a.reply, rows = _a.rows;
|
|
506
|
+
reply({ type: 'sasjs-persist', files: { '/sasjs/drive/a.txt': null } });
|
|
507
|
+
return [4 /*yield*/, settleUntil(function () { return rows.has('deleted'); })];
|
|
508
|
+
case 1:
|
|
509
|
+
_b.sent();
|
|
510
|
+
expect(rows.get('deleted')).toEqual(['/sasjs/drive/a.txt']);
|
|
511
|
+
// the row itself is gone, and the tombstone is what stops the index bringing
|
|
512
|
+
// it back
|
|
513
|
+
expect(rows.has('/sasjs/drive/a.txt')).toBe(false);
|
|
514
|
+
reply({ type: 'sasjs-persist', files: { '/sasjs/drive/a.txt': 'again' } });
|
|
515
|
+
// the tombstone is written back after the file, so poll for it rather than
|
|
516
|
+
// for the write that precedes it
|
|
517
|
+
return [4 /*yield*/, settleUntil(function () { return !rows.has('deleted'); })];
|
|
518
|
+
case 2:
|
|
519
|
+
// the tombstone is written back after the file, so poll for it rather than
|
|
520
|
+
// for the write that precedes it
|
|
521
|
+
_b.sent();
|
|
522
|
+
expect(rows.has('deleted')).toBe(false);
|
|
523
|
+
expect(rows.get('/sasjs/drive/a.txt')).toBe('again');
|
|
524
|
+
return [2 /*return*/];
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
}); });
|
|
528
|
+
it('writes no file when the worker could not read the drive whole', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
529
|
+
var controller, _a, page, listeners, reply, answering, pending;
|
|
530
|
+
return __generator(this, function (_b) {
|
|
531
|
+
switch (_b.label) {
|
|
532
|
+
case 0:
|
|
533
|
+
controller = { postMessage: jest.fn() };
|
|
534
|
+
_a = loadPersist({
|
|
535
|
+
controller: controller,
|
|
536
|
+
ready: Promise.resolve({})
|
|
537
|
+
}), page = _a.window, listeners = _a.listeners, reply = _a.reply;
|
|
538
|
+
answering = setInterval(function () {
|
|
539
|
+
var asked = controller.postMessage.mock.calls;
|
|
540
|
+
if (!asked.length)
|
|
541
|
+
return;
|
|
542
|
+
__spreadArray([], __read(listeners), false).forEach(function (fn) {
|
|
543
|
+
return fn({ data: { type: asked[0][0].type + '-ack', booted: true } });
|
|
544
|
+
});
|
|
545
|
+
}, 5);
|
|
546
|
+
return [4 /*yield*/, page.__sasjsMockReady];
|
|
547
|
+
case 1:
|
|
548
|
+
_b.sent();
|
|
549
|
+
clearInterval(answering);
|
|
550
|
+
pending = page.SASjsMock.download();
|
|
551
|
+
return [4 /*yield*/, Promise.resolve()
|
|
552
|
+
// the worker answers with what it has and says why it could not finish, and a
|
|
553
|
+
// file built from that would be a capture with every app bundle missing
|
|
554
|
+
];
|
|
555
|
+
case 2:
|
|
556
|
+
_b.sent();
|
|
557
|
+
// the worker answers with what it has and says why it could not finish, and a
|
|
558
|
+
// file built from that would be a capture with every app bundle missing
|
|
559
|
+
reply({
|
|
560
|
+
type: 'sasjs-snapshot',
|
|
561
|
+
files: { '/sasjs/drive/a.txt': 'a' },
|
|
562
|
+
error: 'ensureAll rejected: ENOENT'
|
|
563
|
+
});
|
|
564
|
+
return [4 /*yield*/, expect(pending).rejects.toThrow(/could not be read whole/)];
|
|
565
|
+
case 3:
|
|
566
|
+
_b.sent();
|
|
567
|
+
return [2 /*return*/];
|
|
568
|
+
}
|
|
569
|
+
});
|
|
570
|
+
}); });
|
|
571
|
+
it('drops the recorded deletions with the rest of the drive', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
572
|
+
var _a, window, listeners, rows, pending;
|
|
573
|
+
return __generator(this, function (_b) {
|
|
574
|
+
switch (_b.label) {
|
|
575
|
+
case 0:
|
|
576
|
+
_a = loadPersist({
|
|
577
|
+
seeded: { '/sasjs/drive/a.txt': 'a' },
|
|
578
|
+
seededDeleted: ['/sasjs/drive/a.txt']
|
|
579
|
+
}), window = _a.window, listeners = _a.listeners, rows = _a.rows;
|
|
580
|
+
pending = window.SASjsMock.reset();
|
|
581
|
+
return [4 /*yield*/, Promise.resolve()];
|
|
582
|
+
case 1:
|
|
583
|
+
_b.sent();
|
|
584
|
+
__spreadArray([], __read(listeners), false).forEach(function (fn) { return fn({ data: { type: 'sasjs-reset-ack' } }); });
|
|
585
|
+
return [4 /*yield*/, pending];
|
|
586
|
+
case 2:
|
|
587
|
+
_b.sent();
|
|
588
|
+
return [4 /*yield*/, settleUntil(function () { return !rows.has('deleted'); })];
|
|
589
|
+
case 3:
|
|
590
|
+
_b.sent();
|
|
591
|
+
expect(rows.has('deleted')).toBe(false);
|
|
592
|
+
return [2 /*return*/];
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
}); });
|
|
596
|
+
it('tells the worker which paths were deleted, so its index cannot restore them', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
597
|
+
var controller, _a, window, listeners, answering;
|
|
598
|
+
return __generator(this, function (_b) {
|
|
599
|
+
switch (_b.label) {
|
|
600
|
+
case 0:
|
|
601
|
+
controller = { postMessage: jest.fn() };
|
|
602
|
+
_a = loadPersist({
|
|
603
|
+
controller: controller,
|
|
604
|
+
ready: Promise.resolve({}),
|
|
605
|
+
seeded: { '/sasjs/drive/b.txt': 'b' },
|
|
606
|
+
seededDeleted: ['/sasjs/drive/a.txt']
|
|
607
|
+
}), window = _a.window, listeners = _a.listeners;
|
|
608
|
+
answering = setInterval(function () {
|
|
609
|
+
var asked = controller.postMessage.mock.calls;
|
|
610
|
+
if (!asked.length)
|
|
611
|
+
return;
|
|
612
|
+
var type = asked[0][0].type;
|
|
613
|
+
__spreadArray([], __read(listeners), false).forEach(function (fn) {
|
|
614
|
+
return fn({ data: { type: type + '-ack', booted: true } });
|
|
615
|
+
});
|
|
616
|
+
}, 5);
|
|
617
|
+
return [4 /*yield*/, window.__sasjsMockReady];
|
|
618
|
+
case 1:
|
|
619
|
+
_b.sent();
|
|
620
|
+
clearInterval(answering);
|
|
621
|
+
expect(controller.postMessage.mock.calls[0][0]).toEqual(expect.objectContaining({
|
|
622
|
+
type: 'sasjs-load',
|
|
623
|
+
deleted: ['/sasjs/drive/a.txt']
|
|
624
|
+
}));
|
|
625
|
+
return [2 /*return*/];
|
|
626
|
+
}
|
|
627
|
+
});
|
|
628
|
+
}); });
|
|
451
629
|
it('resets the worker and the stored drive together', function () { return __awaiter(void 0, void 0, void 0, function () {
|
|
452
630
|
var _a, window, posted, reply, rows, pending;
|
|
453
631
|
return __generator(this, function (_b) {
|