@schukai/monster 4.148.0 → 4.148.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/source/components/form/select.mjs +151 -34
- package/source/components/form/util/floating-layout-queue.mjs +38 -18
- package/source/components/form/util/floating-ui.mjs +68 -12
- package/source/data/pathfinder.mjs +44 -6
- package/source/dom/updater.mjs +76 -25
- package/source/types/proxyobserver.mjs +15 -0
- package/source/util/clone.mjs +8 -6
- package/test/cases/components/form/floating-ui.mjs +48 -1
- package/test/cases/components/form/select.mjs +276 -23
- package/test/cases/data/pathfinder.mjs +105 -1
- package/test/cases/dom/updater.mjs +293 -0
- package/test/cases/types/proxyobserver.mjs +52 -0
- package/test/cases/util/clone.mjs +24 -0
|
@@ -86,6 +86,17 @@ function waitForCondition(check, {timeout = 4000, interval = 25} = {}) {
|
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function configureRemotePaginatedSelect(select) {
|
|
90
|
+
select.setOption('url', 'https://example.com/items?filter={filter}&page={page}');
|
|
91
|
+
select.setOption('filter.mode', 'remote');
|
|
92
|
+
select.setOption('mapping.selector', 'items.*');
|
|
93
|
+
select.setOption('mapping.labelTemplate', '${name}');
|
|
94
|
+
select.setOption('mapping.valueTemplate', '${id}');
|
|
95
|
+
select.setOption('mapping.total', 'pagination.total');
|
|
96
|
+
select.setOption('mapping.currentPage', 'pagination.page');
|
|
97
|
+
select.setOption('mapping.objectsPerPage', 'pagination.perPage');
|
|
98
|
+
}
|
|
99
|
+
|
|
89
100
|
let Select,
|
|
90
101
|
SelectStyleSheet,
|
|
91
102
|
getDefaultSelectPopperPositionProfile,
|
|
@@ -397,6 +408,46 @@ describe('Select', function () {
|
|
|
397
408
|
}
|
|
398
409
|
});
|
|
399
410
|
|
|
411
|
+
it('should keep the floating layout queue usable after cancelling a running reentrant job', async function () {
|
|
412
|
+
const cancelledPopper = document.createElement('div');
|
|
413
|
+
const nextPopper = document.createElement('div');
|
|
414
|
+
const releasePosition = createDeferred();
|
|
415
|
+
let positionStarted = false;
|
|
416
|
+
let positionFinished = false;
|
|
417
|
+
let nextMutationCount = 0;
|
|
418
|
+
|
|
419
|
+
enqueueFloatingLayout({
|
|
420
|
+
popperElement: cancelledPopper,
|
|
421
|
+
reason: FLOATING_LAYOUT_REASON.POSITION,
|
|
422
|
+
position: async () => {
|
|
423
|
+
positionStarted = true;
|
|
424
|
+
await releasePosition.promise;
|
|
425
|
+
await enqueueFloatingLayout({
|
|
426
|
+
popperElement: cancelledPopper,
|
|
427
|
+
reason: FLOATING_LAYOUT_REASON.SETTLE
|
|
428
|
+
});
|
|
429
|
+
positionFinished = true;
|
|
430
|
+
}
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
await waitForCondition(() => positionStarted === true);
|
|
434
|
+
cancelFloatingLayout(cancelledPopper);
|
|
435
|
+
releasePosition.resolve();
|
|
436
|
+
await waitForCondition(() => positionFinished === true);
|
|
437
|
+
await new Promise(resolve => setTimeout(resolve, 0));
|
|
438
|
+
|
|
439
|
+
enqueueFloatingLayout({
|
|
440
|
+
popperElement: nextPopper,
|
|
441
|
+
reason: FLOATING_LAYOUT_REASON.POSITION,
|
|
442
|
+
mutate: () => {
|
|
443
|
+
nextMutationCount += 1;
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
await flushFloatingLayoutQueueForTests();
|
|
447
|
+
|
|
448
|
+
expect(nextMutationCount).to.equal(1);
|
|
449
|
+
});
|
|
450
|
+
|
|
400
451
|
it('should flush reentrant floating layout queue jobs through the watchdog', async function () {
|
|
401
452
|
const originalRequestAnimationFrame = global.requestAnimationFrame;
|
|
402
453
|
const originalCancelAnimationFrame = global.cancelAnimationFrame;
|
|
@@ -738,7 +789,7 @@ describe('Select', function () {
|
|
|
738
789
|
expect(popper.style.display).to.equal('block');
|
|
739
790
|
});
|
|
740
791
|
|
|
741
|
-
it('should allow the popper to become wider than a narrow control', function (
|
|
792
|
+
it('should allow the popper to become wider than a narrow control', async function () {
|
|
742
793
|
const mocks = document.getElementById('mocks');
|
|
743
794
|
const select = document.createElement('monster-select');
|
|
744
795
|
|
|
@@ -764,23 +815,12 @@ describe('Select', function () {
|
|
|
764
815
|
y: 100
|
|
765
816
|
});
|
|
766
817
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
expect(popper.dataset.monsterWidthBehavior).to.equal('preferred');
|
|
774
|
-
expect(popper.dataset.monsterPreferredWidth).to.equal('240');
|
|
775
|
-
done();
|
|
776
|
-
} catch (e) {
|
|
777
|
-
done(e);
|
|
778
|
-
}
|
|
779
|
-
}, 80);
|
|
780
|
-
} catch (e) {
|
|
781
|
-
done(e);
|
|
782
|
-
}
|
|
783
|
-
}, 20);
|
|
818
|
+
await new Promise(resolve => setTimeout(resolve, 20));
|
|
819
|
+
shadowRoot.querySelector('[data-monster-role=container]').click();
|
|
820
|
+
await waitForCondition(() => popper.style.minWidth === '240px');
|
|
821
|
+
|
|
822
|
+
expect(popper.dataset.monsterWidthBehavior).to.equal('preferred');
|
|
823
|
+
expect(popper.dataset.monsterPreferredWidth).to.equal('240');
|
|
784
824
|
});
|
|
785
825
|
|
|
786
826
|
it('should use fixed positioning inside a control bar', function (done) {
|
|
@@ -1136,12 +1176,48 @@ describe('Select', function () {
|
|
|
1136
1176
|
]);
|
|
1137
1177
|
});
|
|
1138
1178
|
|
|
1139
|
-
it('should
|
|
1179
|
+
it('should use the reduced visual viewport while a soft keyboard is open', function () {
|
|
1180
|
+
const result = resolveSelectViewportMetrics({
|
|
1181
|
+
layoutWidth: 390,
|
|
1182
|
+
layoutHeight: 844,
|
|
1183
|
+
visualWidth: 390,
|
|
1184
|
+
visualHeight: 480,
|
|
1185
|
+
offsetLeft: 0,
|
|
1186
|
+
offsetTop: 0,
|
|
1187
|
+
padding: 12
|
|
1188
|
+
});
|
|
1189
|
+
|
|
1190
|
+
expect(result.width).to.equal(390);
|
|
1191
|
+
expect(result.height).to.equal(480);
|
|
1192
|
+
expect(result.left).to.equal(0);
|
|
1193
|
+
expect(result.top).to.equal(0);
|
|
1194
|
+
expect(result.padding).to.equal(12);
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
it('should preserve visual viewport offsets for zoomed mobile layouts', function () {
|
|
1198
|
+
const result = resolveSelectViewportMetrics({
|
|
1199
|
+
layoutWidth: 1200,
|
|
1200
|
+
layoutHeight: 900,
|
|
1201
|
+
visualWidth: 600,
|
|
1202
|
+
visualHeight: 450,
|
|
1203
|
+
offsetLeft: 20,
|
|
1204
|
+
offsetTop: 30,
|
|
1205
|
+
padding: 12
|
|
1206
|
+
});
|
|
1207
|
+
|
|
1208
|
+
expect(result.width).to.equal(600);
|
|
1209
|
+
expect(result.height).to.equal(450);
|
|
1210
|
+
expect(result.left).to.equal(20);
|
|
1211
|
+
expect(result.top).to.equal(30);
|
|
1212
|
+
expect(result.padding).to.equal(12);
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
it('should fall back to layout viewport metrics without a visual viewport', function () {
|
|
1140
1216
|
const result = resolveSelectViewportMetrics({
|
|
1141
1217
|
layoutWidth: 1400,
|
|
1142
1218
|
layoutHeight: 900,
|
|
1143
|
-
visualWidth:
|
|
1144
|
-
visualHeight:
|
|
1219
|
+
visualWidth: 0,
|
|
1220
|
+
visualHeight: 0,
|
|
1145
1221
|
offsetLeft: 20,
|
|
1146
1222
|
offsetTop: 30,
|
|
1147
1223
|
padding: 12
|
|
@@ -1149,8 +1225,8 @@ describe('Select', function () {
|
|
|
1149
1225
|
|
|
1150
1226
|
expect(result.width).to.equal(1400);
|
|
1151
1227
|
expect(result.height).to.equal(900);
|
|
1152
|
-
expect(result.left).to.equal(
|
|
1153
|
-
expect(result.top).to.equal(
|
|
1228
|
+
expect(result.left).to.equal(0);
|
|
1229
|
+
expect(result.top).to.equal(0);
|
|
1154
1230
|
expect(result.padding).to.equal(12);
|
|
1155
1231
|
});
|
|
1156
1232
|
|
|
@@ -1487,6 +1563,142 @@ describe('Select', function () {
|
|
|
1487
1563
|
.catch((e) => done(e));
|
|
1488
1564
|
}, 50);
|
|
1489
1565
|
});
|
|
1566
|
+
|
|
1567
|
+
it('should ignore a remote page response that settles after reset', async function () {
|
|
1568
|
+
this.timeout(5000);
|
|
1569
|
+
|
|
1570
|
+
const deferredResponse = createDeferred();
|
|
1571
|
+
let requestStarted = false;
|
|
1572
|
+
global['fetch'] = function () {
|
|
1573
|
+
requestStarted = true;
|
|
1574
|
+
return deferredResponse.promise;
|
|
1575
|
+
};
|
|
1576
|
+
|
|
1577
|
+
const mocks = document.getElementById('mocks');
|
|
1578
|
+
const select = document.createElement('monster-select');
|
|
1579
|
+
configureRemotePaginatedSelect(select);
|
|
1580
|
+
mocks.appendChild(select);
|
|
1581
|
+
|
|
1582
|
+
const request = select.fetch('https://example.com/items?filter=old&page=3');
|
|
1583
|
+
await waitForCondition(() => requestStarted === true);
|
|
1584
|
+
|
|
1585
|
+
select.reset();
|
|
1586
|
+
await waitForCondition(() => {
|
|
1587
|
+
return select.getOption('options').length === 0 && select.getOption('total') === null;
|
|
1588
|
+
});
|
|
1589
|
+
|
|
1590
|
+
deferredResponse.resolve(
|
|
1591
|
+
await createJsonResponse({
|
|
1592
|
+
items: [{id: 'old-3', name: 'Old page 3'}],
|
|
1593
|
+
pagination: {
|
|
1594
|
+
total: 9,
|
|
1595
|
+
page: 3,
|
|
1596
|
+
perPage: 1
|
|
1597
|
+
}
|
|
1598
|
+
})
|
|
1599
|
+
);
|
|
1600
|
+
await request;
|
|
1601
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1602
|
+
|
|
1603
|
+
const pagination = select.shadowRoot.querySelector('[data-monster-role=pagination]');
|
|
1604
|
+
expect(select.getOption('options')).to.deep.equal([]);
|
|
1605
|
+
expect(select.getOption('total')).to.equal(null);
|
|
1606
|
+
expect(pagination.style.display).to.equal('none');
|
|
1607
|
+
expect(pagination.getOption('currentPage')).to.equal(null);
|
|
1608
|
+
expect(pagination.getOption('pages')).to.equal(null);
|
|
1609
|
+
});
|
|
1610
|
+
|
|
1611
|
+
it('should ignore a remote page response after disconnect and reconnect', async function () {
|
|
1612
|
+
this.timeout(5000);
|
|
1613
|
+
|
|
1614
|
+
const deferredResponse = createDeferred();
|
|
1615
|
+
let requestStarted = false;
|
|
1616
|
+
global['fetch'] = function () {
|
|
1617
|
+
requestStarted = true;
|
|
1618
|
+
return deferredResponse.promise;
|
|
1619
|
+
};
|
|
1620
|
+
|
|
1621
|
+
const mocks = document.getElementById('mocks');
|
|
1622
|
+
const select = document.createElement('monster-select');
|
|
1623
|
+
configureRemotePaginatedSelect(select);
|
|
1624
|
+
mocks.appendChild(select);
|
|
1625
|
+
|
|
1626
|
+
const request = select.fetch('https://example.com/items?filter=old&page=2');
|
|
1627
|
+
await waitForCondition(() => requestStarted === true);
|
|
1628
|
+
|
|
1629
|
+
select.remove();
|
|
1630
|
+
deferredResponse.resolve(
|
|
1631
|
+
await createJsonResponse({
|
|
1632
|
+
items: [{id: 'old-2', name: 'Old page 2'}],
|
|
1633
|
+
pagination: {
|
|
1634
|
+
total: 4,
|
|
1635
|
+
page: 2,
|
|
1636
|
+
perPage: 1
|
|
1637
|
+
}
|
|
1638
|
+
})
|
|
1639
|
+
);
|
|
1640
|
+
await request;
|
|
1641
|
+
mocks.appendChild(select);
|
|
1642
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
1643
|
+
|
|
1644
|
+
const pagination = select.shadowRoot.querySelector('[data-monster-role=pagination]');
|
|
1645
|
+
expect(select.getOption('options')).to.deep.equal([]);
|
|
1646
|
+
expect(select.getOption('total')).to.equal(null);
|
|
1647
|
+
expect(pagination.style.display).to.equal('none');
|
|
1648
|
+
expect(pagination.getOption('currentPage')).to.equal(null);
|
|
1649
|
+
expect(pagination.getOption('pages')).to.equal(null);
|
|
1650
|
+
});
|
|
1651
|
+
|
|
1652
|
+
it('should keep the newest remote page when responses settle out of order', async function () {
|
|
1653
|
+
this.timeout(5000);
|
|
1654
|
+
|
|
1655
|
+
const page2Response = createDeferred();
|
|
1656
|
+
const page3Response = createDeferred();
|
|
1657
|
+
const requests = [];
|
|
1658
|
+
global['fetch'] = function (url) {
|
|
1659
|
+
const requestUrl = String(url);
|
|
1660
|
+
requests.push(requestUrl);
|
|
1661
|
+
return requestUrl.includes('page=2') ? page2Response.promise : page3Response.promise;
|
|
1662
|
+
};
|
|
1663
|
+
|
|
1664
|
+
const mocks = document.getElementById('mocks');
|
|
1665
|
+
const select = document.createElement('monster-select');
|
|
1666
|
+
configureRemotePaginatedSelect(select);
|
|
1667
|
+
mocks.appendChild(select);
|
|
1668
|
+
|
|
1669
|
+
const request2 = select.fetch('https://example.com/items?filter=all&page=2');
|
|
1670
|
+
const request3 = select.fetch('https://example.com/items?filter=all&page=3');
|
|
1671
|
+
await waitForCondition(() => requests.length === 2);
|
|
1672
|
+
|
|
1673
|
+
page3Response.resolve(
|
|
1674
|
+
await createJsonResponse({
|
|
1675
|
+
items: [{id: 'page-3', name: 'Page 3'}],
|
|
1676
|
+
pagination: {
|
|
1677
|
+
total: 3,
|
|
1678
|
+
page: 3,
|
|
1679
|
+
perPage: 1
|
|
1680
|
+
}
|
|
1681
|
+
})
|
|
1682
|
+
);
|
|
1683
|
+
await request3;
|
|
1684
|
+
|
|
1685
|
+
page2Response.resolve(
|
|
1686
|
+
await createJsonResponse({
|
|
1687
|
+
items: [{id: 'page-2', name: 'Page 2'}],
|
|
1688
|
+
pagination: {
|
|
1689
|
+
total: 3,
|
|
1690
|
+
page: 2,
|
|
1691
|
+
perPage: 1
|
|
1692
|
+
}
|
|
1693
|
+
})
|
|
1694
|
+
);
|
|
1695
|
+
await request2;
|
|
1696
|
+
|
|
1697
|
+
const pagination = select.shadowRoot.querySelector('[data-monster-role=pagination]');
|
|
1698
|
+
expect(select.getOption('options').map(option => option.value)).to.deep.equal(['page-3']);
|
|
1699
|
+
expect(pagination.getOption('currentPage')).to.equal(3);
|
|
1700
|
+
expect(pagination.getOption('pages')).to.equal(3);
|
|
1701
|
+
});
|
|
1490
1702
|
});
|
|
1491
1703
|
|
|
1492
1704
|
describe('document.createElement()', function () {
|
|
@@ -2488,6 +2700,47 @@ describe('Select', function () {
|
|
|
2488
2700
|
expect(requests).to.have.length(1);
|
|
2489
2701
|
});
|
|
2490
2702
|
|
|
2703
|
+
it('should cancel a pending remote filter request on reset', async function () {
|
|
2704
|
+
this.timeout(5000);
|
|
2705
|
+
|
|
2706
|
+
const mocks = document.getElementById('mocks');
|
|
2707
|
+
const requests = [];
|
|
2708
|
+
global['fetch'] = function (url) {
|
|
2709
|
+
requests.push(String(url));
|
|
2710
|
+
return createJsonResponse({
|
|
2711
|
+
items: [{id: 'alpha', name: 'Alpha'}],
|
|
2712
|
+
pagination: {
|
|
2713
|
+
total: 1,
|
|
2714
|
+
page: 1,
|
|
2715
|
+
perPage: 1
|
|
2716
|
+
}
|
|
2717
|
+
});
|
|
2718
|
+
};
|
|
2719
|
+
|
|
2720
|
+
const select = document.createElement('monster-select');
|
|
2721
|
+
configureRemotePaginatedSelect(select);
|
|
2722
|
+
select.setOption('filter.position', 'popper');
|
|
2723
|
+
mocks.appendChild(select);
|
|
2724
|
+
|
|
2725
|
+
await waitForCondition(() => {
|
|
2726
|
+
return select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]') instanceof HTMLInputElement;
|
|
2727
|
+
});
|
|
2728
|
+
|
|
2729
|
+
const filterInput = select.shadowRoot.querySelector('[data-monster-role=filter][name="popper-filter"]');
|
|
2730
|
+
filterInput.value = 'alpha';
|
|
2731
|
+
filterInput.dispatchEvent(new Event('input', {
|
|
2732
|
+
bubbles: true,
|
|
2733
|
+
composed: true
|
|
2734
|
+
}));
|
|
2735
|
+
select.reset();
|
|
2736
|
+
|
|
2737
|
+
await new Promise(resolve => setTimeout(resolve, 260));
|
|
2738
|
+
|
|
2739
|
+
expect(requests).to.deep.equal([]);
|
|
2740
|
+
expect(select.getOption('options')).to.deep.equal([]);
|
|
2741
|
+
expect(select.getOption('total')).to.equal(null);
|
|
2742
|
+
});
|
|
2743
|
+
|
|
2491
2744
|
it('should keep unresolved lookup values visible and mark their badge', function (done) {
|
|
2492
2745
|
this.timeout(3000);
|
|
2493
2746
|
|
|
@@ -5,6 +5,111 @@ import {Pathfinder} from "../../../source/data/pathfinder.mjs";
|
|
|
5
5
|
|
|
6
6
|
describe('Pathfinder', function () {
|
|
7
7
|
|
|
8
|
+
describe('mutation path security issue #492', function () {
|
|
9
|
+
const probe = 'monsterPathfinderSecurityProbe';
|
|
10
|
+
|
|
11
|
+
afterEach(function () {
|
|
12
|
+
delete Object.prototype[probe];
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
[
|
|
16
|
+
`__proto__.${probe}`,
|
|
17
|
+
`constructor.prototype.${probe}`,
|
|
18
|
+
['__proto__', probe],
|
|
19
|
+
['constructor', 'prototype', probe],
|
|
20
|
+
].forEach(function (path) {
|
|
21
|
+
it(`setVia(${JSON.stringify(path)}) should reject unsafe mutation paths`, function () {
|
|
22
|
+
try {
|
|
23
|
+
expect(() => new Pathfinder({}).setVia(path, 'polluted')).to.throw(Error);
|
|
24
|
+
expect(Object.prototype).to.not.have.own.property(probe);
|
|
25
|
+
} finally {
|
|
26
|
+
delete Object.prototype[probe];
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('deleteVia() should not cross a prototype boundary', function () {
|
|
32
|
+
Object.defineProperty(Object.prototype, probe, {
|
|
33
|
+
configurable: true,
|
|
34
|
+
value: 'preserved',
|
|
35
|
+
writable: true,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
expect(() => new Pathfinder({}).deleteVia(`__proto__.${probe}`)).to.throw(Error);
|
|
40
|
+
expect(Object.prototype[probe]).to.equal('preserved');
|
|
41
|
+
} finally {
|
|
42
|
+
delete Object.prototype[probe];
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('setVia() should keep safe paths working', function () {
|
|
47
|
+
const subject = {};
|
|
48
|
+
new Pathfinder(subject).setVia('safe.nested.value', true);
|
|
49
|
+
expect(subject).to.deep.equal({safe: {nested: {value: true}}});
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe('array and collection path contracts issue #493', function () {
|
|
54
|
+
it('getVia() should accept an array path without mutating it', function () {
|
|
55
|
+
const path = ['a', 'b'];
|
|
56
|
+
const subject = {a: {b: 4}};
|
|
57
|
+
|
|
58
|
+
expect(new Pathfinder(subject).getVia(path)).to.equal(4);
|
|
59
|
+
expect(path).to.deep.equal(['a', 'b']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('getVia([]) should return the root subject', function () {
|
|
63
|
+
const subject = {a: true};
|
|
64
|
+
expect(new Pathfinder(subject).getVia([])).to.equal(subject);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('setVia() should not mutate an array path', function () {
|
|
68
|
+
const path = ['a', 'b'];
|
|
69
|
+
const subject = {};
|
|
70
|
+
|
|
71
|
+
new Pathfinder(subject).setVia(path, 4);
|
|
72
|
+
|
|
73
|
+
expect(subject).to.deep.equal({a: {b: 4}});
|
|
74
|
+
expect(path).to.deep.equal(['a', 'b']);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('deleteVia() should not mutate an array path', function () {
|
|
78
|
+
const path = ['a', 'b'];
|
|
79
|
+
const subject = {a: {b: 4, c: 5}};
|
|
80
|
+
|
|
81
|
+
new Pathfinder(subject).deleteVia(path);
|
|
82
|
+
|
|
83
|
+
expect(subject).to.deep.equal({a: {c: 5}});
|
|
84
|
+
expect(path).to.deep.equal(['a', 'b']);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('empty mutation paths should be no-ops', function () {
|
|
88
|
+
const setPath = [];
|
|
89
|
+
const deletePath = [];
|
|
90
|
+
const subject = {a: true};
|
|
91
|
+
const pathfinder = new Pathfinder(subject);
|
|
92
|
+
|
|
93
|
+
pathfinder.setVia(setPath, false).deleteVia(deletePath);
|
|
94
|
+
|
|
95
|
+
expect(subject).to.deep.equal({a: true});
|
|
96
|
+
expect(setPath).to.deep.equal([]);
|
|
97
|
+
expect(deletePath).to.deep.equal([]);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('setVia() should add values to a Set', function () {
|
|
101
|
+
const values = new Set(['first']);
|
|
102
|
+
new Pathfinder({values}).setVia('values.next', 'second');
|
|
103
|
+
expect([...values]).to.deep.equal(['first', 'second']);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('setVia() should reject WeakSet writes explicitly', function () {
|
|
107
|
+
const values = new WeakSet();
|
|
108
|
+
expect(() => new Pathfinder({values}).setVia('values.next', {}))
|
|
109
|
+
.to.throw(Error, 'unsupported action for this data type in setValueViaPath');
|
|
110
|
+
});
|
|
111
|
+
});
|
|
112
|
+
|
|
8
113
|
let convertMapResult = function (r) {
|
|
9
114
|
if (r instanceof Map) {
|
|
10
115
|
r = Object.fromEntries(r);
|
|
@@ -419,4 +524,3 @@ describe('Pathfinder', function () {
|
|
|
419
524
|
|
|
420
525
|
});
|
|
421
526
|
});
|
|
422
|
-
|