@loadbare/app 0.5.6 → 0.7.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 (143) 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 +81 -7
  5. package/dist/build/assemble.js.map +1 -0
  6. package/dist/build/cli.d.ts +2 -2
  7. package/dist/build/cli.js +3 -2
  8. package/dist/build/cli.js.map +1 -0
  9. package/dist/build/elements.js +1 -0
  10. package/dist/build/elements.js.map +1 -0
  11. package/dist/build/expand.d.ts.map +1 -1
  12. package/dist/build/expand.js +20 -32
  13. package/dist/build/expand.js.map +1 -0
  14. package/dist/build/format.js +1 -0
  15. package/dist/build/format.js.map +1 -0
  16. package/dist/build/locations.d.ts +4 -4
  17. package/dist/build/locations.d.ts.map +1 -1
  18. package/dist/build/locations.js +16 -5
  19. package/dist/build/locations.js.map +1 -0
  20. package/dist/build/origins.d.ts +0 -13
  21. package/dist/build/origins.d.ts.map +1 -1
  22. package/dist/build/origins.js +33 -8
  23. package/dist/build/origins.js.map +1 -0
  24. package/dist/build/package-root.js +1 -0
  25. package/dist/build/package-root.js.map +1 -0
  26. package/dist/build/pages.d.ts +7 -3
  27. package/dist/build/pages.d.ts.map +1 -1
  28. package/dist/build/pages.js +14 -7
  29. package/dist/build/pages.js.map +1 -0
  30. package/dist/build/styles.js +1 -0
  31. package/dist/build/styles.js.map +1 -0
  32. package/dist/core/lb-constants.d.ts +15 -13
  33. package/dist/core/lb-constants.d.ts.map +1 -1
  34. package/dist/core/lb-constants.js +104 -53
  35. package/dist/core/lb-constants.js.map +1 -0
  36. package/dist/core/lb-types.d.ts +103 -62
  37. package/dist/core/lb-types.d.ts.map +1 -1
  38. package/dist/core/lb-types.js +12 -3
  39. package/dist/core/lb-types.js.map +1 -0
  40. package/dist/hub/lb-apply.d.ts +28 -4
  41. package/dist/hub/lb-apply.d.ts.map +1 -1
  42. package/dist/hub/lb-apply.js +227 -40
  43. package/dist/hub/lb-apply.js.map +1 -0
  44. package/dist/hub/lb-hub.browser.d.ts +1 -1
  45. package/dist/hub/lb-hub.browser.d.ts.map +1 -1
  46. package/dist/hub/lb-hub.browser.js +165 -110
  47. package/dist/hub/lb-hub.browser.js.map +1 -0
  48. package/dist/server/lb-express.d.ts +8 -5
  49. package/dist/server/lb-express.d.ts.map +1 -1
  50. package/dist/server/lb-express.js +46 -33
  51. package/dist/server/lb-express.js.map +1 -0
  52. package/dist/server/lb-server.d.ts +67 -45
  53. package/dist/server/lb-server.d.ts.map +1 -1
  54. package/dist/server/lb-server.js +56 -18
  55. package/dist/server/lb-server.js.map +1 -0
  56. package/docs/TECHREF-1.0.md +1107 -0
  57. package/docs/analysis-accidental-complexity.md +149 -0
  58. package/docs/reference/builder.md +4 -4
  59. package/docs/reference/chrome.md +10 -9
  60. package/docs/reference/custom-elements.md +54 -46
  61. package/docs/reference/data-binding.md +179 -97
  62. package/docs/reference/overview.md +1 -1
  63. package/docs/reference/page-files.md +64 -49
  64. package/docs/reference/server.md +25 -6
  65. package/docs/reference/widgets.md +22 -30
  66. package/docs/roadmap.md +68 -22
  67. package/docs/testing.md +47 -17
  68. package/docs/theory.md +116 -3
  69. package/docs/tutorials/010-pages-and-navigation.md +8 -8
  70. package/docs/tutorials/040-displaying-data.md +9 -9
  71. package/docs/tutorials/050-actions.md +5 -5
  72. package/docs/tutorials/060-custom-element-code.md +1 -1
  73. package/docs/tutorials/065-conditional-rendering.md +4 -4
  74. package/docs/tutorials/070-displaying-a-list.md +24 -47
  75. package/docs/tutorials/072-inserting-into-a-list.md +18 -15
  76. package/docs/tutorials/074-deleting-from-a-list.md +15 -17
  77. package/docs/tutorials/076-updating-a-list-item.md +20 -22
  78. package/docs/tutorials/080-widget-requests.md +22 -35
  79. package/docs/tutorials/090-using-widget-libraries.md +1 -1
  80. package/package.json +2 -3
  81. package/dist/hub/lb-rows.d.ts +0 -18
  82. package/dist/hub/lb-rows.d.ts.map +0 -1
  83. package/dist/hub/lb-rows.js +0 -106
  84. package/dist/tests/assemble.test.d.ts +0 -8
  85. package/dist/tests/assemble.test.d.ts.map +0 -1
  86. package/dist/tests/assemble.test.js +0 -58
  87. package/dist/tests/elements.test.d.ts +0 -8
  88. package/dist/tests/elements.test.d.ts.map +0 -1
  89. package/dist/tests/elements.test.js +0 -118
  90. package/dist/tests/expand.test.d.ts +0 -10
  91. package/dist/tests/expand.test.d.ts.map +0 -1
  92. package/dist/tests/expand.test.js +0 -250
  93. package/dist/tests/fixtures/elements/collision/imports.d.ts +0 -3
  94. package/dist/tests/fixtures/elements/collision/imports.d.ts.map +0 -1
  95. package/dist/tests/fixtures/elements/collision/imports.js +0 -1
  96. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts +0 -2
  97. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.d.ts.map +0 -1
  98. package/dist/tests/fixtures/elements/collision/widgets/acme-widget.browser.js +0 -1
  99. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts +0 -2
  100. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.d.ts.map +0 -1
  101. package/dist/tests/fixtures/elements/local/widgets/app-box.browser.js +0 -1
  102. package/dist/tests/fixtures/elements/manifest/imports.d.ts +0 -3
  103. package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +0 -1
  104. package/dist/tests/fixtures/elements/manifest/imports.js +0 -1
  105. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +0 -3
  106. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +0 -1
  107. package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +0 -1
  108. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts +0 -5
  109. package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +0 -1
  110. package/dist/tests/fixtures/elements/manifest-not-array/imports.js +0 -1
  111. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts +0 -2
  112. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.d.ts.map +0 -1
  113. package/dist/tests/fixtures/elements/pkg/acme-widget.browser.js +0 -1
  114. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts +0 -6
  115. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.d.ts.map +0 -1
  116. package/dist/tests/fixtures/elements/unmarked/widgets/app-box.js +0 -1
  117. package/dist/tests/helpers/console.d.ts +0 -20
  118. package/dist/tests/helpers/console.d.ts.map +0 -1
  119. package/dist/tests/helpers/console.js +0 -28
  120. package/dist/tests/helpers/dom.d.ts +0 -18
  121. package/dist/tests/helpers/dom.d.ts.map +0 -1
  122. package/dist/tests/helpers/dom.js +0 -22
  123. package/dist/tests/lb-apply.test.d.ts +0 -8
  124. package/dist/tests/lb-apply.test.d.ts.map +0 -1
  125. package/dist/tests/lb-apply.test.js +0 -153
  126. package/dist/tests/lb-express.test.d.ts +0 -14
  127. package/dist/tests/lb-express.test.d.ts.map +0 -1
  128. package/dist/tests/lb-express.test.js +0 -238
  129. package/dist/tests/lb-rows.test.d.ts +0 -12
  130. package/dist/tests/lb-rows.test.d.ts.map +0 -1
  131. package/dist/tests/lb-rows.test.js +0 -336
  132. package/dist/tests/lb-server.test.d.ts +0 -9
  133. package/dist/tests/lb-server.test.d.ts.map +0 -1
  134. package/dist/tests/lb-server.test.js +0 -495
  135. package/dist/tests/origins.test.d.ts +0 -10
  136. package/dist/tests/origins.test.d.ts.map +0 -1
  137. package/dist/tests/origins.test.js +0 -369
  138. package/dist/tests/pages.test.d.ts +0 -6
  139. package/dist/tests/pages.test.d.ts.map +0 -1
  140. package/dist/tests/pages.test.js +0 -98
  141. package/dist/tests/styles.test.d.ts +0 -7
  142. package/dist/tests/styles.test.d.ts.map +0 -1
  143. package/dist/tests/styles.test.js +0 -76
@@ -1,58 +0,0 @@
1
- /**
2
- * Assembling a chrome file and its pages — see build/assemble.ts.
3
- *
4
- * The chrome and pages together are one write to a temp tree per test,
5
- * since assemble() reads from disk rather than taking sources in memory.
6
- */
7
- import assert from "node:assert/strict";
8
- import { describe, it } from "node:test";
9
- import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
10
- import { tmpdir } from "node:os";
11
- import path from "node:path";
12
- import { assemble } from "../build/assemble";
13
- import { resolveLocations } from "../build/locations";
14
- import { scanAll } from "../build/origins";
15
- async function tree(files) {
16
- const dir = await mkdtemp(path.join(tmpdir(), "loadbare-assemble-"));
17
- for (const [name, content] of Object.entries(files)) {
18
- const file = path.join(dir, name);
19
- await mkdir(path.dirname(file), { recursive: true });
20
- await writeFile(file, content, "utf-8");
21
- }
22
- return dir;
23
- }
24
- async function scansFor(locations) {
25
- return scanAll(locations, locations.srcDir, locations.importsFile);
26
- }
27
- describe("assemble", () => {
28
- it("expands a widget written in the chrome, not only in pages", async () => {
29
- const src = await tree({
30
- "chrome.html": `<!doctype html><body><lb-nav exp-label="Site"><a href="/a">A</a></lb-nav><lb-hub><main></main></lb-hub></body>`,
31
- "lb-nav.html": `<nav aria-label="{{label}}" lb-slot></nav>`,
32
- });
33
- try {
34
- const locations = await resolveLocations({ src, out: `${src}/dist` });
35
- const { html, usedTags } = await assemble({
36
- ...locations,
37
- scans: await scansFor(locations),
38
- });
39
- assert.match(html, /<nav aria-label="Site"><a href="\/a">A<\/a><\/nav>/);
40
- assert.ok(usedTags.has("lb-nav"));
41
- }
42
- finally {
43
- await rm(src, { recursive: true, force: true });
44
- }
45
- });
46
- it("still throws when the chrome uses an undefined widget", async () => {
47
- const src = await tree({
48
- "chrome.html": `<!doctype html><body><lb-nope></lb-nope></body>`,
49
- });
50
- try {
51
- const locations = await resolveLocations({ src, out: `${src}/dist` });
52
- await assert.rejects(async () => assemble({ ...locations, scans: await scansFor(locations) }), /<lb-nope> has no definition/);
53
- }
54
- finally {
55
- await rm(src, { recursive: true, force: true });
56
- }
57
- });
58
- });
@@ -1,8 +0,0 @@
1
- /**
2
- * Resolving a used tag to the script that registers it — see build/elements.ts.
3
- *
4
- * Fixtures under tests/fixtures/elements/ each stand in for one <src> tree,
5
- * the same role tests/fixtures/definitions plays for expand.ts.
6
- */
7
- export {};
8
- //# sourceMappingURL=elements.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"elements.test.d.ts","sourceRoot":"","sources":["../../tests/elements.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -1,118 +0,0 @@
1
- /**
2
- * Resolving a used tag to the script that registers it — see build/elements.ts.
3
- *
4
- * Fixtures under tests/fixtures/elements/ each stand in for one <src> tree,
5
- * the same role tests/fixtures/definitions plays for expand.ts.
6
- */
7
- import assert from "node:assert/strict";
8
- import { describe, it } from "node:test";
9
- import { fileURLToPath } from "node:url";
10
- import { customElementTags } from "../build/expand";
11
- import { loadElementManifest, resolveElements } from "../build/elements";
12
- import { packageInfo } from "../build/locations";
13
- import { scanOrigin } from "../build/origins";
14
- const FIXTURES = fileURLToPath(new URL("./fixtures/elements", import.meta.url));
15
- /**
16
- * The cascade resolveElements reads, built here rather than by
17
- * build/origins.ts's buildOrigins: these fixtures name packages that are not
18
- * installed, and resolution is what is under test, not package lookup.
19
- */
20
- async function scansFor(src, packageDir) {
21
- const info = await packageInfo();
22
- const origins = [
23
- {
24
- name: info.packageName,
25
- dirs: [info.builtinHubDir],
26
- specifier: () => info.packageName,
27
- },
28
- ...(packageDir ? [{ name: "@acme/widget", dirs: [packageDir] }] : []),
29
- { name: src, dirs: [src] },
30
- ];
31
- return Promise.all(origins.map(scanOrigin));
32
- }
33
- describe("customElementTags", () => {
34
- it("collects every hyphenated tag, native elements excluded", () => {
35
- const tags = customElementTags(`<div><lb-input></lb-input><app-box>hi</app-box><span></span></div>`);
36
- assert.deepEqual([...tags].sort(), ["app-box", "lb-input"]);
37
- });
38
- it("reaches into template content", () => {
39
- const tags = customElementTags(`<template><acme-widget></acme-widget></template>`);
40
- assert.deepEqual([...tags], ["acme-widget"]);
41
- });
42
- });
43
- describe("loadElementManifest", () => {
44
- it("returns the declared package list", async () => {
45
- const declared = await loadElementManifest(`${FIXTURES}/manifest/imports.ts`);
46
- assert.deepEqual(declared, ["@acme/widget"]);
47
- });
48
- it("returns empty when there is no imports.ts", async () => {
49
- const declared = await loadElementManifest(`${FIXTURES}/local/imports.ts`);
50
- assert.deepEqual(declared, []);
51
- });
52
- it("rejects a default export that is not an array", async () => {
53
- await assert.rejects(() => loadElementManifest(`${FIXTURES}/manifest-not-array/imports.ts`), /must default-export an array of package names/);
54
- });
55
- it("rejects an entry that is not a package name", async () => {
56
- await assert.rejects(() => loadElementManifest(`${FIXTURES}/manifest-bad-entry/imports.ts`), /every entry must be a package name/);
57
- });
58
- });
59
- describe("resolveElements", () => {
60
- it("resolves lb-hub to the package itself, the one built-in", async () => {
61
- const { resolved } = resolveElements({
62
- used: new Set(["lb-hub"]),
63
- scans: await scansFor(`${FIXTURES}/local`),
64
- });
65
- assert.deepEqual(resolved.get("lb-hub"), {
66
- kind: "specifier",
67
- value: "@loadbare/app",
68
- });
69
- });
70
- it("resolves a local tag to its file in <src>", async () => {
71
- const { resolved } = resolveElements({
72
- used: new Set(["app-box"]),
73
- scans: await scansFor(`${FIXTURES}/local`),
74
- });
75
- assert.equal(resolved.get("app-box")?.kind, "file");
76
- assert.match(resolved.get("app-box").value, /local\/widgets\/app-box\.browser$/);
77
- });
78
- it("resolves a tag found by scanning a declared package", async () => {
79
- const { resolved } = resolveElements({
80
- used: new Set(["acme-widget"]),
81
- scans: await scansFor(`${FIXTURES}/manifest`, `${FIXTURES}/pkg`),
82
- });
83
- assert.equal(resolved.get("acme-widget")?.kind, "file");
84
- assert.match(resolved.get("acme-widget").value, /pkg\/acme-widget\.browser$/);
85
- });
86
- it("needs no script for a tag with only an .html definition", async () => {
87
- const { resolved } = resolveElements({
88
- used: new Set(["lb-nav"]),
89
- scans: await scansFor(`${FIXTURES}/markup-only`),
90
- });
91
- assert.equal(resolved.size, 0);
92
- });
93
- it("names the unmarked file when a tag's script forgot to say .browser", async () => {
94
- await assert.rejects(async () => resolveElements({
95
- used: new Set(["app-box"]),
96
- scans: await scansFor(`${FIXTURES}/unmarked`),
97
- }), (err) => {
98
- assert.match(err.message, /<app-box> has no script/);
99
- assert.match(err.message, /widgets[/\\]app-box\.ts' is named after it/);
100
- assert.match(err.message, /rename it 'app-box\.browser\.ts'/);
101
- return true;
102
- });
103
- });
104
- it("throws when a used tag registers nothing", async () => {
105
- await assert.rejects(async () => resolveElements({
106
- used: new Set(["nowhere-widget"]),
107
- scans: await scansFor(`${FIXTURES}/local`),
108
- }), /<nowhere-widget> has neither a script nor a definition/);
109
- });
110
- it("lets src win over a package that has the same tag", async () => {
111
- const { resolved } = resolveElements({
112
- used: new Set(["acme-widget"]),
113
- scans: await scansFor(`${FIXTURES}/collision`, `${FIXTURES}/pkg`),
114
- });
115
- assert.equal(resolved.get("acme-widget")?.kind, "file");
116
- assert.match(resolved.get("acme-widget").value, /collision\/widgets\/acme-widget\.browser$/);
117
- });
118
- });
@@ -1,10 +0,0 @@
1
- /**
2
- * Expansion — see docs/reference/custom-elements.md and docs/testing.md, tier 1.
3
- *
4
- * Definitions are written inline here because `expand` takes them as a map
5
- * and a two-line definition beside its assertion reads better than a file
6
- * reference. The cases that genuinely need files — the checks `loadDefinitions`
7
- * runs, and directory ordering — use tests/fixtures/definitions.
8
- */
9
- export {};
10
- //# sourceMappingURL=expand.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"expand.test.d.ts","sourceRoot":"","sources":["../../tests/expand.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
@@ -1,250 +0,0 @@
1
- /**
2
- * Expansion — see docs/reference/custom-elements.md and docs/testing.md, tier 1.
3
- *
4
- * Definitions are written inline here because `expand` takes them as a map
5
- * and a two-line definition beside its assertion reads better than a file
6
- * reference. The cases that genuinely need files — the checks `loadDefinitions`
7
- * runs, and directory ordering — use tests/fixtures/definitions.
8
- */
9
- import assert from "node:assert/strict";
10
- import { describe, it } from "node:test";
11
- import { fileURLToPath } from "node:url";
12
- import { JSDOM } from "jsdom";
13
- import { expand, loadDefinitions } from "../build/expand";
14
- const FIXTURES = fileURLToPath(new URL("./fixtures/definitions", import.meta.url));
15
- /**
16
- * Read expansion's output as a tree.
17
- *
18
- * Used where the claim is about the document rather than about the text —
19
- * which elements exist, which attributes an element has. Matching a
20
- * serialization would test the serializer.
21
- */
22
- function parseHtml(html) {
23
- const holder = new JSDOM("").window.document.createElement("div");
24
- holder.innerHTML = html;
25
- return holder;
26
- }
27
- /** Definitions are keyed by the uppercased tag they define. */
28
- function defs(entries) {
29
- const map = {};
30
- for (const [tag, source] of Object.entries(entries)) {
31
- map[tag.toUpperCase()] = source;
32
- }
33
- return map;
34
- }
35
- // --- Rejections ----------------------------------------------------------
36
- //
37
- // Expansion runs before any request exists, so every one of these is a build
38
- // that must not produce output. Each asserts on the message as well as the
39
- // throw: an author reads the message, and it is the whole of the diagnosis.
40
- describe("expansion rejects", () => {
41
- it("a hub tag with no definition", () => {
42
- assert.throws(() => expand(`<lb-nope></lb-nope>`, defs({})), /<lb-nope> has no definition/);
43
- });
44
- it("a parameter the definition never declared", () => {
45
- assert.throws(() => expand(`<lb-thing exp-label="Name"></lb-thing>`, defs({ "lb-thing": `<div></div>` })), /given exp-label, but its definition has no \{\{label\}\}/);
46
- });
47
- it("a placeholder no tag could supply", async () => {
48
- await assert.rejects(() => loadDefinitions([`${FIXTURES}/camel`]), /declares \{\{inputClass\}\}.*Write \{\{input-class\}\}/s);
49
- });
50
- it("a cycle in the definition graph", async () => {
51
- await assert.rejects(() => loadDefinitions([`${FIXTURES}/cycle`]), /cycle in the definition graph/);
52
- });
53
- it("two destinations with one name", () => {
54
- assert.throws(() => expand(`<lb-thing></lb-thing>`, defs({
55
- "lb-thing": `<table><thead lb-template="head"></thead><tfoot lb-template="head"></tfoot></table>`,
56
- })), /declares two lb-template="head" destinations/);
57
- });
58
- it("two templates for one destination", () => {
59
- assert.throws(() => expand(`<lb-thing>` +
60
- `<template lb-template="head"><tr></tr></template>` +
61
- `<template lb-template="head"><tr></tr></template>` +
62
- `</lb-thing>`, defs({
63
- "lb-thing": `<table><thead lb-template="head"></thead></table>`,
64
- })), /was given two templates for 'head'/);
65
- });
66
- it("a template naming a destination the definition does not have", () => {
67
- assert.throws(() => expand(`<lb-thing><template lb-template="foot"><tr></tr></template></lb-thing>`, defs({
68
- "lb-thing": `<table><thead lb-template="head"></thead></table>`,
69
- })), /has no destination by that name/);
70
- });
71
- it("content written into a definition with no slot", () => {
72
- assert.throws(() => expand(`<lb-thing><p>hello</p></lb-thing>`, defs({ "lb-thing": `<div></div>` })), /content was written inside <lb-thing>, whose definition has no lb-slot/);
73
- });
74
- it("a definition declaring more than one slot", () => {
75
- assert.throws(() => expand(`<lb-thing></lb-thing>`, defs({
76
- "lb-thing": `<div lb-slot></div><span lb-slot></span>`,
77
- })), /declares more than one lb-slot/);
78
- });
79
- });
80
- // --- Substitution --------------------------------------------------------
81
- describe("substitution", () => {
82
- it("fills an attribute and a text node", () => {
83
- const html = expand(`<lb-thing exp-label="Name" exp-name="who"></lb-thing>`, defs({
84
- "lb-thing": `<label>{{label}}<input name="{{name}}" /></label>`,
85
- }));
86
- assert.match(html, /<label>Name<input name="who"><\/label>/);
87
- });
88
- it("drops an attribute whose placeholder was not supplied", () => {
89
- // Presence propagates: an unsupplied placeholder must not emit
90
- // readonly="", which HTML reads as readonly.
91
- const html = expand(`<lb-thing></lb-thing>`, defs({ "lb-thing": `<input readonly="{{readonly}}" />` }));
92
- assert.match(html, /<input>/);
93
- assert.doesNotMatch(html, /readonly/);
94
- });
95
- it("keeps the whitespace around a text placeholder", () => {
96
- // "{{label}} <input>" has to keep separating the label from the control.
97
- const html = expand(`<lb-thing exp-label="Name"></lb-thing>`, defs({ "lb-thing": `<label>{{label}} <input /></label>` }));
98
- assert.match(html, /<label>Name <input><\/label>/);
99
- });
100
- it("fills an unsupplied text placeholder with nothing", () => {
101
- const html = expand(`<lb-thing></lb-thing>`, defs({ "lb-thing": `<label>{{label}}</label>` }));
102
- assert.match(html, /<label><\/label>/);
103
- });
104
- it("falls back to a default the definition wrote", () => {
105
- const html = expand(`<lb-thing></lb-thing>`, defs({ "lb-thing": `<button>{{text|OK}}</button>` }));
106
- assert.match(html, /<button>OK<\/button>/);
107
- });
108
- it("lets a supplied value win over a default", () => {
109
- const html = expand(`<lb-thing exp-text="Go"></lb-thing>`, defs({ "lb-thing": `<button>{{text|OK}}</button>` }));
110
- assert.match(html, /<button>Go<\/button>/);
111
- });
112
- it("trims whitespace around a name and its default", () => {
113
- // A spaced form that shipped as literal braces would be a silent failure
114
- // found in the browser, so the space is not part of either side.
115
- const html = expand(`<lb-thing></lb-thing>`, defs({
116
- "lb-thing": `<button title="{{ tip | Press me }}">{{ text | OK }}</button>`,
117
- }));
118
- assert.match(html, /<button title="Press me">OK<\/button>/);
119
- });
120
- it("emits an attribute whose default is empty", () => {
121
- // The one case a default distinguishes from no default: presence.
122
- // readonly="{{readonly|}}" is a boolean attribute on unless a tag turns
123
- // it off — which, since a supplied value is a string, a tag cannot do;
124
- // the default is for a definition that wants the attribute there.
125
- const html = expand(`<lb-thing></lb-thing>`, defs({ "lb-thing": `<input readonly="{{readonly|}}" />` }));
126
- assert.match(html, /<input readonly="">/);
127
- });
128
- it("reaches placeholders inside template content", () => {
129
- // lb-picker ships a row template of its own, so {{key}} written inside
130
- // one has to be a placeholder rather than literal text that ships.
131
- const html = expand(`<lb-thing exp-key="id" exp-cell="name"></lb-thing>`, defs({
132
- "lb-thing": `<select><template lb-key="{{key}}"><option lb-cell="{{cell}}"></option></template></select>`,
133
- }));
134
- assert.match(html, /<template lb-key="id">/);
135
- assert.match(html, /<option lb-cell="name">/);
136
- });
137
- it("leaves a partial placeholder alone", () => {
138
- // A placeholder is an entire attribute value or an entire text node.
139
- const html = expand(`<lb-thing></lb-thing>`, defs({ "lb-thing": `<label title="a {{label}} b"></label>` }));
140
- assert.match(html, /title="a \{\{label\}\} b"/);
141
- });
142
- it("refuses a parameter for a partial placeholder", () => {
143
- // The consequence of the rule above, and the reason it is safe: a name
144
- // that is not a placeholder is a name the definition never declared, so
145
- // supplying it is the same error as supplying a name that is not there
146
- // at all. The author is told rather than shipped literal braces.
147
- assert.throws(() => expand(`<lb-thing exp-label="Name"></lb-thing>`, defs({ "lb-thing": `<label title="a {{label}} b"></label>` })), /has no \{\{label\}\}/);
148
- });
149
- it("keeps the exp- attributes on the expanded tag", () => {
150
- // What ships shows what was asked for beside what it produced.
151
- const html = expand(`<lb-thing exp-label="Name"></lb-thing>`, defs({ "lb-thing": `<label>{{label}}</label>` }));
152
- assert.match(html, /<lb-thing exp-label="Name">/);
153
- });
154
- it("cannot inject markup, by construction", () => {
155
- // Substitution goes through setAttribute and node values, never through a
156
- // string. This test exists to fail if anyone reaches for concatenation.
157
- //
158
- // The assertion reparses rather than matching the serialization, because
159
- // the serialization is not the claim. An expanded tag keeps the exp-
160
- // attributes it was given, and `<` in an attribute value is legal HTML
161
- // that no serializer has to escape — so the output does contain the
162
- // characters. What must not exist is an element.
163
- const html = expand(`<lb-thing exp-label="<img src=x onerror=alert(1)>" exp-name="&quot; onclick=&quot;alert(1)"></lb-thing>`, defs({
164
- "lb-thing": `<label>{{label}}<input name="{{name}}" /></label>`,
165
- }));
166
- const tree = parseHtml(html);
167
- assert.equal(tree.querySelectorAll("img").length, 0);
168
- assert.equal(tree.querySelector("label").textContent, "<img src=x onerror=alert(1)>");
169
- // The second value closes an attribute and opens an event handler, if it
170
- // is ever concatenated. Through setAttribute it is one attribute's value.
171
- const input = tree.querySelector("input");
172
- assert.deepEqual([...input.attributes].map((a) => a.name), ["name"]);
173
- assert.equal(input.getAttribute("name"), `" onclick="alert(1)`);
174
- });
175
- });
176
- // --- Slots and destinations ---------------------------------------------
177
- describe("placement", () => {
178
- it("puts authored content in the slot and removes the marker", () => {
179
- const html = expand(`<lb-list><p>hello</p></lb-list>`, defs({ "lb-list": `<div lb-slot></div>` }));
180
- assert.match(html, /<div><p>hello<\/p><\/div>/);
181
- assert.doesNotMatch(html, /lb-slot/);
182
- });
183
- it("unwraps a named template into its destination as real markup", () => {
184
- // The wrapper existed only to survive the parser. What ships is a real
185
- // <tr> in a real <thead>, visible in view-source like everything else.
186
- const html = expand(`<lb-table>` +
187
- `<template lb-template="head"><tr><th>Name</th></tr></template>` +
188
- `<template lb-key="id"><tr><td lb-cell="name"></td></tr></template>` +
189
- `</lb-table>`, defs({
190
- "lb-table": `<table><thead lb-template="head"></thead><tbody lb-slot></tbody></table>`,
191
- }));
192
- assert.match(html, /<thead><tr><th>Name<\/th><\/tr><\/thead>/);
193
- assert.match(html, /<tbody><template lb-key="id">/);
194
- assert.doesNotMatch(html, /lb-template/);
195
- });
196
- it("does not count whitespace as content", () => {
197
- // An author who writes only named templates still leaves the newlines
198
- // between them, and those are the file's formatting.
199
- const html = expand(`<lb-thing>\n \n</lb-thing>`, defs({ "lb-thing": `<div></div>` }));
200
- assert.match(html, /<lb-thing><div><\/div><\/lb-thing>/);
201
- });
202
- it("expands a widget used inside a definition", () => {
203
- const html = expand(`<lb-outer></lb-outer>`, defs({
204
- "lb-outer": `<div><lb-inner exp-label="deep"></lb-inner></div>`,
205
- "lb-inner": `<span>{{label}}</span>`,
206
- }));
207
- assert.match(html, /<span>deep<\/span>/);
208
- });
209
- it("expands a widget written inside a page's row template", () => {
210
- // Template content is markup in a file like any other, and it is inert,
211
- // so a row template ships already expanded.
212
- const html = expand(`<lb-list><template lb-key="id"><lb-inner exp-label="row"></lb-inner></template></lb-list>`, defs({
213
- "lb-list": `<div lb-slot></div>`,
214
- "lb-inner": `<span>{{label}}</span>`,
215
- }));
216
- assert.match(html, /<span>row<\/span>/);
217
- });
218
- it("expands a tag outside the lb- namespace when it has a definition", () => {
219
- // An application's widget can use its own prefix — see docs/tutorials/000-getting-started.md.
220
- // The definition must still apply; discovering it is not enough.
221
- const html = expand(`<app-nav exp-label="Site"></app-nav>`, defs({ "app-nav": `<nav aria-label="{{label}}" lb-slot></nav>` }));
222
- assert.match(html, /<nav aria-label="Site"><\/nav>/);
223
- });
224
- it("leaves a tag outside the lb- namespace alone when it has no definition", () => {
225
- // A plain custom element, registered by script alone — see build/elements.ts.
226
- const html = expand(`<app-box>hi</app-box>`, defs({}));
227
- assert.match(html, /<app-box>hi<\/app-box>/);
228
- });
229
- });
230
- // --- Loading -------------------------------------------------------------
231
- describe("loadDefinitions", () => {
232
- it("keys a definition by the tag its filename names", async () => {
233
- const loaded = await loadDefinitions([`${FIXTURES}/base`]);
234
- assert.deepEqual(Object.keys(loaded), ["LB-THING"]);
235
- });
236
- it("lets a later directory override a built-in", async () => {
237
- const loaded = await loadDefinitions([
238
- `${FIXTURES}/base`,
239
- `${FIXTURES}/override`,
240
- ]);
241
- assert.match(loaded["LB-THING"], /application/);
242
- });
243
- it("skips a directory that is not there", async () => {
244
- const loaded = await loadDefinitions([
245
- `${FIXTURES}/nothing-here`,
246
- `${FIXTURES}/base`,
247
- ]);
248
- assert.deepEqual(Object.keys(loaded), ["LB-THING"]);
249
- });
250
- });
@@ -1,3 +0,0 @@
1
- declare const _default: string[];
2
- export default _default;
3
- //# sourceMappingURL=imports.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/collision/imports.ts"],"names":[],"mappings":";AAAA,wBAAgC"}
@@ -1 +0,0 @@
1
- export default ["@acme/widget"];
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=acme-widget.browser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"acme-widget.browser.d.ts","sourceRoot":"","sources":["../../../../../../tests/fixtures/elements/collision/widgets/acme-widget.browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC"}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=app-box.browser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-box.browser.d.ts","sourceRoot":"","sources":["../../../../../../tests/fixtures/elements/local/widgets/app-box.browser.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC"}
@@ -1,3 +0,0 @@
1
- declare const _default: string[];
2
- export default _default;
3
- //# sourceMappingURL=imports.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/manifest/imports.ts"],"names":[],"mappings":";AAAA,wBAAgC"}
@@ -1 +0,0 @@
1
- export default ["@acme/widget"];
@@ -1,3 +0,0 @@
1
- declare const _default: (string | number)[];
2
- export default _default;
3
- //# sourceMappingURL=imports.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/manifest-bad-entry/imports.ts"],"names":[],"mappings":";AAAA,wBAAoC"}
@@ -1 +0,0 @@
1
- export default ["@acme/widget", 42];
@@ -1,5 +0,0 @@
1
- declare const _default: {
2
- "acme-widget": string;
3
- };
4
- export default _default;
5
- //# sourceMappingURL=imports.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/manifest-not-array/imports.ts"],"names":[],"mappings":";;;AAAA,wBAAiD"}
@@ -1 +0,0 @@
1
- export default { "acme-widget": "@acme/widget" };
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=acme-widget.browser.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"acme-widget.browser.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/pkg/acme-widget.browser.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC"}
@@ -1,6 +0,0 @@
1
- /**
2
- * A widget script that forgot to say `.browser` — the fixture behind the
3
- * error that names the file instead of reporting an absence.
4
- */
5
- export {};
6
- //# sourceMappingURL=app-box.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"app-box.d.ts","sourceRoot":"","sources":["../../../../../../tests/fixtures/elements/unmarked/widgets/app-box.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,CAAC"}
@@ -1,20 +0,0 @@
1
- /**
2
- * The console is an interface — see docs/testing.md.
3
- *
4
- * Loadbare App reports what it survives through `console.warn` and `console.error`:
5
- * a page that does not exist, a query with no scope, a list widget with no
6
- * template. The behavior under test in those cases is that Loadbare App reported and
7
- * carried on, so the report is as much the assertion as the return value is.
8
- *
9
- * This is the seam until those calls go through one `report()` in `core/`.
10
- */
11
- type Line = string;
12
- export interface Captured<T> {
13
- result: T;
14
- warn: Line[];
15
- error: Line[];
16
- }
17
- /** Run something with the console collected rather than printed. */
18
- export declare function captured<T>(run: () => T | Promise<T>): Promise<Captured<T>>;
19
- export {};
20
- //# sourceMappingURL=console.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"console.d.ts","sourceRoot":"","sources":["../../../tests/helpers/console.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,KAAK,IAAI,GAAG,MAAM,CAAC;AAEnB,MAAM,WAAW,QAAQ,CAAC,CAAC;IACzB,MAAM,EAAE,CAAC,CAAC;IACV,IAAI,EAAE,IAAI,EAAE,CAAC;IACb,KAAK,EAAE,IAAI,EAAE,CAAC;CACf;AAED,oEAAoE;AACpE,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,GAAG,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,GACxB,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAgBtB"}
@@ -1,28 +0,0 @@
1
- /**
2
- * The console is an interface — see docs/testing.md.
3
- *
4
- * Loadbare App reports what it survives through `console.warn` and `console.error`:
5
- * a page that does not exist, a query with no scope, a list widget with no
6
- * template. The behavior under test in those cases is that Loadbare App reported and
7
- * carried on, so the report is as much the assertion as the return value is.
8
- *
9
- * This is the seam until those calls go through one `report()` in `core/`.
10
- */
11
- /** Run something with the console collected rather than printed. */
12
- export async function captured(run) {
13
- const warn = [];
14
- const error = [];
15
- const realWarn = console.warn;
16
- const realError = console.error;
17
- // Arguments are joined the way the console would show them, so a test can
18
- // match one string against the message an author would read.
19
- console.warn = (...args) => void warn.push(args.join(" "));
20
- console.error = (...args) => void error.push(args.join(" "));
21
- try {
22
- return { result: await run(), warn, error };
23
- }
24
- finally {
25
- console.warn = realWarn;
26
- console.error = realError;
27
- }
28
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * A document for tier 3 — see docs/testing.md.
3
- *
4
- * The landing code touches nothing global: it is handed a root and calls
5
- * `matches`, `querySelectorAll`, `cloneNode` and `setAttribute` on it. So a
6
- * test needs a parsed tree and nothing else — no globals installed, no
7
- * custom elements registered, no window on the loose between test files.
8
- *
9
- * Markup is parsed as the body of a document rather than into a detached
10
- * `<div>`, because the parser's content models are half of what this code
11
- * exists to work around: a `<tr>` assigned to a div's innerHTML is discarded,
12
- * and a test that lost its rows would be testing the parser.
13
- */
14
- /** Parse markup and hand back the body holding it. */
15
- export declare function body(html: string): HTMLElement;
16
- /** Parse markup and hand back its first element. */
17
- export declare function one(html: string): Element;
18
- //# sourceMappingURL=dom.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"dom.d.ts","sourceRoot":"","sources":["../../../tests/helpers/dom.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,sDAAsD;AACtD,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAE9C;AAED,oDAAoD;AACpD,wBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEzC"}