@schukai/monster 4.147.1 → 4.148.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.
@@ -132,6 +132,86 @@ describe("ChoiceCards", function () {
132
132
  cards.shadowRoot.querySelector('[data-choice-value="api"]').click();
133
133
  });
134
134
 
135
+ it("toggles multiple cards when the multiple option is enabled", function () {
136
+ const cards = document.createElement("monster-choice-cards");
137
+ cards.setOption("multiple", true);
138
+ cards.setItems([
139
+ { value: "assets", label: "Project assets" },
140
+ { value: "api", label: "API collection" },
141
+ { value: "manual", label: "Manual" },
142
+ ]);
143
+
144
+ const changes = [];
145
+ cards.addEventListener("monster-change", (event) => {
146
+ changes.push(event.detail);
147
+ });
148
+
149
+ document.getElementById("test").appendChild(cards);
150
+
151
+ expect(
152
+ cards.shadowRoot
153
+ .querySelector('[data-monster-role="control"]')
154
+ .getAttribute("role"),
155
+ ).is.equal("group");
156
+ expect(
157
+ cards.shadowRoot
158
+ .querySelector('[data-choice-value="assets"]')
159
+ .getAttribute("role"),
160
+ ).is.equal("checkbox");
161
+
162
+ cards.shadowRoot.querySelector('[data-choice-value="assets"]').click();
163
+ cards.shadowRoot.querySelector('[data-choice-value="api"]').click();
164
+ cards.shadowRoot.querySelector('[data-choice-value="assets"]').click();
165
+
166
+ expect(cards.value).deep.equal(["api"]);
167
+ expect(
168
+ cards.shadowRoot
169
+ .querySelector('[data-choice-value="assets"]')
170
+ .getAttribute("aria-checked"),
171
+ ).is.equal("false");
172
+ expect(
173
+ cards.shadowRoot
174
+ .querySelector('[data-choice-value="api"]')
175
+ .getAttribute("aria-checked"),
176
+ ).is.equal("true");
177
+ expect(changes.length).is.equal(3);
178
+ expect(changes[0].value).deep.equal(["assets"]);
179
+ expect(changes[0].selected).is.equal(true);
180
+ expect(changes[2].value).deep.equal(["api"]);
181
+ expect(changes[2].selected).is.equal(false);
182
+ });
183
+
184
+ it("reads multiple values from the value attribute", function () {
185
+ document.getElementById("test").innerHTML = `
186
+ <monster-choice-cards
187
+ id="multi-attribute"
188
+ value="assets::api"
189
+ data-monster-option-multiple="true"
190
+ data-monster-options='{
191
+ "items": [
192
+ { "value": "assets", "label": "Assets" },
193
+ { "value": "api", "label": "API" },
194
+ { "value": "manual", "label": "Manual" }
195
+ ]
196
+ }'
197
+ ></monster-choice-cards>
198
+ `;
199
+
200
+ const cards = document.getElementById("multi-attribute");
201
+
202
+ expect(cards.value).deep.equal(["assets", "api"]);
203
+ expect(
204
+ cards.shadowRoot
205
+ .querySelector('[data-choice-value="assets"]')
206
+ .getAttribute("aria-checked"),
207
+ ).is.equal("true");
208
+ expect(
209
+ cards.shadowRoot
210
+ .querySelector('[data-choice-value="manual"]')
211
+ .getAttribute("aria-checked"),
212
+ ).is.equal("false");
213
+ });
214
+
135
215
  it("reads from and writes back to a form datasource", async function () {
136
216
  this.timeout(6000);
137
217
 
@@ -177,6 +257,80 @@ describe("ChoiceCards", function () {
177
257
  await waitForCondition(() => datasource.data?.[0]?.source === "api");
178
258
  });
179
259
 
260
+ it("writes multiple values back to a form datasource", async function () {
261
+ this.timeout(6000);
262
+
263
+ document.getElementById("test").innerHTML = `
264
+ <monster-datasource-dom id="multi-choice-ds">
265
+ <script type="application/json">
266
+ [
267
+ {
268
+ "sources": []
269
+ }
270
+ ]
271
+ </script>
272
+ </monster-datasource-dom>
273
+ <monster-form
274
+ id="multi-choice-form"
275
+ data-monster-option-datasource-selector="#multi-choice-ds"
276
+ data-monster-option-mapping-data=""
277
+ data-monster-option-mapping-index="0"
278
+ >
279
+ <monster-choice-cards
280
+ id="multi-choice-source"
281
+ data-monster-attributes="value path:data.sources"
282
+ data-monster-bind="path:data.sources"
283
+ data-monster-option-multiple="true"
284
+ data-monster-options='{
285
+ "items": [
286
+ { "value": "assets", "label": "Assets" },
287
+ { "value": "api", "label": "API" },
288
+ { "value": "manual", "label": "Manual" }
289
+ ]
290
+ }'
291
+ ></monster-choice-cards>
292
+ </monster-form>
293
+ `;
294
+
295
+ const datasource = document.getElementById("multi-choice-ds");
296
+ const cards = document.getElementById("multi-choice-source");
297
+
298
+ await waitForCondition(() => Array.isArray(datasource.data?.[0]?.sources));
299
+ await new Promise((resolve) => setTimeout(resolve, 500));
300
+
301
+ cards.select("assets");
302
+ await waitForCondition(
303
+ () => JSON.stringify(datasource.data?.[0]?.sources) === '["assets"]',
304
+ );
305
+
306
+ cards.select("api");
307
+
308
+ await waitForCondition(
309
+ () => JSON.stringify(datasource.data?.[0]?.sources) === '["assets","api"]',
310
+ );
311
+ });
312
+
313
+ it("submits multiple values through form data", function () {
314
+ const form = document.createElement("form");
315
+ const cards = document.createElement("monster-choice-cards");
316
+ cards.setAttribute("name", "sources");
317
+ cards.setOption("multiple", true);
318
+ cards.setItems([
319
+ { value: "assets", label: "Project assets" },
320
+ { value: "api", label: "API collection" },
321
+ { value: "manual", label: "Manual" },
322
+ ]);
323
+
324
+ form.appendChild(cards);
325
+ document.getElementById("test").appendChild(form);
326
+
327
+ cards.select("assets");
328
+ cards.select("api");
329
+
330
+ const formData = new window.FormData(form);
331
+ expect(formData.getAll("sources")).deep.equal(["assets::api"]);
332
+ });
333
+
180
334
  it("does not select disabled items", function () {
181
335
  const cards = document.createElement("monster-choice-cards");
182
336
  cards.setItems([
@@ -189,4 +343,24 @@ describe("ChoiceCards", function () {
189
343
 
190
344
  expect(cards.value).is.equal(null);
191
345
  });
346
+
347
+ it("does not toggle disabled items in multiple mode", function () {
348
+ const cards = document.createElement("monster-choice-cards");
349
+ cards.setOption("multiple", true);
350
+ cards.setItems([
351
+ { value: "assets", label: "Project assets" },
352
+ { value: "api", label: "API collection", disabled: true },
353
+ ]);
354
+
355
+ document.getElementById("test").appendChild(cards);
356
+ cards.select("assets");
357
+ cards.select("api");
358
+
359
+ expect(cards.value).deep.equal(["assets"]);
360
+ expect(
361
+ cards.shadowRoot
362
+ .querySelector('[data-choice-value="api"]')
363
+ .getAttribute("aria-checked"),
364
+ ).is.equal("false");
365
+ });
192
366
  });
@@ -5,6 +5,7 @@ let expect = chai.expect;
5
5
 
6
6
  let resolveClippingBoundaryElement;
7
7
  let applyAdaptiveFloatingElementSize;
8
+ let applyFloatingReferenceVisibility;
8
9
  let createVisibilityRecoveryConfig;
9
10
  let getFloatingVisibleRatio;
10
11
 
@@ -19,6 +20,7 @@ describe("form floating-ui boundary resolution", function () {
19
20
  .then((m) => {
20
21
  resolveClippingBoundaryElement = m.resolveClippingBoundaryElement;
21
22
  applyAdaptiveFloatingElementSize = m.applyAdaptiveFloatingElementSize;
23
+ applyFloatingReferenceVisibility = m.applyFloatingReferenceVisibility;
22
24
  createVisibilityRecoveryConfig = m.createVisibilityRecoveryConfig;
23
25
  getFloatingVisibleRatio = m.getFloatingVisibleRatio;
24
26
  done();
@@ -169,7 +171,8 @@ describe("form floating-ui boundary resolution", function () {
169
171
 
170
172
  popper.style.maxHeight = "300px";
171
173
  content.setAttribute("part", "content");
172
- content.textContent = "A long help text that still needs one readable line.";
174
+ content.textContent =
175
+ "A long help text that still needs one readable line.";
173
176
  content.style.fontSize = "16px";
174
177
  content.style.lineHeight = "24px";
175
178
  popper.appendChild(content);
@@ -476,4 +479,48 @@ describe("form floating-ui boundary resolution", function () {
476
479
  "arrow",
477
480
  ]);
478
481
  });
482
+
483
+ it("should hide a floating element while its reference is clipped", function () {
484
+ const reference = document.createElement("div");
485
+ const popper = document.createElement("div");
486
+ reference.getBoundingClientRect = () => ({
487
+ top: -100,
488
+ left: 10,
489
+ right: 110,
490
+ bottom: -50,
491
+ width: 100,
492
+ height: 50,
493
+ x: 10,
494
+ y: -100,
495
+ });
496
+
497
+ const visible = applyFloatingReferenceVisibility(reference, popper);
498
+
499
+ expect(visible).to.equal(false);
500
+ expect(popper.style.visibility).to.equal("hidden");
501
+ expect(popper.dataset.monsterReferenceHidden).to.equal("true");
502
+ });
503
+
504
+ it("should restore a floating element when its reference is visible again", function () {
505
+ const reference = document.createElement("div");
506
+ const popper = document.createElement("div");
507
+ reference.getBoundingClientRect = () => ({
508
+ top: 10,
509
+ left: 10,
510
+ right: 110,
511
+ bottom: 60,
512
+ width: 100,
513
+ height: 50,
514
+ x: 10,
515
+ y: 10,
516
+ });
517
+ popper.dataset.monsterReferenceHidden = "true";
518
+ popper.style.visibility = "hidden";
519
+
520
+ const visible = applyFloatingReferenceVisibility(reference, popper);
521
+
522
+ expect(visible).to.equal(true);
523
+ expect(popper.style.visibility).to.equal("");
524
+ expect(popper.dataset.monsterReferenceHidden).to.equal(undefined);
525
+ });
479
526
  });
@@ -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 (done) {
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
- setTimeout(() => {
768
- try {
769
- shadowRoot.querySelector('[data-monster-role=container]').click();
770
- setTimeout(() => {
771
- try {
772
- expect(popper.style.minWidth).to.equal('240px');
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 prefer the larger live viewport metrics after a resize', function () {
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: 1024,
1144
- visualHeight: 700,
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(20);
1153
- expect(result.top).to.equal(30);
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