@loadbare/app 0.5.5 → 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.
Files changed (81) hide show
  1. package/README.md +3 -4
  2. package/dist/build/assemble.d.ts +1 -1
  3. package/dist/build/assemble.d.ts.map +1 -1
  4. package/dist/build/assemble.js +80 -7
  5. package/dist/build/cli.d.ts +2 -2
  6. package/dist/build/cli.js +2 -2
  7. package/dist/build/expand.d.ts.map +1 -1
  8. package/dist/build/expand.js +30 -34
  9. package/dist/build/locations.d.ts +3 -3
  10. package/dist/build/locations.d.ts.map +1 -1
  11. package/dist/build/locations.js +15 -3
  12. package/dist/build/origins.d.ts +0 -13
  13. package/dist/build/origins.d.ts.map +1 -1
  14. package/dist/build/origins.js +32 -8
  15. package/dist/build/pages.d.ts +3 -3
  16. package/dist/build/pages.d.ts.map +1 -1
  17. package/dist/build/pages.js +9 -7
  18. package/dist/core/lb-constants.d.ts +17 -12
  19. package/dist/core/lb-constants.d.ts.map +1 -1
  20. package/dist/core/lb-constants.js +106 -43
  21. package/dist/core/lb-types.d.ts +103 -62
  22. package/dist/core/lb-types.d.ts.map +1 -1
  23. package/dist/core/lb-types.js +11 -3
  24. package/dist/hub/lb-apply.d.ts +18 -4
  25. package/dist/hub/lb-apply.d.ts.map +1 -1
  26. package/dist/hub/lb-apply.js +184 -34
  27. package/dist/hub/lb-hub.browser.d.ts +1 -1
  28. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  29. package/dist/hub/lb-hub.browser.js +165 -83
  30. package/dist/server/lb-express.d.ts +7 -4
  31. package/dist/server/lb-express.d.ts.map +1 -1
  32. package/dist/server/lb-express.js +45 -33
  33. package/dist/server/lb-server.d.ts +67 -45
  34. package/dist/server/lb-server.d.ts.map +1 -1
  35. package/dist/server/lb-server.js +55 -18
  36. package/dist/tests/assemble.test.js +154 -2
  37. package/dist/tests/expand.test.js +30 -3
  38. package/dist/tests/helpers/hub.d.ts +73 -0
  39. package/dist/tests/helpers/hub.d.ts.map +1 -0
  40. package/dist/tests/helpers/hub.js +151 -0
  41. package/dist/tests/lb-apply.test.js +86 -62
  42. package/dist/tests/lb-express.test.d.ts +1 -1
  43. package/dist/tests/lb-express.test.js +43 -38
  44. package/dist/tests/lb-hub.test.d.ts +14 -0
  45. package/dist/tests/lb-hub.test.d.ts.map +1 -0
  46. package/dist/tests/lb-hub.test.js +319 -0
  47. package/dist/tests/{lb-rows.test.d.ts → lb-list.test.d.ts} +1 -1
  48. package/dist/tests/lb-list.test.d.ts.map +1 -0
  49. package/dist/tests/{lb-rows.test.js → lb-list.test.js} +109 -106
  50. package/dist/tests/lb-server.test.js +151 -100
  51. package/dist/tests/origins.test.js +19 -1
  52. package/dist/tests/pages.test.d.ts +1 -1
  53. package/dist/tests/pages.test.js +64 -14
  54. package/docs/TECHREF-1.0.md +1000 -0
  55. package/docs/reference/builder.md +4 -4
  56. package/docs/reference/chrome.md +56 -5
  57. package/docs/reference/custom-elements.md +59 -36
  58. package/docs/reference/data-binding.md +142 -84
  59. package/docs/reference/overview.md +1 -1
  60. package/docs/reference/page-files.md +64 -49
  61. package/docs/reference/server.md +7 -6
  62. package/docs/reference/widgets.md +43 -32
  63. package/docs/roadmap.md +68 -22
  64. package/docs/testing.md +47 -17
  65. package/docs/theory.md +2 -2
  66. package/docs/tutorials/010-pages-and-navigation.md +14 -8
  67. package/docs/tutorials/040-displaying-data.md +9 -9
  68. package/docs/tutorials/050-actions.md +5 -5
  69. package/docs/tutorials/060-custom-element-code.md +1 -1
  70. package/docs/tutorials/065-conditional-rendering.md +4 -4
  71. package/docs/tutorials/070-displaying-a-list.md +24 -47
  72. package/docs/tutorials/072-inserting-into-a-list.md +17 -17
  73. package/docs/tutorials/074-deleting-from-a-list.md +15 -17
  74. package/docs/tutorials/076-updating-a-list-item.md +20 -22
  75. package/docs/tutorials/080-widget-requests.md +27 -23
  76. package/docs/tutorials/090-using-widget-libraries.md +23 -1
  77. package/package.json +1 -2
  78. package/dist/hub/lb-rows.d.ts +0 -18
  79. package/dist/hub/lb-rows.d.ts.map +0 -1
  80. package/dist/hub/lb-rows.js +0 -106
  81. 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 { applyRows } from "../hub/lb-rows";
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 widget's subtree: the template a page author wrote, and nothing
18
- * else. The default `place` puts rows in front of the template, so the
19
- * template staying put is part of what is under test.
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}</lb-list>`).firstElementChild;
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
- const rows = (list) => ({ op: "rows", rows: list });
37
- const patch = (change) => ({ op: "patch", ...change });
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 tuple and fills it", () => {
44
+ it("clones the template once per row and fills it", () => {
41
45
  const scope = list();
42
- applyRows(scope, rows([
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 and carries the key value
51
- // on a row. One name in two positions, never ambiguous, because a
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
- applyRows(scope, rows([{ id: "7", name: "Ada" }]));
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
- applyRows(scope, rows([{ id: "1", name: "Ada" }]));
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
- applyRows(scope, rows([
68
+ applyList(scope, rows([
66
69
  { id: "1", name: "Ada" },
67
70
  { id: "2", name: "Bab" },
68
71
  ]));
69
- applyRows(scope, rows([{ id: "2", name: "Bab" }]));
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
- applyRows(scope, rows([
77
+ applyList(scope, rows([
75
78
  { id: "1", name: "Ada" },
76
79
  { id: "2", name: "Bab" },
77
80
  ]));
78
- applyRows(scope, rows([
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
- applyRows(scope, rows([{ id: "1", name: "Ada" }]));
90
+ applyList(scope, rows([{ id: "1", name: "Ada" }]));
88
91
  const before = scope.querySelector("p");
89
- applyRows(scope, rows([{ id: "1", name: "Grace" }]));
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
- applyRows(scope, rows([{ id: "1", name: "Ada" }]));
96
- applyRows(scope, rows([]));
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
- applyRows(scope, rows([{ id: "1", name: "Ada" }]));
106
- applyRows(scope, patch({ rows: [{ id: "2", name: "Bab" }] }));
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
- applyRows(scope, rows([{ id: "1", name: "Ada" }]));
112
- applyRows(scope, patch({ rows: [{ id: "1", name: "Grace" }] }));
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
- applyRows(scope, rows([
120
+ applyList(scope, rows([
118
121
  { id: "1", name: "Ada" },
119
122
  { id: "2", name: "Bab" },
120
123
  ]));
121
- applyRows(scope, patch({ drop: ["1"] }));
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
- applyRows(scope, rows([
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
- applyRows(scope, patch({ rows: [{ id: "2", name: "Grace" }] }));
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 place = (row, _tuple, template) => {
141
- placed.push(row.getAttribute("lb-key"));
142
- template.parentElement.insertBefore(row, template);
143
- };
144
- const scope = list();
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
- ]), place);
150
+ ]));
149
151
  placed.length = 0;
150
- applyRows(scope, patch({ rows: [{ id: "1", name: "Grace" }] }), place);
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 place = (row, _tuple, template) => {
156
- placed.push(row.getAttribute("lb-key"));
157
- template.parentElement.insertBefore(row, template);
158
- };
159
- const scope = list();
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
- applyRows(scope, patch({ rows: [{ id: "2", name: "Bab" }] }), place);
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
- applyRows(scope, rows([{ id: "1", name: "Ada" }]));
168
- assert.doesNotThrow(() => applyRows(scope, patch({ drop: ["9"] })));
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
- applyRows(scope, rows([{ id: "1", name: "Ada" }]));
174
+ applyList(scope, rows([{ id: "1", name: "Ada" }]));
174
175
  const before = scope.innerHTML;
175
- applyRows(scope, patch({}));
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 row, the tuple and the template", () => {
182
+ it("hands the widget the element, the row and the template", () => {
182
183
  const seen = [];
183
- const place = (row, tuple, template) => {
184
- seen.push([row.localName, tuple, template.localName]);
185
- template.parentElement.insertBefore(row, template);
186
- };
187
- const scope = list();
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 place = (row, _tuple, template) => {
198
- atPlacement = row.textContent.trim();
199
- template.parentElement.insertBefore(row, template);
200
- };
201
- applyRows(list(), rows([{ id: "1", name: "Ada" }]), place);
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 place = (row, _tuple, template) => {
207
- connected = row.parentElement !== null;
208
- template.parentElement.insertBefore(row, template);
209
- };
210
- applyRows(list(), rows([{ id: "1", name: "Ada" }]), place);
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
- applyRows(scope, rows([
222
+ applyList(scope, rows([
223
223
  { id: "1", name: "Ada" },
224
224
  { id: "2", name: "Bab" },
225
225
  ]));
226
- assert.equal(scope.getAttribute("data-rows"), "2");
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
- applyRows(scope, rows([{ id: "1", name: "Ada" }]));
231
- applyRows(scope, rows([]));
232
- assert.equal(scope.getAttribute("data-rows"), "0");
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
- applyRows(viaRows, rows([
236
+ applyList(viaRows, rows([
237
237
  { id: "1", name: "Ada" },
238
238
  { id: "2", name: "Bab" },
239
239
  ]));
240
240
  const viaPatch = list();
241
- applyRows(viaPatch, rows([
241
+ applyList(viaPatch, rows([
242
242
  { id: "1", name: "Ada" },
243
243
  { id: "3", name: "Cai" },
244
244
  ]));
245
- applyRows(viaPatch, patch({ rows: [{ id: "2", name: "Bab" }], drop: ["3"] }));
246
- assert.equal(viaPatch.getAttribute("data-rows"), viaRows.getAttribute("data-rows"));
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("applyRows reports and stops", () => {
251
- it("when the widget has no template to clone", async () => {
252
- const scope = body(`<lb-list></lb-list>`).firstElementChild;
253
- const { error } = await captured(() => applyRows(scope, rows([{ id: "1" }])));
254
- assert.match(error.join("\n"), /<lb-list> has no <template> to clone/);
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(() => applyRows(scope, rows([{ id: "1" }])));
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(`<lb-list></lb-list>`).firstElementChild;
265
- await captured(() => applyRows(scope, rows([{ id: "1" }])));
266
- assert.equal(scope.hasAttribute("data-rows"), false);
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 tuple has no key cell, and lands its siblings anyway", async () => {
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(() => applyRows(scope, rows([
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 tuple for <lb-list> has no 'id' cell/);
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 tuple does not take a surviving row down with it", async () => {
279
- // A tuple with no key contributes no key to the arrived set, so the
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
- applyRows(scope, rows([{ id: "1", name: "Ada" }]));
283
- await captured(() => applyRows(scope, rows([{ id: "1", name: "Ada" }, { name: "nameless" }])));
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
- applyRows(once, rows(SET));
299
+ applyList(once, rows(SET));
297
300
  const twice = list();
298
- applyRows(twice, rows(SET));
299
- applyRows(twice, rows(SET));
301
+ applyList(twice, rows(SET));
302
+ applyList(twice, rows(SET));
300
303
  assert.equal(twice.innerHTML, once.innerHTML);
301
- assert.equal(twice.getAttribute("data-rows"), once.getAttribute("data-rows"));
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
- applyRows(fresh, rows(SET));
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
- applyRows(scope, step);
331
- applyRows(scope, rows(SET));
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("data-rows"), String(SET.length));
336
+ assert.equal(scope.getAttribute("lb-row-count"), String(SET.length));
334
337
  }
335
338
  });
336
339
  });