@loadbare/app 0.4.0 → 0.5.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 +53 -82
- package/dist/build/assemble.d.ts +7 -5
- package/dist/build/assemble.d.ts.map +1 -1
- package/dist/build/assemble.js +29 -9
- package/dist/build/cli.d.ts +20 -12
- package/dist/build/cli.d.ts.map +1 -1
- package/dist/build/cli.js +34 -16
- package/dist/build/elements.d.ts +15 -29
- package/dist/build/elements.d.ts.map +1 -1
- package/dist/build/elements.js +25 -111
- package/dist/build/expand.d.ts +1 -1
- package/dist/build/expand.js +1 -1
- package/dist/build/locations.d.ts +14 -37
- package/dist/build/locations.d.ts.map +1 -1
- package/dist/build/locations.js +24 -67
- package/dist/build/origins.d.ts +109 -0
- package/dist/build/origins.d.ts.map +1 -0
- package/dist/build/origins.js +270 -0
- package/dist/core/lb-constants.d.ts +1 -0
- package/dist/core/lb-constants.d.ts.map +1 -1
- package/dist/core/lb-constants.js +15 -8
- package/dist/core/lb-types.d.ts +2 -2
- package/dist/core/lb-types.d.ts.map +1 -1
- package/dist/hub/lb-apply.js +1 -1
- package/dist/hub/lb-hub.d.ts.map +1 -1
- package/dist/hub/lb-hub.js +44 -17
- package/dist/hub/lb-rows.js +3 -3
- package/dist/server/lb-server.d.ts +5 -4
- package/dist/server/lb-server.d.ts.map +1 -1
- package/dist/tests/assemble.test.js +11 -4
- package/dist/tests/elements.test.js +47 -51
- package/dist/tests/expand.test.d.ts +1 -1
- package/dist/tests/expand.test.js +2 -2
- package/dist/tests/fixtures/elements/collision/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/collision/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/collision/imports.js +1 -0
- package/dist/tests/fixtures/elements/manifest/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/manifest/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest/imports.js +1 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts +3 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest-bad-entry/imports.js +1 -0
- package/dist/tests/fixtures/elements/{collision/elements.d.ts → manifest-not-array/imports.d.ts} +1 -1
- package/dist/tests/fixtures/elements/manifest-not-array/imports.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/manifest-not-array/imports.js +1 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts +2 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.d.ts.map +1 -0
- package/dist/tests/fixtures/elements/pkg/acme-widget.js +1 -0
- package/dist/tests/lb-express.test.js +1 -1
- package/dist/tests/origins.test.d.ts +10 -0
- package/dist/tests/origins.test.d.ts.map +1 -0
- package/dist/tests/origins.test.js +326 -0
- package/dist/tests/pages.test.js +3 -3
- package/dist/tests/styles.test.js +7 -4
- package/docs/reference/builder.md +128 -0
- package/docs/reference/chrome.md +75 -0
- package/docs/reference/css.md +44 -0
- package/docs/reference/custom-elements.md +327 -0
- package/docs/reference/data-binding.md +240 -0
- package/docs/reference/overview.md +38 -0
- package/docs/reference/page-files.md +175 -0
- package/docs/reference/server.md +123 -0
- package/docs/reference/widgets.md +163 -0
- package/docs/roadmap.md +130 -0
- package/docs/testing.md +228 -0
- package/docs/theory.md +344 -223
- package/docs/tutorials/000-getting-started.md +86 -0
- package/docs/tutorials/010-pages-and-navigation.md +129 -0
- package/docs/tutorials/020-css.md +103 -0
- package/docs/tutorials/030-html-decomposition.md +79 -0
- package/docs/tutorials/040-displaying-data.md +169 -0
- package/docs/tutorials/050-actions.md +77 -0
- package/docs/tutorials/060-custom-element-code.md +73 -0
- package/docs/tutorials/065-conditional-rendering.md +161 -0
- package/docs/tutorials/070-displaying-a-list.md +137 -0
- package/docs/tutorials/072-inserting-into-a-list.md +88 -0
- package/docs/tutorials/074-deleting-from-a-list.md +77 -0
- package/docs/tutorials/076-updating-a-list-item.md +86 -0
- package/docs/tutorials/080-widget-requests.md +124 -0
- package/docs/tutorials/090-using-widget-libraries.md +75 -0
- package/package.json +4 -12
- package/dist/client.js +0 -522
- package/dist/demo-static/src/widgets/app-box.d.ts +0 -15
- package/dist/demo-static/src/widgets/app-box.d.ts.map +0 -1
- package/dist/demo-static/src/widgets/app-box.js +0 -19
- package/dist/tests/fixtures/elements/collision/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/collision/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-tag/elements.js +0 -3
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts +0 -5
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.d.ts.map +0 -1
- package/dist/tests/fixtures/elements/manifest-bad-value/elements.js +0 -3
- package/dist/tests/golden.test.d.ts +0 -19
- package/dist/tests/golden.test.d.ts.map +0 -1
- package/dist/tests/golden.test.js +0 -60
- package/dist/tests/helpers/window.d.ts +0 -43
- package/dist/tests/helpers/window.d.ts.map +0 -1
- package/dist/tests/helpers/window.js +0 -78
- package/dist/tests/lb-input.test.d.ts +0 -9
- package/dist/tests/lb-input.test.d.ts.map +0 -1
- package/dist/tests/lb-input.test.js +0 -78
- package/dist/tests/lb-list.test.d.ts +0 -12
- package/dist/tests/lb-list.test.d.ts.map +0 -1
- package/dist/tests/lb-list.test.js +0 -44
- package/dist/tests/lb-options.test.d.ts +0 -10
- package/dist/tests/lb-options.test.d.ts.map +0 -1
- package/dist/tests/lb-options.test.js +0 -121
- package/dist/tests/lb-picker.test.d.ts +0 -14
- package/dist/tests/lb-picker.test.d.ts.map +0 -1
- package/dist/tests/lb-picker.test.js +0 -59
- package/dist/tests/lb-select.test.d.ts +0 -9
- package/dist/tests/lb-select.test.d.ts.map +0 -1
- package/dist/tests/lb-select.test.js +0 -71
- package/dist/tests/lb-table.test.d.ts +0 -15
- package/dist/tests/lb-table.test.d.ts.map +0 -1
- package/dist/tests/lb-table.test.js +0 -205
- package/dist/widgets/index.d.ts +0 -7
- package/dist/widgets/index.d.ts.map +0 -1
- package/dist/widgets/index.js +0 -6
- package/dist/widgets/lb-input.d.ts +0 -2
- package/dist/widgets/lb-input.d.ts.map +0 -1
- package/dist/widgets/lb-input.js +0 -48
- package/dist/widgets/lb-list.d.ts +0 -2
- package/dist/widgets/lb-list.d.ts.map +0 -1
- package/dist/widgets/lb-list.js +0 -17
- package/dist/widgets/lb-options.d.ts +0 -26
- package/dist/widgets/lb-options.d.ts.map +0 -1
- package/dist/widgets/lb-options.js +0 -72
- package/dist/widgets/lb-picker.d.ts +0 -2
- package/dist/widgets/lb-picker.d.ts.map +0 -1
- package/dist/widgets/lb-picker.js +0 -25
- package/dist/widgets/lb-select.d.ts +0 -2
- package/dist/widgets/lb-select.d.ts.map +0 -1
- package/dist/widgets/lb-select.js +0 -43
- package/dist/widgets/lb-table.d.ts +0 -2
- package/dist/widgets/lb-table.d.ts.map +0 -1
- package/dist/widgets/lb-table.js +0 -113
- package/docs/application-chrome.md +0 -36
- package/docs/building-html-pages.md +0 -130
- package/docs/getting-started.md +0 -120
- package/docs/guide.md +0 -1164
- package/docs/hosting.md +0 -218
- package/docs/latent-risks.md +0 -20
- package/widgets/index.ts +0 -6
- package/widgets/lb-input.html +0 -1
- package/widgets/lb-input.ts +0 -64
- package/widgets/lb-list.html +0 -1
- package/widgets/lb-list.ts +0 -21
- package/widgets/lb-options.html +0 -4
- package/widgets/lb-options.ts +0 -88
- package/widgets/lb-picker.html +0 -7
- package/widgets/lb-picker.ts +0 -27
- package/widgets/lb-select.html +0 -4
- package/widgets/lb-select.ts +0 -55
- package/widgets/lb-table.html +0 -8
- package/widgets/lb-table.ts +0 -126
|
@@ -9,20 +9,26 @@ import { describe, it } from "node:test";
|
|
|
9
9
|
import { fileURLToPath } from "node:url";
|
|
10
10
|
import { customElementTags } from "../build/expand";
|
|
11
11
|
import { loadElementManifest, resolveElements } from "../build/elements";
|
|
12
|
-
import {
|
|
12
|
+
import { packageInfo } from "../build/locations";
|
|
13
|
+
import { scanOrigin } from "../build/origins";
|
|
13
14
|
const FIXTURES = fileURLToPath(new URL("./fixtures/elements", import.meta.url));
|
|
14
15
|
/**
|
|
15
|
-
* The
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* resolveElements does not need one.
|
|
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
19
|
*/
|
|
20
|
-
async function
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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));
|
|
26
32
|
}
|
|
27
33
|
describe("customElementTags", () => {
|
|
28
34
|
it("collects every hyphenated tag, native elements excluded", () => {
|
|
@@ -35,77 +41,67 @@ describe("customElementTags", () => {
|
|
|
35
41
|
});
|
|
36
42
|
});
|
|
37
43
|
describe("loadElementManifest", () => {
|
|
38
|
-
it("returns the
|
|
39
|
-
const
|
|
40
|
-
assert.deepEqual(
|
|
44
|
+
it("returns the declared package list", async () => {
|
|
45
|
+
const declared = await loadElementManifest(`${FIXTURES}/manifest/imports.ts`);
|
|
46
|
+
assert.deepEqual(declared, ["@acme/widget"]);
|
|
41
47
|
});
|
|
42
|
-
it("returns empty when there is no
|
|
43
|
-
const
|
|
44
|
-
assert.deepEqual(
|
|
48
|
+
it("returns empty when there is no imports.ts", async () => {
|
|
49
|
+
const declared = await loadElementManifest(`${FIXTURES}/local/imports.ts`);
|
|
50
|
+
assert.deepEqual(declared, []);
|
|
45
51
|
});
|
|
46
|
-
it("rejects a
|
|
47
|
-
await assert.rejects(() => loadElementManifest(`${FIXTURES}/manifest-
|
|
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/);
|
|
48
54
|
});
|
|
49
|
-
it("rejects
|
|
50
|
-
await assert.rejects(() => loadElementManifest(`${FIXTURES}/manifest-bad-
|
|
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/);
|
|
51
57
|
});
|
|
52
58
|
});
|
|
53
59
|
describe("resolveElements", () => {
|
|
54
|
-
it("resolves
|
|
55
|
-
const { resolved } =
|
|
56
|
-
used: new Set(["lb-input"]),
|
|
57
|
-
...(await locationsFor(`${FIXTURES}/local`)),
|
|
58
|
-
});
|
|
59
|
-
assert.deepEqual(resolved.get("lb-input"), {
|
|
60
|
-
kind: "specifier",
|
|
61
|
-
value: "@loadbare/app/widgets/lb-input",
|
|
62
|
-
});
|
|
63
|
-
});
|
|
64
|
-
it("resolves lb-hub the same way, by its script in hub/ rather than widgets/", async () => {
|
|
65
|
-
const { resolved } = await resolveElements({
|
|
60
|
+
it("resolves lb-hub to the package itself, the one built-in", async () => {
|
|
61
|
+
const { resolved } = resolveElements({
|
|
66
62
|
used: new Set(["lb-hub"]),
|
|
67
|
-
|
|
63
|
+
scans: await scansFor(`${FIXTURES}/local`),
|
|
68
64
|
});
|
|
69
65
|
assert.deepEqual(resolved.get("lb-hub"), {
|
|
70
66
|
kind: "specifier",
|
|
71
67
|
value: "@loadbare/app",
|
|
72
68
|
});
|
|
73
69
|
});
|
|
74
|
-
it("resolves a local tag to its file in <src
|
|
75
|
-
const { resolved } =
|
|
70
|
+
it("resolves a local tag to its file in <src>", async () => {
|
|
71
|
+
const { resolved } = resolveElements({
|
|
76
72
|
used: new Set(["app-box"]),
|
|
77
|
-
|
|
73
|
+
scans: await scansFor(`${FIXTURES}/local`),
|
|
78
74
|
});
|
|
79
75
|
assert.equal(resolved.get("app-box")?.kind, "file");
|
|
80
76
|
assert.match(resolved.get("app-box").value, /local\/widgets\/app-box$/);
|
|
81
77
|
});
|
|
82
|
-
it("resolves a tag
|
|
83
|
-
const { resolved } =
|
|
78
|
+
it("resolves a tag found by scanning a declared package", async () => {
|
|
79
|
+
const { resolved } = resolveElements({
|
|
84
80
|
used: new Set(["acme-widget"]),
|
|
85
|
-
|
|
86
|
-
});
|
|
87
|
-
assert.deepEqual(resolved.get("acme-widget"), {
|
|
88
|
-
kind: "specifier",
|
|
89
|
-
value: "@acme/widget",
|
|
81
|
+
scans: await scansFor(`${FIXTURES}/manifest`, `${FIXTURES}/pkg`),
|
|
90
82
|
});
|
|
83
|
+
assert.equal(resolved.get("acme-widget")?.kind, "file");
|
|
84
|
+
assert.match(resolved.get("acme-widget").value, /pkg\/acme-widget$/);
|
|
91
85
|
});
|
|
92
86
|
it("needs no script for a tag with only an .html definition", async () => {
|
|
93
|
-
const { resolved } =
|
|
87
|
+
const { resolved } = resolveElements({
|
|
94
88
|
used: new Set(["lb-nav"]),
|
|
95
|
-
|
|
89
|
+
scans: await scansFor(`${FIXTURES}/markup-only`),
|
|
96
90
|
});
|
|
97
91
|
assert.equal(resolved.size, 0);
|
|
98
92
|
});
|
|
99
93
|
it("throws when a used tag registers nothing", async () => {
|
|
100
94
|
await assert.rejects(async () => resolveElements({
|
|
101
95
|
used: new Set(["nowhere-widget"]),
|
|
102
|
-
|
|
96
|
+
scans: await scansFor(`${FIXTURES}/local`),
|
|
103
97
|
}), /<nowhere-widget> has neither a script nor a definition/);
|
|
104
98
|
});
|
|
105
|
-
it("
|
|
106
|
-
|
|
99
|
+
it("lets src win over a package that has the same tag", async () => {
|
|
100
|
+
const { resolved } = resolveElements({
|
|
107
101
|
used: new Set(["acme-widget"]),
|
|
108
|
-
|
|
109
|
-
})
|
|
102
|
+
scans: await scansFor(`${FIXTURES}/collision`, `${FIXTURES}/pkg`),
|
|
103
|
+
});
|
|
104
|
+
assert.equal(resolved.get("acme-widget")?.kind, "file");
|
|
105
|
+
assert.match(resolved.get("acme-widget").value, /collision\/widgets\/acme-widget$/);
|
|
110
106
|
});
|
|
111
107
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Expansion — see docs/
|
|
2
|
+
* Expansion — see docs/reference/custom-elements.md and docs/testing.md, tier 1.
|
|
3
3
|
*
|
|
4
4
|
* Definitions are written inline here because `expand` takes them as a map
|
|
5
5
|
* and a two-line definition beside its assertion reads better than a file
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Expansion — see docs/
|
|
2
|
+
* Expansion — see docs/reference/custom-elements.md and docs/testing.md, tier 1.
|
|
3
3
|
*
|
|
4
4
|
* Definitions are written inline here because `expand` takes them as a map
|
|
5
5
|
* and a two-line definition beside its assertion reads better than a file
|
|
@@ -192,7 +192,7 @@ describe("placement", () => {
|
|
|
192
192
|
assert.match(html, /<span>row<\/span>/);
|
|
193
193
|
});
|
|
194
194
|
it("expands a tag outside the lb- namespace when it has a definition", () => {
|
|
195
|
-
// An application's widget can use its own prefix — see docs/getting-started.md.
|
|
195
|
+
// An application's widget can use its own prefix — see docs/tutorials/000-getting-started.md.
|
|
196
196
|
// The definition must still apply; discovering it is not enough.
|
|
197
197
|
const html = expand(`<app-nav exp-label="Site"></app-nav>`, defs({ "app-nav": `<nav aria-label="{{label}}" lb-slot></nav>` }));
|
|
198
198
|
assert.match(html, /<nav aria-label="Site"><\/nav>/);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/collision/imports.ts"],"names":[],"mappings":";AAAA,wBAAgC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default ["@acme/widget"];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/manifest/imports.ts"],"names":[],"mappings":";AAAA,wBAAgC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default ["@acme/widget"];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/manifest-bad-entry/imports.ts"],"names":[],"mappings":";AAAA,wBAAoC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default ["@acme/widget", 42];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"imports.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/manifest-not-array/imports.ts"],"names":[],"mappings":";;;AAAA,wBAAiD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default { "acme-widget": "@acme/widget" };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"acme-widget.d.ts","sourceRoot":"","sources":["../../../../../tests/fixtures/elements/pkg/acme-widget.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -107,7 +107,7 @@ describe("the request endpoint", () => {
|
|
|
107
107
|
assert.equal(calls[0].name, "pick");
|
|
108
108
|
assert.equal(calls[0].page, "roster");
|
|
109
109
|
});
|
|
110
|
-
it("passes on an action carrying no
|
|
110
|
+
it("passes on an action carrying no binding at all", async () => {
|
|
111
111
|
// A button carries no value and may sit outside any scope. Undefined
|
|
112
112
|
// stays undefined rather than becoming the empty string.
|
|
113
113
|
calls.length = 0;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The origin cascade — see build/origins.ts.
|
|
3
|
+
*
|
|
4
|
+
* Definitions, scripts and stylesheets all come from the same ordered list of
|
|
5
|
+
* origins, so they are all tested here: that one origin is scanned the same
|
|
6
|
+
* way whatever it is, that a duplicate tag inside one origin is an error, and
|
|
7
|
+
* that a later origin wins over an earlier one.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=origins.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"origins.test.d.ts","sourceRoot":"","sources":["../../tests/origins.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The origin cascade — see build/origins.ts.
|
|
3
|
+
*
|
|
4
|
+
* Definitions, scripts and stylesheets all come from the same ordered list of
|
|
5
|
+
* origins, so they are all tested here: that one origin is scanned the same
|
|
6
|
+
* way whatever it is, that a duplicate tag inside one origin is an error, and
|
|
7
|
+
* that a later origin wins over an earlier one.
|
|
8
|
+
*/
|
|
9
|
+
import assert from "node:assert/strict";
|
|
10
|
+
import { describe, it } from "node:test";
|
|
11
|
+
import { mkdtemp, mkdir, rm, writeFile } from "node:fs/promises";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
import { buildOrigins, cssFrom, scanAll, scanOrigin } from "../build/origins";
|
|
15
|
+
async function tree(files) {
|
|
16
|
+
const dir = await mkdtemp(path.join(tmpdir(), "loadbare-origins-"));
|
|
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
|
+
/** A builtin with no files of its own, so a test sees only what it wrote. */
|
|
25
|
+
const NO_BUILTIN = {
|
|
26
|
+
builtinHubDir: path.join(tmpdir(), "loadbare-absent-hub"),
|
|
27
|
+
packageName: "@loadbare/app",
|
|
28
|
+
};
|
|
29
|
+
const base = (name) => path.basename(name);
|
|
30
|
+
describe("scanOrigin", () => {
|
|
31
|
+
it("classifies by filename: a tag-shaped .html, .ts, and any .css", async () => {
|
|
32
|
+
const dir = await tree({
|
|
33
|
+
"app-box.html": "<div lb-slot></div>",
|
|
34
|
+
"app-box.ts": "export {};",
|
|
35
|
+
"theme.css": "* {}",
|
|
36
|
+
});
|
|
37
|
+
try {
|
|
38
|
+
const scan = await scanOrigin({ name: "src", dirs: [dir] });
|
|
39
|
+
assert.deepEqual([...scan.definitions.keys()], ["app-box"]);
|
|
40
|
+
assert.deepEqual([...scan.scripts.keys()], ["app-box"]);
|
|
41
|
+
assert.deepEqual(scan.css.map(base), ["theme.css"]);
|
|
42
|
+
}
|
|
43
|
+
finally {
|
|
44
|
+
await rm(dir, { recursive: true, force: true });
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
it("ignores files whose basename is not a tag", async () => {
|
|
48
|
+
const dir = await tree({
|
|
49
|
+
"chrome.html": "<lb-hub><main></main></lb-hub>",
|
|
50
|
+
"about.page.html": "<h1>About</h1>",
|
|
51
|
+
"about.hooks.ts": "export const hooks = {};",
|
|
52
|
+
"imports.ts": "export default {};",
|
|
53
|
+
});
|
|
54
|
+
try {
|
|
55
|
+
const scan = await scanOrigin({ name: "src", dirs: [dir] });
|
|
56
|
+
assert.equal(scan.definitions.size, 0);
|
|
57
|
+
assert.equal(scan.scripts.size, 0);
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
await rm(dir, { recursive: true, force: true });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
it("finds files at any depth, and sorts css by filename not directory", async () => {
|
|
64
|
+
const dir = await tree({
|
|
65
|
+
"deep/nested/app-box.ts": "export {};",
|
|
66
|
+
"widgets/app-box.css": ".b {}",
|
|
67
|
+
"00-reset.css": "* {}",
|
|
68
|
+
});
|
|
69
|
+
try {
|
|
70
|
+
const scan = await scanOrigin({ name: "src", dirs: [dir] });
|
|
71
|
+
assert.deepEqual([...scan.scripts.keys()], ["app-box"]);
|
|
72
|
+
assert.deepEqual(scan.css.map(base), ["00-reset.css", "app-box.css"]);
|
|
73
|
+
}
|
|
74
|
+
finally {
|
|
75
|
+
await rm(dir, { recursive: true, force: true });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
it("throws when one origin has two definitions for the same tag", async () => {
|
|
79
|
+
const dir = await tree({
|
|
80
|
+
"a/app-box.html": "<div></div>",
|
|
81
|
+
"b/app-box.html": "<span></span>",
|
|
82
|
+
});
|
|
83
|
+
try {
|
|
84
|
+
await assert.rejects(() => scanOrigin({ name: "src", dirs: [dir] }), /'src' has two definition files for <app-box>/);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await rm(dir, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
it("throws when one origin has two scripts for the same tag", async () => {
|
|
91
|
+
const dir = await tree({
|
|
92
|
+
"a/app-box.ts": "export {};",
|
|
93
|
+
"b/app-box.ts": "export {};",
|
|
94
|
+
});
|
|
95
|
+
try {
|
|
96
|
+
await assert.rejects(() => scanOrigin({ name: "src", dirs: [dir] }), /'src' has two script files for <app-box>/);
|
|
97
|
+
}
|
|
98
|
+
finally {
|
|
99
|
+
await rm(dir, { recursive: true, force: true });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
it("takes a compiled .js as a script, the way a published package ships one", async () => {
|
|
103
|
+
const dir = await tree({
|
|
104
|
+
"acme-widget.html": "<div lb-slot></div>",
|
|
105
|
+
"acme-widget.js": "export {};",
|
|
106
|
+
"acme-widget.d.ts": "export {};",
|
|
107
|
+
});
|
|
108
|
+
try {
|
|
109
|
+
const scan = await scanOrigin({ name: "@acme/widgets", dirs: [dir] });
|
|
110
|
+
assert.deepEqual([...scan.definitions.keys()], ["acme-widget"]);
|
|
111
|
+
// The .d.ts is not a second script: stripping ".ts" leaves a dot.
|
|
112
|
+
assert.deepEqual([...scan.scripts.keys()], ["acme-widget"]);
|
|
113
|
+
assert.match(scan.scripts.get("acme-widget"), /acme-widget$/);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await rm(dir, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
describe("buildOrigins", () => {
|
|
121
|
+
it("orders the built-in first, then packages as declared, then src", async () => {
|
|
122
|
+
const src = await tree({
|
|
123
|
+
"node_modules/pkg-a/package.json": "{}",
|
|
124
|
+
"node_modules/pkg-b/package.json": "{}",
|
|
125
|
+
});
|
|
126
|
+
try {
|
|
127
|
+
const origins = await buildOrigins(NO_BUILTIN, ["pkg-b", "pkg-a"], src);
|
|
128
|
+
assert.deepEqual(origins.map((o) => o.name), ["@loadbare/app", "pkg-b", "pkg-a", src]);
|
|
129
|
+
}
|
|
130
|
+
finally {
|
|
131
|
+
await rm(src, { recursive: true, force: true });
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
it("throws when a declared package is not installed", async () => {
|
|
135
|
+
const src = await tree({});
|
|
136
|
+
try {
|
|
137
|
+
await assert.rejects(() => buildOrigins(NO_BUILTIN, ["nowhere-pkg"], src), /could not find installed package 'nowhere-pkg'/);
|
|
138
|
+
}
|
|
139
|
+
finally {
|
|
140
|
+
await rm(src, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
it("walks up from src to find node_modules at the application root", async () => {
|
|
144
|
+
const src = await tree({
|
|
145
|
+
"node_modules/pkg-a/package.json": "{}",
|
|
146
|
+
"node_modules/pkg-a/style.css": "a {}",
|
|
147
|
+
"app/src/chrome.html": "<lb-hub><main></main></lb-hub>",
|
|
148
|
+
});
|
|
149
|
+
try {
|
|
150
|
+
const inner = path.join(src, "app", "src");
|
|
151
|
+
const origins = await buildOrigins(NO_BUILTIN, ["pkg-a"], inner);
|
|
152
|
+
const scans = await Promise.all(origins.map(scanOrigin));
|
|
153
|
+
assert.deepEqual(cssFrom(scans).map(base), ["style.css"]);
|
|
154
|
+
}
|
|
155
|
+
finally {
|
|
156
|
+
await rm(src, { recursive: true, force: true });
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe("a package's declared widget directory", () => {
|
|
161
|
+
/**
|
|
162
|
+
* The published shape this exists for: compiled output beside the source it
|
|
163
|
+
* was built from. Scanning the whole package finds both and calls the tag
|
|
164
|
+
* a duplicate; scanning what the package declared finds the one that ships.
|
|
165
|
+
*/
|
|
166
|
+
it("scans only loadbare.widgets, so source beside dist is not a duplicate", async () => {
|
|
167
|
+
const src = await tree({
|
|
168
|
+
"node_modules/acme/package.json": JSON.stringify({
|
|
169
|
+
loadbare: { widgets: "./dist" },
|
|
170
|
+
}),
|
|
171
|
+
"node_modules/acme/dist/acme-widget.js": "export {};",
|
|
172
|
+
"node_modules/acme/dist/acme-widget.html": "<b lb-slot></b>",
|
|
173
|
+
"node_modules/acme/src/acme-widget.ts": "export {};",
|
|
174
|
+
"node_modules/acme/src/acme-widget.html": "<b lb-slot></b>",
|
|
175
|
+
"node_modules/acme/README.md": "# acme",
|
|
176
|
+
"imports.ts": `export default ["acme"];`,
|
|
177
|
+
});
|
|
178
|
+
try {
|
|
179
|
+
const scans = await scanAll(NO_BUILTIN, src, path.join(src, "imports.ts"));
|
|
180
|
+
const acme = scans.find((scan) => scan.name === "acme");
|
|
181
|
+
assert.deepEqual([...acme.scripts.keys()], ["acme-widget"]);
|
|
182
|
+
assert.match(acme.scripts.get("acme-widget"), /dist[/\\]acme-widget$/);
|
|
183
|
+
assert.match(acme.definitions.get("acme-widget"), /dist[/\\]acme-widget\.html$/);
|
|
184
|
+
}
|
|
185
|
+
finally {
|
|
186
|
+
await rm(src, { recursive: true, force: true });
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
it("scans the whole package when it declares nothing", async () => {
|
|
190
|
+
const src = await tree({
|
|
191
|
+
"node_modules/acme/package.json": "{}",
|
|
192
|
+
"node_modules/acme/widgets/acme-widget.ts": "export {};",
|
|
193
|
+
});
|
|
194
|
+
try {
|
|
195
|
+
const origins = await buildOrigins(NO_BUILTIN, ["acme"], src);
|
|
196
|
+
const scans = await Promise.all(origins.map(scanOrigin));
|
|
197
|
+
assert.deepEqual([...scans[1].scripts.keys()], ["acme-widget"]);
|
|
198
|
+
}
|
|
199
|
+
finally {
|
|
200
|
+
await rm(src, { recursive: true, force: true });
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
it("refuses a declared directory the installed package does not have", async () => {
|
|
204
|
+
const src = await tree({
|
|
205
|
+
"node_modules/acme/package.json": JSON.stringify({
|
|
206
|
+
loadbare: { widgets: "./dist" },
|
|
207
|
+
}),
|
|
208
|
+
"node_modules/acme/widgets/acme-widget.ts": "export {};",
|
|
209
|
+
});
|
|
210
|
+
try {
|
|
211
|
+
await assert.rejects(() => buildOrigins(NO_BUILTIN, ["acme"], src), /'acme' declares widgets at '\.\/dist', which is not in the installed package/);
|
|
212
|
+
}
|
|
213
|
+
finally {
|
|
214
|
+
await rm(src, { recursive: true, force: true });
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
it("refuses a declared directory outside the package", async () => {
|
|
218
|
+
const src = await tree({
|
|
219
|
+
"node_modules/acme/package.json": JSON.stringify({
|
|
220
|
+
loadbare: { widgets: "../../src" },
|
|
221
|
+
}),
|
|
222
|
+
});
|
|
223
|
+
try {
|
|
224
|
+
await assert.rejects(() => buildOrigins(NO_BUILTIN, ["acme"], src), /which is outside the package/);
|
|
225
|
+
}
|
|
226
|
+
finally {
|
|
227
|
+
await rm(src, { recursive: true, force: true });
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
it("refuses a loadbare.widgets that is not a path", async () => {
|
|
231
|
+
const src = await tree({
|
|
232
|
+
"node_modules/acme/package.json": JSON.stringify({
|
|
233
|
+
loadbare: { widgets: ["./dist"] },
|
|
234
|
+
}),
|
|
235
|
+
});
|
|
236
|
+
try {
|
|
237
|
+
await assert.rejects(() => buildOrigins(NO_BUILTIN, ["acme"], src), /is not a path/);
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
await rm(src, { recursive: true, force: true });
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
});
|
|
244
|
+
describe("the css cascade", () => {
|
|
245
|
+
it("collects one package's CSS, sorted by filename", async () => {
|
|
246
|
+
const src = await tree({
|
|
247
|
+
"node_modules/acme-widgets/package.json": "{}",
|
|
248
|
+
"node_modules/acme-widgets/z.css": "z {}",
|
|
249
|
+
"node_modules/acme-widgets/a.css": "a {}",
|
|
250
|
+
"node_modules/acme-widgets/dist/00-reset.css": "* { margin: 0; }",
|
|
251
|
+
"imports.ts": `export default ["acme-widgets"];`,
|
|
252
|
+
});
|
|
253
|
+
try {
|
|
254
|
+
const scans = await scanAll(NO_BUILTIN, src, path.join(src, "imports.ts"));
|
|
255
|
+
assert.deepEqual(cssFrom(scans).map(base), [
|
|
256
|
+
"00-reset.css",
|
|
257
|
+
"a.css",
|
|
258
|
+
"z.css",
|
|
259
|
+
]);
|
|
260
|
+
}
|
|
261
|
+
finally {
|
|
262
|
+
await rm(src, { recursive: true, force: true });
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
it("keeps packages in declaration order, without interleaving their files", async () => {
|
|
266
|
+
const src = await tree({
|
|
267
|
+
"node_modules/pkg-a/package.json": "{}",
|
|
268
|
+
"node_modules/pkg-a/a-only.css": "a {}",
|
|
269
|
+
"node_modules/pkg-b/package.json": "{}",
|
|
270
|
+
"node_modules/pkg-b/aaa-early.css": "b {}",
|
|
271
|
+
"imports.ts": `export default ["pkg-b", "pkg-a"];`,
|
|
272
|
+
});
|
|
273
|
+
try {
|
|
274
|
+
const scans = await scanAll(NO_BUILTIN, src, path.join(src, "imports.ts"));
|
|
275
|
+
assert.deepEqual(cssFrom(scans).map(base), [
|
|
276
|
+
"aaa-early.css",
|
|
277
|
+
"a-only.css",
|
|
278
|
+
]);
|
|
279
|
+
}
|
|
280
|
+
finally {
|
|
281
|
+
await rm(src, { recursive: true, force: true });
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
it("puts the application's own css last, after every package's", async () => {
|
|
285
|
+
const src = await tree({
|
|
286
|
+
"node_modules/pkg-a/package.json": "{}",
|
|
287
|
+
"node_modules/pkg-a/zzz-late.css": "a {}",
|
|
288
|
+
"00-reset.css": "* {}",
|
|
289
|
+
"imports.ts": `export default ["pkg-a"];`,
|
|
290
|
+
});
|
|
291
|
+
try {
|
|
292
|
+
const scans = await scanAll(NO_BUILTIN, src, path.join(src, "imports.ts"));
|
|
293
|
+
assert.deepEqual(cssFrom(scans).map(base), [
|
|
294
|
+
"zzz-late.css",
|
|
295
|
+
"00-reset.css",
|
|
296
|
+
]);
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
await rm(src, { recursive: true, force: true });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
it("ignores a package's own nested node_modules", async () => {
|
|
303
|
+
const src = await tree({
|
|
304
|
+
"node_modules/pkg-a/package.json": "{}",
|
|
305
|
+
"node_modules/pkg-a/style.css": "a {}",
|
|
306
|
+
"node_modules/pkg-a/node_modules/transitive/style.css": "t {}",
|
|
307
|
+
"imports.ts": `export default ["pkg-a"];`,
|
|
308
|
+
});
|
|
309
|
+
try {
|
|
310
|
+
const scans = await scanAll(NO_BUILTIN, src, path.join(src, "imports.ts"));
|
|
311
|
+
assert.deepEqual(cssFrom(scans).map(base), ["style.css"]);
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
await rm(src, { recursive: true, force: true });
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
it("returns nothing when there is no css anywhere", async () => {
|
|
318
|
+
const src = await tree({ "chrome.html": "<lb-hub><main></main></lb-hub>" });
|
|
319
|
+
try {
|
|
320
|
+
assert.deepEqual(cssFrom(await scanAll(NO_BUILTIN, src)), []);
|
|
321
|
+
}
|
|
322
|
+
finally {
|
|
323
|
+
await rm(src, { recursive: true, force: true });
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
});
|
package/dist/tests/pages.test.js
CHANGED
|
@@ -22,7 +22,7 @@ async function tree(files) {
|
|
|
22
22
|
describe("discovering hooks and queries", () => {
|
|
23
23
|
it("pairs a *.hooks.ts and *.queries.ts with the page sharing their basename", async () => {
|
|
24
24
|
const src = await tree({
|
|
25
|
-
"chrome.
|
|
25
|
+
"chrome.html": CHROME,
|
|
26
26
|
"pages/hello.page.html": `<p>hi</p>`,
|
|
27
27
|
"pages/hello.hooks.ts": `export const hooks = {};`,
|
|
28
28
|
"pages/hello.queries.ts": `export const queries = {};`,
|
|
@@ -42,7 +42,7 @@ describe("discovering hooks and queries", () => {
|
|
|
42
42
|
});
|
|
43
43
|
it("allows a page to have only one of the two", async () => {
|
|
44
44
|
const src = await tree({
|
|
45
|
-
"chrome.
|
|
45
|
+
"chrome.html": CHROME,
|
|
46
46
|
"pages/counter.page.html": `<p>0</p>`,
|
|
47
47
|
"pages/counter.queries.ts": `export const queries = {};`,
|
|
48
48
|
});
|
|
@@ -61,7 +61,7 @@ describe("discovering hooks and queries", () => {
|
|
|
61
61
|
});
|
|
62
62
|
it("throws when a hooks or queries file has no matching page", async () => {
|
|
63
63
|
const src = await tree({
|
|
64
|
-
"chrome.
|
|
64
|
+
"chrome.html": CHROME,
|
|
65
65
|
"pages/orphan.hooks.ts": `export const hooks = {};`,
|
|
66
66
|
});
|
|
67
67
|
try {
|