@loadbare/app 0.6.0 → 0.7.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.
Files changed (122) hide show
  1. package/dist/build/assemble.js +1 -0
  2. package/dist/build/assemble.js.map +1 -0
  3. package/dist/build/cli.js +1 -0
  4. package/dist/build/cli.js.map +1 -0
  5. package/dist/build/elements.js +1 -0
  6. package/dist/build/elements.js.map +1 -0
  7. package/dist/build/expand.js +1 -0
  8. package/dist/build/expand.js.map +1 -0
  9. package/dist/build/format.js +1 -0
  10. package/dist/build/format.js.map +1 -0
  11. package/dist/build/locations.d.ts +2 -2
  12. package/dist/build/locations.d.ts.map +1 -1
  13. package/dist/build/locations.js +3 -4
  14. package/dist/build/locations.js.map +1 -0
  15. package/dist/build/origins.js +1 -0
  16. package/dist/build/origins.js.map +1 -0
  17. package/dist/build/package-root.js +1 -0
  18. package/dist/build/package-root.js.map +1 -0
  19. package/dist/build/pages.d.ts +4 -0
  20. package/dist/build/pages.d.ts.map +1 -1
  21. package/dist/build/pages.js +5 -0
  22. package/dist/build/pages.js.map +1 -0
  23. package/dist/build/styles.js +1 -0
  24. package/dist/build/styles.js.map +1 -0
  25. package/dist/core/lb-constants.js +1 -0
  26. package/dist/core/lb-constants.js.map +1 -0
  27. package/dist/core/lb-types.d.ts +1 -1
  28. package/dist/core/lb-types.d.ts.map +1 -1
  29. package/dist/core/lb-types.js +2 -1
  30. package/dist/core/lb-types.js.map +1 -0
  31. package/dist/hub/lb-apply.d.ts +11 -1
  32. package/dist/hub/lb-apply.d.ts.map +1 -1
  33. package/dist/hub/lb-apply.js +50 -13
  34. package/dist/hub/lb-apply.js.map +1 -0
  35. package/dist/hub/lb-hub.browser.d.ts +1 -1
  36. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  37. package/dist/hub/lb-hub.browser.js +143 -112
  38. package/dist/hub/lb-hub.browser.js.map +1 -0
  39. package/dist/server/lb-express.d.ts +1 -1
  40. package/dist/server/lb-express.d.ts.map +1 -1
  41. package/dist/server/lb-express.js +1 -0
  42. package/dist/server/lb-express.js.map +1 -0
  43. package/dist/server/lb-server.d.ts +1 -1
  44. package/dist/server/lb-server.d.ts.map +1 -1
  45. package/dist/server/lb-server.js +2 -1
  46. package/dist/server/lb-server.js.map +1 -0
  47. package/docs/TECHREF-1.0.md +158 -43
  48. package/docs/analysis-accidental-complexity.md +149 -0
  49. package/docs/reference/custom-elements.md +27 -17
  50. package/docs/reference/data-binding.md +88 -29
  51. package/docs/reference/server.md +18 -0
  52. package/docs/reference/widgets.md +3 -3
  53. package/docs/theory.md +114 -1
  54. package/docs/tutorials/072-inserting-into-a-list.md +14 -11
  55. package/docs/tutorials/080-widget-requests.md +9 -26
  56. package/package.json +2 -2
  57. package/dist/tests/assemble.test.d.ts +0 -8
  58. package/dist/tests/assemble.test.d.ts.map +0 -1
  59. package/dist/tests/assemble.test.js +0 -210
  60. package/dist/tests/elements.test.d.ts +0 -8
  61. package/dist/tests/elements.test.d.ts.map +0 -1
  62. package/dist/tests/elements.test.js +0 -118
  63. package/dist/tests/expand.test.d.ts +0 -10
  64. package/dist/tests/expand.test.d.ts.map +0 -1
  65. package/dist/tests/expand.test.js +0 -253
  66. package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
  67. package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
  68. package/dist/tests/fixtures/elements/collision/imports.js +0 -1
  69. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
  70. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
  71. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
  72. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
  73. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
  74. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
  75. package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
  76. package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
  77. package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
  78. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
  79. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
  80. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
  81. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
  82. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
  83. package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
  84. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
  85. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
  86. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
  87. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
  88. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
  89. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
  90. package/dist/tests/helpers/console.d.ts +0 -20
  91. package/dist/tests/helpers/console.d.ts.map +0 -1
  92. package/dist/tests/helpers/console.js +0 -28
  93. package/dist/tests/helpers/dom.d.ts +0 -18
  94. package/dist/tests/helpers/dom.d.ts.map +0 -1
  95. package/dist/tests/helpers/dom.js +0 -22
  96. package/dist/tests/helpers/hub.d.ts +0 -73
  97. package/dist/tests/helpers/hub.d.ts.map +0 -1
  98. package/dist/tests/helpers/hub.js +0 -151
  99. package/dist/tests/lb-apply.test.d.ts +0 -8
  100. package/dist/tests/lb-apply.test.d.ts.map +0 -1
  101. package/dist/tests/lb-apply.test.js +0 -177
  102. package/dist/tests/lb-express.test.d.ts +0 -14
  103. package/dist/tests/lb-express.test.d.ts.map +0 -1
  104. package/dist/tests/lb-express.test.js +0 -243
  105. package/dist/tests/lb-hub.test.d.ts +0 -14
  106. package/dist/tests/lb-hub.test.d.ts.map +0 -1
  107. package/dist/tests/lb-hub.test.js +0 -319
  108. package/dist/tests/lb-list.test.d.ts +0 -12
  109. package/dist/tests/lb-list.test.d.ts.map +0 -1
  110. package/dist/tests/lb-list.test.js +0 -339
  111. package/dist/tests/lb-server.test.d.ts +0 -9
  112. package/dist/tests/lb-server.test.d.ts.map +0 -1
  113. package/dist/tests/lb-server.test.js +0 -546
  114. package/dist/tests/origins.test.d.ts +0 -10
  115. package/dist/tests/origins.test.d.ts.map +0 -1
  116. package/dist/tests/origins.test.js +0 -387
  117. package/dist/tests/pages.test.d.ts +0 -6
  118. package/dist/tests/pages.test.d.ts.map +0 -1
  119. package/dist/tests/pages.test.js +0 -148
  120. package/dist/tests/styles.test.d.ts +0 -7
  121. package/dist/tests/styles.test.d.ts.map +0 -1
  122. package/dist/tests/styles.test.js +0 -76
@@ -1,339 +0,0 @@
1
- /**
2
- * The row machinery — see docs/testing.md, tier 3.
3
- *
4
- * This is the most intricate code in the framework and the most likely to
5
- * break in ways nobody notices, because what it gets wrong is a row in the
6
- * wrong place rather than an exception.
7
- *
8
- * The last describe holds the two properties. Reconciliation's contract is
9
- * convergence, and two properties state it better than twenty examples.
10
- */
11
- import assert from "node:assert/strict";
12
- import { describe, it } from "node:test";
13
- import { applyList } from "../hub/lb-apply";
14
- import { body } from "./helpers/dom";
15
- import { captured } from "./helpers/console";
16
- /**
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.
21
- */
22
- function list(template = `<template lb-key="id"><p><span lb-cell="name"></span></p></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;
30
- }
31
- /** The keys of the rows showing, in document order. */
32
- function keys(scope) {
33
- return [...scope.querySelectorAll(`[lb-key-value]`)].map((el) => el.getAttribute("lb-key-value"));
34
- }
35
- /** What each row is showing, in document order. */
36
- function shown(scope) {
37
- return [...scope.querySelectorAll(`[lb-key-value]`)].map((el) => el.textContent.trim());
38
- }
39
- /** A whole set is the array itself; `Array.isArray` is the discriminant. */
40
- const rows = (set) => set;
41
- const patch = (change) => change;
42
- // --- The whole set -------------------------------------------------------
43
- describe("a rows result", () => {
44
- it("clones the template once per row and fills it", () => {
45
- const scope = list();
46
- applyList(scope, rows([
47
- { id: "1", name: "Ada" },
48
- { id: "2", name: "Bab" },
49
- ]));
50
- assert.deepEqual(keys(scope), ["1", "2"]);
51
- assert.deepEqual(shown(scope), ["Ada", "Bab"]);
52
- });
53
- it("carries the key value on the live 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.
56
- const scope = list();
57
- applyList(scope, rows([{ id: "7", name: "Ada" }]));
58
- const row = scope.querySelector("p");
59
- assert.equal(row.getAttribute("lb-key-value"), "7");
60
- });
61
- it("leaves the template where it was, after the rows", () => {
62
- const scope = list();
63
- applyList(scope, rows([{ id: "1", name: "Ada" }]));
64
- assert.equal(scope.lastElementChild.localName, "template");
65
- });
66
- it("decides membership: a key that did not arrive is gone", () => {
67
- const scope = list();
68
- applyList(scope, rows([
69
- { id: "1", name: "Ada" },
70
- { id: "2", name: "Bab" },
71
- ]));
72
- applyList(scope, rows([{ id: "2", name: "Bab" }]));
73
- assert.deepEqual(keys(scope), ["2"]);
74
- });
75
- it("decides order: a row that already exists is placed again", () => {
76
- const scope = list();
77
- applyList(scope, rows([
78
- { id: "1", name: "Ada" },
79
- { id: "2", name: "Bab" },
80
- ]));
81
- applyList(scope, rows([
82
- { id: "2", name: "Bab" },
83
- { id: "1", name: "Ada" },
84
- ]));
85
- assert.deepEqual(keys(scope), ["2", "1"]);
86
- });
87
- it("updates a row it keeps rather than replacing it", () => {
88
- // The row is the same element, so anything a widget put on it survives.
89
- const scope = list();
90
- applyList(scope, rows([{ id: "1", name: "Ada" }]));
91
- const before = scope.querySelector("p");
92
- applyList(scope, rows([{ id: "1", name: "Grace" }]));
93
- assert.equal(scope.querySelector("p"), before, "same element");
94
- assert.deepEqual(shown(scope), ["Grace"]);
95
- });
96
- it("empties the list when the set is empty", () => {
97
- const scope = list();
98
- applyList(scope, rows([{ id: "1", name: "Ada" }]));
99
- applyList(scope, rows([]));
100
- assert.deepEqual(keys(scope), []);
101
- assert.equal(scope.querySelector("template"), scope.firstElementChild);
102
- });
103
- });
104
- // --- Only what changed ---------------------------------------------------
105
- describe("a patch result", () => {
106
- it("adds a row it names", () => {
107
- const scope = list();
108
- applyList(scope, rows([{ id: "1", name: "Ada" }]));
109
- applyList(scope, patch({ rows: [{ id: "2", name: "Bab" }] }));
110
- assert.deepEqual(keys(scope), ["1", "2"]);
111
- });
112
- it("updates a row it names", () => {
113
- const scope = list();
114
- applyList(scope, rows([{ id: "1", name: "Ada" }]));
115
- applyList(scope, patch({ rows: [{ id: "1", name: "Grace" }] }));
116
- assert.deepEqual(shown(scope), ["Grace"]);
117
- });
118
- it("drops a row it names", () => {
119
- const scope = list();
120
- applyList(scope, rows([
121
- { id: "1", name: "Ada" },
122
- { id: "2", name: "Bab" },
123
- ]));
124
- applyList(scope, patch({ drop: ["1"] }));
125
- assert.deepEqual(keys(scope), ["2"]);
126
- });
127
- it("leaves a row it did not name, in contents and in place", () => {
128
- const scope = list();
129
- applyList(scope, rows([
130
- { id: "1", name: "Ada" },
131
- { id: "2", name: "Bab" },
132
- { id: "3", name: "Cai" },
133
- ]));
134
- applyList(scope, patch({ rows: [{ id: "2", name: "Grace" }] }));
135
- assert.deepEqual(keys(scope), ["1", "2", "3"]);
136
- assert.deepEqual(shown(scope), ["Ada", "Grace", "Cai"]);
137
- });
138
- it("does not re-place a row that already exists", () => {
139
- // Today's behavior, and exactly the gap NEEDED-FOR-1.0 names: a row whose
140
- // sort column changed stays where it was. This test states what is true
141
- // now, and is the one that flips when that item closes.
142
- const placed = [];
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([
148
- { id: "1", name: "Ada" },
149
- { id: "2", name: "Bab" },
150
- ]));
151
- placed.length = 0;
152
- applyList(scope, patch({ rows: [{ id: "1", name: "Grace" }] }));
153
- assert.deepEqual(placed, [], "an existing row is filled, never placed");
154
- });
155
- it("places a row it has not seen before", () => {
156
- const placed = [];
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" }]));
162
- placed.length = 0;
163
- applyList(scope, patch({ rows: [{ id: "2", name: "Bab" }] }));
164
- assert.deepEqual(placed, ["2"]);
165
- });
166
- it("ignores a drop for a row that is not showing", () => {
167
- const scope = list();
168
- applyList(scope, rows([{ id: "1", name: "Ada" }]));
169
- assert.doesNotThrow(() => applyList(scope, patch({ drop: ["9"] })));
170
- assert.deepEqual(keys(scope), ["1"]);
171
- });
172
- it("naming nothing changes nothing", () => {
173
- const scope = list();
174
- applyList(scope, rows([{ id: "1", name: "Ada" }]));
175
- const before = scope.innerHTML;
176
- applyList(scope, patch({}));
177
- assert.equal(scope.innerHTML, before);
178
- });
179
- });
180
- // --- Placement -----------------------------------------------------------
181
- describe("placement", () => {
182
- it("hands the widget the element, the row and the template", () => {
183
- const seen = [];
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
- assert.deepEqual(seen, [["p", { id: "1", name: "Ada" }, "template"]]);
190
- });
191
- it("fills the row before the widget places it", () => {
192
- // The attributes are already there when the row upgrades, which is the
193
- // same thing that makes hydration and refresh one operation everywhere
194
- // else. A widget that reads a cell to decide where the row goes — every
195
- // sorting or grouping widget — depends on this ordering.
196
- let atPlacement = "";
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
- assert.equal(atPlacement, "Ada");
203
- });
204
- it("hands the widget a row that is not yet in the document", () => {
205
- let connected = true;
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
- assert.equal(connected, false, "the row arrives detached");
212
- });
213
- });
214
- // --- How many are showing ------------------------------------------------
215
- describe("the row count", () => {
216
- it("is stamped on the widget", () => {
217
- // The one conditional a page cannot be sent: the server answers with rows
218
- // and says nothing about how many survived reconciliation. It is here so
219
- // that "no members yet" is a stylesheet rule rather than code in every
220
- // list widget.
221
- const scope = list();
222
- applyList(scope, rows([
223
- { id: "1", name: "Ada" },
224
- { id: "2", name: "Bab" },
225
- ]));
226
- assert.equal(scope.getAttribute("lb-row-count"), "2");
227
- });
228
- it("is zero for an empty set", () => {
229
- const scope = list();
230
- applyList(scope, rows([{ id: "1", name: "Ada" }]));
231
- applyList(scope, rows([]));
232
- assert.equal(scope.getAttribute("lb-row-count"), "0");
233
- });
234
- it("reports the same fact the same way after a set and after a patch", () => {
235
- const viaRows = list();
236
- applyList(viaRows, rows([
237
- { id: "1", name: "Ada" },
238
- { id: "2", name: "Bab" },
239
- ]));
240
- const viaPatch = list();
241
- applyList(viaPatch, rows([
242
- { id: "1", name: "Ada" },
243
- { id: "3", name: "Cai" },
244
- ]));
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
- });
248
- });
249
- // --- What it refuses to guess at ----------------------------------------
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, []);
258
- assert.equal(scope.children.length, 0, "nothing was invented");
259
- });
260
- it("when the template does not name the key column", async () => {
261
- const scope = list(`<template><p></p></template>`);
262
- const { error } = await captured(() => applyList(scope, rows([{ id: "1" }])));
263
- assert.match(error.join("\n"), /has no lb-key naming the column that identifies a row/);
264
- assert.deepEqual(keys(scope), []);
265
- });
266
- it("does not stamp a count it could not compute", async () => {
267
- const scope = body(`<div></div>`).firstElementChild;
268
- await captured(() => applyList(scope, rows([{ id: "1" }])));
269
- assert.equal(scope.hasAttribute("lb-row-count"), false);
270
- });
271
- it("when a row has no key cell, and lands its siblings anyway", async () => {
272
- const scope = list();
273
- const { error } = await captured(() => applyList(scope, rows([
274
- { id: "1", name: "Ada" },
275
- { name: "nameless" },
276
- { id: "2", name: "Bab" },
277
- ])));
278
- assert.match(error.join("\n"), /a row for <div> has no 'id' column/);
279
- assert.deepEqual(keys(scope), ["1", "2"]);
280
- });
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
283
- // reconciliation that follows must not read its absence as a removal.
284
- const scope = list();
285
- applyList(scope, rows([{ id: "1", name: "Ada" }]));
286
- await captured(() => applyList(scope, rows([{ id: "1", name: "Ada" }, { name: "nameless" }])));
287
- assert.deepEqual(keys(scope), ["1"]);
288
- });
289
- });
290
- // --- The properties ------------------------------------------------------
291
- describe("reconciliation converges", () => {
292
- const SET = [
293
- { id: "1", name: "Ada" },
294
- { id: "2", name: "Bab" },
295
- { id: "3", name: "Cai" },
296
- ];
297
- it("so applying the same set twice is applying it once", () => {
298
- const once = list();
299
- applyList(once, rows(SET));
300
- const twice = list();
301
- applyList(twice, rows(SET));
302
- applyList(twice, rows(SET));
303
- assert.equal(twice.innerHTML, once.innerHTML);
304
- assert.equal(twice.getAttribute("lb-row-count"), once.getAttribute("lb-row-count"));
305
- });
306
- it("so a set reached through any patches is the set reached from empty", () => {
307
- // Every history below ends at SET. A reconciler that leaks state — a row
308
- // it forgot to remove, an order it kept from before, a count it stamped
309
- // from the wrong branch — fails here and nowhere else.
310
- const histories = [
311
- [],
312
- [rows([])],
313
- [rows([{ id: "9", name: "Zed" }])],
314
- [rows(SET.slice().reverse())],
315
- [
316
- rows([{ id: "1", name: "Wrong" }]),
317
- patch({ rows: [{ id: "5", name: "Eve" }] }),
318
- ],
319
- [rows(SET), patch({ drop: ["2"] })],
320
- [
321
- rows(SET),
322
- patch({ rows: [{ id: "3", name: "Changed" }] }),
323
- patch({ drop: ["1"] }),
324
- ],
325
- [patch({ rows: [{ id: "4", name: "Dot" }] }), patch({ drop: ["4"] })],
326
- ];
327
- const fresh = list();
328
- applyList(fresh, rows(SET));
329
- const expected = fresh.innerHTML;
330
- for (const [index, history] of histories.entries()) {
331
- const scope = list();
332
- for (const step of history)
333
- applyList(scope, step);
334
- applyList(scope, rows(SET));
335
- assert.equal(scope.innerHTML, expected, `history ${index} did not converge`);
336
- assert.equal(scope.getAttribute("lb-row-count"), String(SET.length));
337
- }
338
- });
339
- });
@@ -1,9 +0,0 @@
1
- /**
2
- * The engine — see docs/testing.md, tier 2.
3
- *
4
- * `createHub` takes a plain object and returns an object. Nothing in
5
- * server/lb-server.ts opens a socket or knows what a request is, so every
6
- * fixture here is a counting stub and the whole tier runs in node.
7
- */
8
- export {};
9
- //# sourceMappingURL=lb-server.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"lb-server.test.d.ts","sourceRoot":"","sources":["../../tests/lb-server.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}