@loadbare/app 0.5.6 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -4
- package/dist/build/assemble.d.ts +1 -1
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +80 -7
- package/dist/build/cli.d.ts +2 -2
- package/dist/build/cli.js +2 -2
- package/dist/build/expand.d.ts.map +1 -1
- package/dist/build/expand.js +19 -32
- package/dist/build/locations.d.ts +3 -3
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +15 -3
- package/dist/build/origins.d.ts +0 -13
- package/dist/build/origins.d.ts.map +1 -1
- package/dist/build/origins.js +32 -8
- package/dist/build/pages.d.ts +3 -3
- package/dist/build/pages.d.ts.map +1 -1
- package/dist/build/pages.js +9 -7
- package/dist/core/lb-constants.d.ts +15 -13
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +103 -53
- package/dist/core/lb-types.d.ts +103 -62
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/core/lb-types.js +11 -3
- package/dist/hub/lb-apply.d.ts +18 -4
- package/dist/hub/lb-apply.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +184 -34
- package/dist/hub/lb-hub.browser.d.ts +1 -1
- package/dist/hub/lb-hub.browser.d.ts.map +1 -1
- package/dist/hub/lb-hub.browser.js +131 -84
- package/dist/server/lb-express.d.ts +7 -4
- package/dist/server/lb-express.d.ts.map +1 -1
- package/dist/server/lb-express.js +45 -33
- package/dist/server/lb-server.d.ts +67 -45
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/server/lb-server.js +55 -18
- package/dist/tests/assemble.test.js +154 -2
- package/dist/tests/expand.test.js +6 -3
- package/dist/tests/helpers/hub.d.ts +73 -0
- package/dist/tests/helpers/hub.d.ts.map +1 -0
- package/dist/tests/helpers/hub.js +151 -0
- package/dist/tests/lb-apply.test.js +86 -62
- package/dist/tests/lb-express.test.d.ts +1 -1
- package/dist/tests/lb-express.test.js +43 -38
- package/dist/tests/lb-hub.test.d.ts +14 -0
- package/dist/tests/lb-hub.test.d.ts.map +1 -0
- package/dist/tests/lb-hub.test.js +319 -0
- package/dist/tests/{lb-rows.test.d.ts → lb-list.test.d.ts} +1 -1
- package/dist/tests/lb-list.test.d.ts.map +1 -0
- package/dist/tests/{lb-rows.test.js → lb-list.test.js} +109 -106
- package/dist/tests/lb-server.test.js +151 -100
- package/dist/tests/origins.test.js +19 -1
- package/dist/tests/pages.test.d.ts +1 -1
- package/dist/tests/pages.test.js +64 -14
- package/docs/TECHREF-1.0.md +1000 -0
- package/docs/reference/builder.md +4 -4
- package/docs/reference/chrome.md +10 -9
- package/docs/reference/custom-elements.md +51 -36
- package/docs/reference/data-binding.md +141 -88
- package/docs/reference/overview.md +1 -1
- package/docs/reference/page-files.md +64 -49
- package/docs/reference/server.md +7 -6
- package/docs/reference/widgets.md +22 -30
- package/docs/roadmap.md +68 -22
- package/docs/testing.md +47 -17
- package/docs/theory.md +2 -2
- package/docs/tutorials/010-pages-and-navigation.md +8 -8
- package/docs/tutorials/040-displaying-data.md +9 -9
- package/docs/tutorials/050-actions.md +5 -5
- package/docs/tutorials/060-custom-element-code.md +1 -1
- package/docs/tutorials/065-conditional-rendering.md +4 -4
- package/docs/tutorials/070-displaying-a-list.md +24 -47
- package/docs/tutorials/072-inserting-into-a-list.md +17 -17
- package/docs/tutorials/074-deleting-from-a-list.md +15 -17
- package/docs/tutorials/076-updating-a-list-item.md +20 -22
- package/docs/tutorials/080-widget-requests.md +27 -23
- package/docs/tutorials/090-using-widget-libraries.md +1 -1
- package/package.json +1 -2
- package/dist/hub/lb-rows.d.ts +0 -18
- package/dist/hub/lb-rows.d.ts.map +0 -1
- package/dist/hub/lb-rows.js +0 -106
- package/dist/tests/lb-rows.test.d.ts.map +0 -1
|
@@ -10,36 +10,40 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import assert from "node:assert/strict";
|
|
12
12
|
import { describe, it } from "node:test";
|
|
13
|
-
import {
|
|
13
|
+
import { applyList } from "../hub/lb-apply";
|
|
14
14
|
import { body } from "./helpers/dom";
|
|
15
15
|
import { captured } from "./helpers/console";
|
|
16
16
|
/**
|
|
17
|
-
* A list
|
|
18
|
-
*
|
|
19
|
-
* template staying put is part of
|
|
17
|
+
* A list scope: the template a developer wrote, and nothing else. No widget,
|
|
18
|
+
* because the hub reconciles a list itself and the default placement puts
|
|
19
|
+
* rows in front of the template — so the template staying put is part of
|
|
20
|
+
* what is under test.
|
|
20
21
|
*/
|
|
21
22
|
function list(template = `<template lb-key="id"><p><span lb-cell="name"></span></p></template>`) {
|
|
22
|
-
return body(`<lb-list>${template}</
|
|
23
|
+
return body(`<div lb-list="members">${template}</div>`).firstElementChild;
|
|
24
|
+
}
|
|
25
|
+
/** The same scope, with a widget's placement hook on it. */
|
|
26
|
+
function placing(lbPlaceRow, template) {
|
|
27
|
+
const scope = list(template);
|
|
28
|
+
scope.lbPlaceRow = lbPlaceRow;
|
|
29
|
+
return scope;
|
|
23
30
|
}
|
|
24
31
|
/** The keys of the rows showing, in document order. */
|
|
25
32
|
function keys(scope) {
|
|
26
|
-
return [...scope.querySelectorAll(`[lb-key]`)]
|
|
27
|
-
.filter((el) => el.localName !== "template")
|
|
28
|
-
.map((el) => el.getAttribute("lb-key"));
|
|
33
|
+
return [...scope.querySelectorAll(`[lb-key-value]`)].map((el) => el.getAttribute("lb-key-value"));
|
|
29
34
|
}
|
|
30
35
|
/** What each row is showing, in document order. */
|
|
31
36
|
function shown(scope) {
|
|
32
|
-
return [...scope.querySelectorAll(`[lb-key]`)]
|
|
33
|
-
.filter((el) => el.localName !== "template")
|
|
34
|
-
.map((el) => el.textContent.trim());
|
|
37
|
+
return [...scope.querySelectorAll(`[lb-key-value]`)].map((el) => el.textContent.trim());
|
|
35
38
|
}
|
|
36
|
-
|
|
37
|
-
const
|
|
39
|
+
/** A whole set is the array itself; `Array.isArray` is the discriminant. */
|
|
40
|
+
const rows = (set) => set;
|
|
41
|
+
const patch = (change) => change;
|
|
38
42
|
// --- The whole set -------------------------------------------------------
|
|
39
43
|
describe("a rows result", () => {
|
|
40
|
-
it("clones the template once per
|
|
44
|
+
it("clones the template once per row and fills it", () => {
|
|
41
45
|
const scope = list();
|
|
42
|
-
|
|
46
|
+
applyList(scope, rows([
|
|
43
47
|
{ id: "1", name: "Ada" },
|
|
44
48
|
{ id: "2", name: "Bab" },
|
|
45
49
|
]));
|
|
@@ -47,35 +51,34 @@ describe("a rows result", () => {
|
|
|
47
51
|
assert.deepEqual(shown(scope), ["Ada", "Bab"]);
|
|
48
52
|
});
|
|
49
53
|
it("carries the key value on the live row", () => {
|
|
50
|
-
// lb-key names the key column on the template
|
|
51
|
-
// on
|
|
52
|
-
// template is not a row.
|
|
54
|
+
// lb-key names the key column on the template; lb-key-value is
|
|
55
|
+
// stamped on the row with that row's value of it.
|
|
53
56
|
const scope = list();
|
|
54
|
-
|
|
57
|
+
applyList(scope, rows([{ id: "7", name: "Ada" }]));
|
|
55
58
|
const row = scope.querySelector("p");
|
|
56
|
-
assert.equal(row.getAttribute("lb-key"), "7");
|
|
59
|
+
assert.equal(row.getAttribute("lb-key-value"), "7");
|
|
57
60
|
});
|
|
58
61
|
it("leaves the template where it was, after the rows", () => {
|
|
59
62
|
const scope = list();
|
|
60
|
-
|
|
63
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
61
64
|
assert.equal(scope.lastElementChild.localName, "template");
|
|
62
65
|
});
|
|
63
66
|
it("decides membership: a key that did not arrive is gone", () => {
|
|
64
67
|
const scope = list();
|
|
65
|
-
|
|
68
|
+
applyList(scope, rows([
|
|
66
69
|
{ id: "1", name: "Ada" },
|
|
67
70
|
{ id: "2", name: "Bab" },
|
|
68
71
|
]));
|
|
69
|
-
|
|
72
|
+
applyList(scope, rows([{ id: "2", name: "Bab" }]));
|
|
70
73
|
assert.deepEqual(keys(scope), ["2"]);
|
|
71
74
|
});
|
|
72
75
|
it("decides order: a row that already exists is placed again", () => {
|
|
73
76
|
const scope = list();
|
|
74
|
-
|
|
77
|
+
applyList(scope, rows([
|
|
75
78
|
{ id: "1", name: "Ada" },
|
|
76
79
|
{ id: "2", name: "Bab" },
|
|
77
80
|
]));
|
|
78
|
-
|
|
81
|
+
applyList(scope, rows([
|
|
79
82
|
{ id: "2", name: "Bab" },
|
|
80
83
|
{ id: "1", name: "Ada" },
|
|
81
84
|
]));
|
|
@@ -84,16 +87,16 @@ describe("a rows result", () => {
|
|
|
84
87
|
it("updates a row it keeps rather than replacing it", () => {
|
|
85
88
|
// The row is the same element, so anything a widget put on it survives.
|
|
86
89
|
const scope = list();
|
|
87
|
-
|
|
90
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
88
91
|
const before = scope.querySelector("p");
|
|
89
|
-
|
|
92
|
+
applyList(scope, rows([{ id: "1", name: "Grace" }]));
|
|
90
93
|
assert.equal(scope.querySelector("p"), before, "same element");
|
|
91
94
|
assert.deepEqual(shown(scope), ["Grace"]);
|
|
92
95
|
});
|
|
93
96
|
it("empties the list when the set is empty", () => {
|
|
94
97
|
const scope = list();
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
99
|
+
applyList(scope, rows([]));
|
|
97
100
|
assert.deepEqual(keys(scope), []);
|
|
98
101
|
assert.equal(scope.querySelector("template"), scope.firstElementChild);
|
|
99
102
|
});
|
|
@@ -102,33 +105,33 @@ describe("a rows result", () => {
|
|
|
102
105
|
describe("a patch result", () => {
|
|
103
106
|
it("adds a row it names", () => {
|
|
104
107
|
const scope = list();
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
109
|
+
applyList(scope, patch({ rows: [{ id: "2", name: "Bab" }] }));
|
|
107
110
|
assert.deepEqual(keys(scope), ["1", "2"]);
|
|
108
111
|
});
|
|
109
112
|
it("updates a row it names", () => {
|
|
110
113
|
const scope = list();
|
|
111
|
-
|
|
112
|
-
|
|
114
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
115
|
+
applyList(scope, patch({ rows: [{ id: "1", name: "Grace" }] }));
|
|
113
116
|
assert.deepEqual(shown(scope), ["Grace"]);
|
|
114
117
|
});
|
|
115
118
|
it("drops a row it names", () => {
|
|
116
119
|
const scope = list();
|
|
117
|
-
|
|
120
|
+
applyList(scope, rows([
|
|
118
121
|
{ id: "1", name: "Ada" },
|
|
119
122
|
{ id: "2", name: "Bab" },
|
|
120
123
|
]));
|
|
121
|
-
|
|
124
|
+
applyList(scope, patch({ drop: ["1"] }));
|
|
122
125
|
assert.deepEqual(keys(scope), ["2"]);
|
|
123
126
|
});
|
|
124
127
|
it("leaves a row it did not name, in contents and in place", () => {
|
|
125
128
|
const scope = list();
|
|
126
|
-
|
|
129
|
+
applyList(scope, rows([
|
|
127
130
|
{ id: "1", name: "Ada" },
|
|
128
131
|
{ id: "2", name: "Bab" },
|
|
129
132
|
{ id: "3", name: "Cai" },
|
|
130
133
|
]));
|
|
131
|
-
|
|
134
|
+
applyList(scope, patch({ rows: [{ id: "2", name: "Grace" }] }));
|
|
132
135
|
assert.deepEqual(keys(scope), ["1", "2", "3"]);
|
|
133
136
|
assert.deepEqual(shown(scope), ["Ada", "Grace", "Cai"]);
|
|
134
137
|
});
|
|
@@ -137,55 +140,52 @@ describe("a patch result", () => {
|
|
|
137
140
|
// sort column changed stays where it was. This test states what is true
|
|
138
141
|
// now, and is the one that flips when that item closes.
|
|
139
142
|
const placed = [];
|
|
140
|
-
const
|
|
141
|
-
placed.push(
|
|
142
|
-
template.parentElement.insertBefore(
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
applyRows(scope, rows([
|
|
143
|
+
const scope = placing((el, _row, template) => {
|
|
144
|
+
placed.push(el.getAttribute("lb-key-value"));
|
|
145
|
+
template.parentElement.insertBefore(el, template);
|
|
146
|
+
});
|
|
147
|
+
applyList(scope, rows([
|
|
146
148
|
{ id: "1", name: "Ada" },
|
|
147
149
|
{ id: "2", name: "Bab" },
|
|
148
|
-
])
|
|
150
|
+
]));
|
|
149
151
|
placed.length = 0;
|
|
150
|
-
|
|
152
|
+
applyList(scope, patch({ rows: [{ id: "1", name: "Grace" }] }));
|
|
151
153
|
assert.deepEqual(placed, [], "an existing row is filled, never placed");
|
|
152
154
|
});
|
|
153
155
|
it("places a row it has not seen before", () => {
|
|
154
156
|
const placed = [];
|
|
155
|
-
const
|
|
156
|
-
placed.push(
|
|
157
|
-
template.parentElement.insertBefore(
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
applyRows(scope, rows([{ id: "1", name: "Ada" }]), place);
|
|
157
|
+
const scope = placing((el, _row, template) => {
|
|
158
|
+
placed.push(el.getAttribute("lb-key-value"));
|
|
159
|
+
template.parentElement.insertBefore(el, template);
|
|
160
|
+
});
|
|
161
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
161
162
|
placed.length = 0;
|
|
162
|
-
|
|
163
|
+
applyList(scope, patch({ rows: [{ id: "2", name: "Bab" }] }));
|
|
163
164
|
assert.deepEqual(placed, ["2"]);
|
|
164
165
|
});
|
|
165
166
|
it("ignores a drop for a row that is not showing", () => {
|
|
166
167
|
const scope = list();
|
|
167
|
-
|
|
168
|
-
assert.doesNotThrow(() =>
|
|
168
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
169
|
+
assert.doesNotThrow(() => applyList(scope, patch({ drop: ["9"] })));
|
|
169
170
|
assert.deepEqual(keys(scope), ["1"]);
|
|
170
171
|
});
|
|
171
172
|
it("naming nothing changes nothing", () => {
|
|
172
173
|
const scope = list();
|
|
173
|
-
|
|
174
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
174
175
|
const before = scope.innerHTML;
|
|
175
|
-
|
|
176
|
+
applyList(scope, patch({}));
|
|
176
177
|
assert.equal(scope.innerHTML, before);
|
|
177
178
|
});
|
|
178
179
|
});
|
|
179
180
|
// --- Placement -----------------------------------------------------------
|
|
180
181
|
describe("placement", () => {
|
|
181
|
-
it("hands the widget the
|
|
182
|
+
it("hands the widget the element, the row and the template", () => {
|
|
182
183
|
const seen = [];
|
|
183
|
-
const
|
|
184
|
-
seen.push([
|
|
185
|
-
template.parentElement.insertBefore(
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
applyRows(scope, rows([{ id: "1", name: "Ada" }]), place);
|
|
184
|
+
const scope = placing((el, row, template) => {
|
|
185
|
+
seen.push([el.localName, row, template.localName]);
|
|
186
|
+
template.parentElement.insertBefore(el, template);
|
|
187
|
+
});
|
|
188
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
189
189
|
assert.deepEqual(seen, [["p", { id: "1", name: "Ada" }, "template"]]);
|
|
190
190
|
});
|
|
191
191
|
it("fills the row before the widget places it", () => {
|
|
@@ -194,20 +194,20 @@ describe("placement", () => {
|
|
|
194
194
|
// else. A widget that reads a cell to decide where the row goes — every
|
|
195
195
|
// sorting or grouping widget — depends on this ordering.
|
|
196
196
|
let atPlacement = "";
|
|
197
|
-
const
|
|
198
|
-
atPlacement =
|
|
199
|
-
template.parentElement.insertBefore(
|
|
200
|
-
};
|
|
201
|
-
|
|
197
|
+
const scope = placing((el, _row, template) => {
|
|
198
|
+
atPlacement = el.textContent.trim();
|
|
199
|
+
template.parentElement.insertBefore(el, template);
|
|
200
|
+
});
|
|
201
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
202
202
|
assert.equal(atPlacement, "Ada");
|
|
203
203
|
});
|
|
204
204
|
it("hands the widget a row that is not yet in the document", () => {
|
|
205
205
|
let connected = true;
|
|
206
|
-
const
|
|
207
|
-
connected =
|
|
208
|
-
template.parentElement.insertBefore(
|
|
209
|
-
};
|
|
210
|
-
|
|
206
|
+
const scope = placing((el, _row, template) => {
|
|
207
|
+
connected = el.parentElement !== null;
|
|
208
|
+
template.parentElement.insertBefore(el, template);
|
|
209
|
+
});
|
|
210
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
211
211
|
assert.equal(connected, false, "the row arrives detached");
|
|
212
212
|
});
|
|
213
213
|
});
|
|
@@ -219,68 +219,71 @@ describe("the row count", () => {
|
|
|
219
219
|
// that "no members yet" is a stylesheet rule rather than code in every
|
|
220
220
|
// list widget.
|
|
221
221
|
const scope = list();
|
|
222
|
-
|
|
222
|
+
applyList(scope, rows([
|
|
223
223
|
{ id: "1", name: "Ada" },
|
|
224
224
|
{ id: "2", name: "Bab" },
|
|
225
225
|
]));
|
|
226
|
-
assert.equal(scope.getAttribute("
|
|
226
|
+
assert.equal(scope.getAttribute("lb-row-count"), "2");
|
|
227
227
|
});
|
|
228
228
|
it("is zero for an empty set", () => {
|
|
229
229
|
const scope = list();
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
assert.equal(scope.getAttribute("
|
|
230
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
231
|
+
applyList(scope, rows([]));
|
|
232
|
+
assert.equal(scope.getAttribute("lb-row-count"), "0");
|
|
233
233
|
});
|
|
234
234
|
it("reports the same fact the same way after a set and after a patch", () => {
|
|
235
235
|
const viaRows = list();
|
|
236
|
-
|
|
236
|
+
applyList(viaRows, rows([
|
|
237
237
|
{ id: "1", name: "Ada" },
|
|
238
238
|
{ id: "2", name: "Bab" },
|
|
239
239
|
]));
|
|
240
240
|
const viaPatch = list();
|
|
241
|
-
|
|
241
|
+
applyList(viaPatch, rows([
|
|
242
242
|
{ id: "1", name: "Ada" },
|
|
243
243
|
{ id: "3", name: "Cai" },
|
|
244
244
|
]));
|
|
245
|
-
|
|
246
|
-
assert.equal(viaPatch.getAttribute("
|
|
245
|
+
applyList(viaPatch, patch({ rows: [{ id: "2", name: "Bab" }], drop: ["3"] }));
|
|
246
|
+
assert.equal(viaPatch.getAttribute("lb-row-count"), viaRows.getAttribute("lb-row-count"));
|
|
247
247
|
});
|
|
248
248
|
});
|
|
249
249
|
// --- What it refuses to guess at ----------------------------------------
|
|
250
|
-
describe("
|
|
251
|
-
it("
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
250
|
+
describe("applyList reports and stops", () => {
|
|
251
|
+
it("says nothing about a scope with no row template", async () => {
|
|
252
|
+
// Bound to the list without displaying it: an insert form names the list
|
|
253
|
+
// it adds a row to, and has no rows of its own. Not a mistake, so not a
|
|
254
|
+
// message.
|
|
255
|
+
const scope = body(`<div></div>`).firstElementChild;
|
|
256
|
+
const { error } = await captured(() => applyList(scope, rows([{ id: "1" }])));
|
|
257
|
+
assert.deepEqual(error, []);
|
|
255
258
|
assert.equal(scope.children.length, 0, "nothing was invented");
|
|
256
259
|
});
|
|
257
260
|
it("when the template does not name the key column", async () => {
|
|
258
261
|
const scope = list(`<template><p></p></template>`);
|
|
259
|
-
const { error } = await captured(() =>
|
|
262
|
+
const { error } = await captured(() => applyList(scope, rows([{ id: "1" }])));
|
|
260
263
|
assert.match(error.join("\n"), /has no lb-key naming the column that identifies a row/);
|
|
261
264
|
assert.deepEqual(keys(scope), []);
|
|
262
265
|
});
|
|
263
266
|
it("does not stamp a count it could not compute", async () => {
|
|
264
|
-
const scope = body(`<
|
|
265
|
-
await captured(() =>
|
|
266
|
-
assert.equal(scope.hasAttribute("
|
|
267
|
+
const scope = body(`<div></div>`).firstElementChild;
|
|
268
|
+
await captured(() => applyList(scope, rows([{ id: "1" }])));
|
|
269
|
+
assert.equal(scope.hasAttribute("lb-row-count"), false);
|
|
267
270
|
});
|
|
268
|
-
it("when a
|
|
271
|
+
it("when a row has no key cell, and lands its siblings anyway", async () => {
|
|
269
272
|
const scope = list();
|
|
270
|
-
const { error } = await captured(() =>
|
|
273
|
+
const { error } = await captured(() => applyList(scope, rows([
|
|
271
274
|
{ id: "1", name: "Ada" },
|
|
272
275
|
{ name: "nameless" },
|
|
273
276
|
{ id: "2", name: "Bab" },
|
|
274
277
|
])));
|
|
275
|
-
assert.match(error.join("\n"), /a
|
|
278
|
+
assert.match(error.join("\n"), /a row for <div> has no 'id' column/);
|
|
276
279
|
assert.deepEqual(keys(scope), ["1", "2"]);
|
|
277
280
|
});
|
|
278
|
-
it("and a keyless
|
|
279
|
-
// A
|
|
281
|
+
it("and a keyless row does not take a surviving row down with it", async () => {
|
|
282
|
+
// A row with no key contributes no key to the arrived set, so the
|
|
280
283
|
// reconciliation that follows must not read its absence as a removal.
|
|
281
284
|
const scope = list();
|
|
282
|
-
|
|
283
|
-
await captured(() =>
|
|
285
|
+
applyList(scope, rows([{ id: "1", name: "Ada" }]));
|
|
286
|
+
await captured(() => applyList(scope, rows([{ id: "1", name: "Ada" }, { name: "nameless" }])));
|
|
284
287
|
assert.deepEqual(keys(scope), ["1"]);
|
|
285
288
|
});
|
|
286
289
|
});
|
|
@@ -293,12 +296,12 @@ describe("reconciliation converges", () => {
|
|
|
293
296
|
];
|
|
294
297
|
it("so applying the same set twice is applying it once", () => {
|
|
295
298
|
const once = list();
|
|
296
|
-
|
|
299
|
+
applyList(once, rows(SET));
|
|
297
300
|
const twice = list();
|
|
298
|
-
|
|
299
|
-
|
|
301
|
+
applyList(twice, rows(SET));
|
|
302
|
+
applyList(twice, rows(SET));
|
|
300
303
|
assert.equal(twice.innerHTML, once.innerHTML);
|
|
301
|
-
assert.equal(twice.getAttribute("
|
|
304
|
+
assert.equal(twice.getAttribute("lb-row-count"), once.getAttribute("lb-row-count"));
|
|
302
305
|
});
|
|
303
306
|
it("so a set reached through any patches is the set reached from empty", () => {
|
|
304
307
|
// Every history below ends at SET. A reconciler that leaks state — a row
|
|
@@ -322,15 +325,15 @@ describe("reconciliation converges", () => {
|
|
|
322
325
|
[patch({ rows: [{ id: "4", name: "Dot" }] }), patch({ drop: ["4"] })],
|
|
323
326
|
];
|
|
324
327
|
const fresh = list();
|
|
325
|
-
|
|
328
|
+
applyList(fresh, rows(SET));
|
|
326
329
|
const expected = fresh.innerHTML;
|
|
327
330
|
for (const [index, history] of histories.entries()) {
|
|
328
331
|
const scope = list();
|
|
329
332
|
for (const step of history)
|
|
330
|
-
|
|
331
|
-
|
|
333
|
+
applyList(scope, step);
|
|
334
|
+
applyList(scope, rows(SET));
|
|
332
335
|
assert.equal(scope.innerHTML, expected, `history ${index} did not converge`);
|
|
333
|
-
assert.equal(scope.getAttribute("
|
|
336
|
+
assert.equal(scope.getAttribute("lb-row-count"), String(SET.length));
|
|
334
337
|
}
|
|
335
338
|
});
|
|
336
339
|
});
|