@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
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# The Basic Widget Library
|
|
2
|
+
|
|
3
|
+
Six widgets: `lb-input`, `lb-select`, `lb-list`, `lb-options`, `lb-table`,
|
|
4
|
+
`lb-picker`. Every one of them is written against the same two contracts
|
|
5
|
+
documented elsewhere — [Custom Elements](./custom-elements.md#html) for its
|
|
6
|
+
definition, [Custom Elements](./custom-elements.md#code) for its class —
|
|
7
|
+
nothing here is special-cased machinery.
|
|
8
|
+
|
|
9
|
+
They ship compiled, in `@loadbare/widgets`, a package the builder resolves the
|
|
10
|
+
way it resolves anyone else's — it declares `"loadbare": { "widgets": "./dist" }`
|
|
11
|
+
and the builder scans that. Install it and list it:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
npm install @loadbare/widgets
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
// src/imports.ts
|
|
19
|
+
export default ["@loadbare/widgets"];
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
See [Using Widget Libraries](../tutorials/090-using-widget-libraries.md) for
|
|
23
|
+
what listing a package does, and [The Builder](./builder.md#where-the-builder-looks)
|
|
24
|
+
for where a listed package sits in the cascade.
|
|
25
|
+
|
|
26
|
+
## `lb-input`
|
|
27
|
+
|
|
28
|
+
Wraps an `<input>`. `lb-value` sets the input's `.value`. The widget sends
|
|
29
|
+
nothing on its own: `data-fire-on-change` asks it to send `cell-change` on the
|
|
30
|
+
input's `change`, addressed by its own `lb-query`/`lb-key`/`lb-cell`
|
|
31
|
+
coordinates.
|
|
32
|
+
|
|
33
|
+
An input inside an `lb-insert` or `lb-update` form leaves the attribute off.
|
|
34
|
+
The form reads every `lb-cell` in it on submit and sends one request for all
|
|
35
|
+
of them, so an input that also sent its own would write the same edit twice.
|
|
36
|
+
|
|
37
|
+
| Parameter | Fills |
|
|
38
|
+
| ---------- | ----- |
|
|
39
|
+
| `exp-label` | the visible `<label>` text |
|
|
40
|
+
| `exp-readonly` | the input's `readonly` attribute |
|
|
41
|
+
|
|
42
|
+
| Attribute | Asks for |
|
|
43
|
+
| ---------- | ----- |
|
|
44
|
+
| `data-fire-on-change` | an edit to be sent, on `change` |
|
|
45
|
+
|
|
46
|
+
## `lb-select`
|
|
47
|
+
|
|
48
|
+
Wraps a `<select>` whose `<option>`s the author writes directly inside (via
|
|
49
|
+
`lb-slot`). `lb-value` sets the select's `.value`; a `change` sends the
|
|
50
|
+
action named by `lb-action`, with the select's `.value` as the request's
|
|
51
|
+
`value` — the choice is the interaction, so this is the case where an
|
|
52
|
+
action carries a value.
|
|
53
|
+
|
|
54
|
+
| Parameter | Fills |
|
|
55
|
+
| ---------- | ----- |
|
|
56
|
+
| `exp-label` | the visible `<label>` text |
|
|
57
|
+
|
|
58
|
+
Requires `lb-action` — a change with none logs and sends nothing.
|
|
59
|
+
|
|
60
|
+
## `lb-list`
|
|
61
|
+
|
|
62
|
+
The plain repeater: whatever the author writes inside a
|
|
63
|
+
`<template lb-key="...">` is cloned once per row, in arrival order. No
|
|
64
|
+
grouping, no sorting, no request of its own — it exists because a
|
|
65
|
+
`Projection` has to land on something with `acceptRows`, and a bare
|
|
66
|
+
`<table>` can't be one (a custom element written inside `<tbody>` is
|
|
67
|
+
discarded by the parser).
|
|
68
|
+
|
|
69
|
+
## `lb-options`
|
|
70
|
+
|
|
71
|
+
A `<select>` whose `<option>`s come from a query instead of being written by
|
|
72
|
+
hand. The author supplies the row template inside the widget (via
|
|
73
|
+
`lb-slot`), same as any list widget:
|
|
74
|
+
|
|
75
|
+
```html
|
|
76
|
+
<lb-options lb-query="statuses" exp-label="Status" lb-action="setStatus">
|
|
77
|
+
<template lb-key="id" lb-group="category">
|
|
78
|
+
<option lb-cell="label"></option>
|
|
79
|
+
</template>
|
|
80
|
+
</lb-options>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
| Parameter | Fills |
|
|
84
|
+
| ---------- | ----- |
|
|
85
|
+
| `exp-label` | the visible `<label>` text |
|
|
86
|
+
|
|
87
|
+
- The row's key becomes the option's `value` — `lb-key` supplies both, so
|
|
88
|
+
nothing is declared twice.
|
|
89
|
+
- `lb-group` on the row template sections the options into `<optgroup>`s,
|
|
90
|
+
one per distinct value, created and removed as rows arrive and leave.
|
|
91
|
+
- `lb-sort` is not read by this widget.
|
|
92
|
+
- A `change` sends the action named by `lb-action`, value from the
|
|
93
|
+
select's `.value`.
|
|
94
|
+
|
|
95
|
+
`lb-picker` is this same class with its row template supplied by the
|
|
96
|
+
definition instead of the page — see below.
|
|
97
|
+
|
|
98
|
+
## `lb-table`
|
|
99
|
+
|
|
100
|
+
A `<table>` that supplies its own scaffolding; the author supplies the
|
|
101
|
+
heading row, the row template, and optionally a footer, each as a
|
|
102
|
+
`<template>` matched to a destination:
|
|
103
|
+
|
|
104
|
+
```html
|
|
105
|
+
<lb-table lb-query="ledger" exp-caption="Ledger">
|
|
106
|
+
<template lb-template="head">
|
|
107
|
+
<tr><th>Date</th><th>Amount</th></tr>
|
|
108
|
+
</template>
|
|
109
|
+
<template lb-key="id" lb-sort="date" lb-group="month">
|
|
110
|
+
<tr><td lb-cell="date"></td><td lb-cell="amount"></td></tr>
|
|
111
|
+
</template>
|
|
112
|
+
<template lb-template="foot">
|
|
113
|
+
<tr lb-query="ledger-total"><td>Total</td><td lb-cell="total"></td></tr>
|
|
114
|
+
</template>
|
|
115
|
+
</lb-table>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
| Parameter | Fills |
|
|
119
|
+
| ---------- | ----- |
|
|
120
|
+
| `exp-caption` | the `<caption>` text |
|
|
121
|
+
|
|
122
|
+
| Destination | Fills |
|
|
123
|
+
| ------------ | ----- |
|
|
124
|
+
| `head` (`lb-template="head"`) | the `<thead>` content |
|
|
125
|
+
| `foot` (`lb-template="foot"`) | the `<tfoot>` content |
|
|
126
|
+
| slot (no `lb-template`) | the row template, via `lb-slot` on `<tbody>` |
|
|
127
|
+
|
|
128
|
+
- `lb-group` on the row template sections rows under a derived heading row,
|
|
129
|
+
one per distinct value, whose `colSpan` matches the row's own column
|
|
130
|
+
count. `lb-sort` orders rows within a section (or the whole body, with no
|
|
131
|
+
grouping) by comparing each row's cell text.
|
|
132
|
+
- The `foot` destination is not delivered through `acceptRows` — it's an
|
|
133
|
+
ordinary scope carrying its own `lb-query`, resolved by name like any
|
|
134
|
+
other on the page. A grand total is a second projection of the same
|
|
135
|
+
data, not a row the hub hands the table.
|
|
136
|
+
|
|
137
|
+
## `lb-picker`
|
|
138
|
+
|
|
139
|
+
`lb-options`, with the row template supplied by the definition instead of
|
|
140
|
+
the page — for when every row is one option and nothing else varies:
|
|
141
|
+
|
|
142
|
+
```html
|
|
143
|
+
<lb-picker
|
|
144
|
+
lb-query="statuses"
|
|
145
|
+
exp-label="Status"
|
|
146
|
+
exp-key="id"
|
|
147
|
+
exp-cell="label"
|
|
148
|
+
exp-group="category"
|
|
149
|
+
lb-action="setStatus"
|
|
150
|
+
></lb-picker>
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
| Parameter | Fills |
|
|
154
|
+
| ---------- | ----- |
|
|
155
|
+
| `exp-label` | the visible `<label>` text |
|
|
156
|
+
| `exp-key` | the row template's `lb-key` |
|
|
157
|
+
| `exp-cell` | the option's `lb-cell` |
|
|
158
|
+
| `exp-group` | the row template's `lb-group` |
|
|
159
|
+
|
|
160
|
+
Behavior — grouping, key-as-value, the action on change — is inherited
|
|
161
|
+
whole from `lb-options`; a page author who needs a second element in the
|
|
162
|
+
row, or an option built from two columns, writes `lb-options` and its own
|
|
163
|
+
`<template>` instead.
|
package/docs/roadmap.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# Roadmap
|
|
2
|
+
|
|
3
|
+
Release 1.0 is optimized to prove that a Loadbare app can be robust and
|
|
4
|
+
highly performant with a codebase that has very low accidental complexity.
|
|
5
|
+
|
|
6
|
+
What follows is everything not yet decided or not yet built: release
|
|
7
|
+
candidates, risks not worth solving speculatively, and open design
|
|
8
|
+
questions. None of it is current behavior — for that, see
|
|
9
|
+
[Reference](./reference/overview.md).
|
|
10
|
+
|
|
11
|
+
## Open questions in the 1.0 mechanism
|
|
12
|
+
|
|
13
|
+
These are not new scope — they're gaps deliberately left unresolved in the
|
|
14
|
+
mechanism Release 1.0 already ships. Each is here because deciding it
|
|
15
|
+
speculatively, before a real page forces the question, risks designing the
|
|
16
|
+
wrong thing. Revisit when the described symptom actually shows up.
|
|
17
|
+
|
|
18
|
+
### Staleness and concurrent writers
|
|
19
|
+
|
|
20
|
+
Two tabs, or two users, updating the same projection at once. A solo
|
|
21
|
+
developer testing in one browser will not produce this by accident, and
|
|
22
|
+
retrofitting a version or conflict check onto every tuple after the fact
|
|
23
|
+
touches every widget that writes.
|
|
24
|
+
|
|
25
|
+
### Nesting
|
|
26
|
+
|
|
27
|
+
Whether a tuple may contain a projection (master-detail, an expanding
|
|
28
|
+
row). [Theory](./theory.md) already flags this as possibly load-bearing if
|
|
29
|
+
disallowed. Worth a decision-in-principle the first time a master-detail
|
|
30
|
+
page is built, even before the mechanism is needed elsewhere.
|
|
31
|
+
|
|
32
|
+
### Whether `lb-query` may be inherited
|
|
33
|
+
|
|
34
|
+
Currently every scope states its own `lb-query`; nothing resolves one from
|
|
35
|
+
an ancestor. Inheritance would be friendlier to the page author but adds a
|
|
36
|
+
resolution rule, and a resolution rule is a mechanism this framework has
|
|
37
|
+
otherwise avoided. Worth deciding before an application grows deep enough
|
|
38
|
+
nesting that restating the query on every level starts to hurt.
|
|
39
|
+
|
|
40
|
+
### Whether `lb-key` is always required
|
|
41
|
+
|
|
42
|
+
Undecided whether every row needs `lb-key`, or only a row something
|
|
43
|
+
targets (a delete button, an update form). Revisit if a list widget shows
|
|
44
|
+
up that never needs to address an individual row by key.
|
|
45
|
+
|
|
46
|
+
### Pending appearance
|
|
47
|
+
|
|
48
|
+
A value that hasn't arrived yet is probably derivable from an absent
|
|
49
|
+
`lb-value` rather than needing a signal of its own. Not yet needed because
|
|
50
|
+
nothing currently produces that gap in practice — revisit if one does.
|
|
51
|
+
|
|
52
|
+
### Validation placement
|
|
53
|
+
|
|
54
|
+
Per-keystroke feedback cannot afford a round trip, so some validation will
|
|
55
|
+
end up living in the widget while the server remains authoritative for the
|
|
56
|
+
same field. Not yet designed — revisit the first time an application needs
|
|
57
|
+
inline validation.
|
|
58
|
+
|
|
59
|
+
## Release 1.1 candidates
|
|
60
|
+
|
|
61
|
+
Net-new scope, not gaps in 1.0. Release 1.1 will optimize for operational
|
|
62
|
+
concerns: maintaining high performance and focus on the essentials in
|
|
63
|
+
different deployment scenarios.
|
|
64
|
+
|
|
65
|
+
### Lazy loading of page templates
|
|
66
|
+
|
|
67
|
+
Release 1.0 packages all pages into HTML `<template>` elements, delivered
|
|
68
|
+
along with the app shell as an HTML monolith on the first page `GET`.
|
|
69
|
+
|
|
70
|
+
For a low page count with fairly simple pages, the monolithic load is
|
|
71
|
+
probably faster than any other approach, and is definitely the simplest
|
|
72
|
+
approach.
|
|
73
|
+
|
|
74
|
+
For a high page count with complex pages, the one-time load of a monolith
|
|
75
|
+
could degrade performance on the first load, not to mention producing a
|
|
76
|
+
very cluttered result in View Page Source.
|
|
77
|
+
|
|
78
|
+
If we add addressable pages, the hub could do a non-blocking gradual load
|
|
79
|
+
of all templates. It could also load each template at first use.
|
|
80
|
+
|
|
81
|
+
### Split data channel from static assets
|
|
82
|
+
|
|
83
|
+
Release 1.0 assumes that static assets are delivered through the same URL
|
|
84
|
+
as data responses.
|
|
85
|
+
|
|
86
|
+
But in Loadbare we have an advantage: the entire browser bundle is static,
|
|
87
|
+
all HTML, JavaScript and CSS is fixed at the time of release.
|
|
88
|
+
|
|
89
|
+
If the hub were configured with a URL for the data channel, the entire app
|
|
90
|
+
could be delivered from a static origin, such as an S3 bucket or static web
|
|
91
|
+
server.
|
|
92
|
+
|
|
93
|
+
### Tree-shaking CSS by tag name
|
|
94
|
+
|
|
95
|
+
Release 1.0 concatenates every `.css` file under `src`, and every one inside
|
|
96
|
+
each imported package. A stylesheet is paired with nothing, so an app ships
|
|
97
|
+
the styles of every widget in every package it imports, used or not.
|
|
98
|
+
|
|
99
|
+
Naming a stylesheet for a tag — `<tag-name>.css` beside `<tag-name>.html`
|
|
100
|
+
and `<tag-name>.ts` — would let the builder ship only the stylesheets whose
|
|
101
|
+
tags survive expansion into the finished document. A `.css` file whose name
|
|
102
|
+
is not a tag would keep today's rule and always ship, which is what
|
|
103
|
+
`00-reset.css` and the rest of an app's own styling already are.
|
|
104
|
+
|
|
105
|
+
This would make a widget three files rather than two, so it changes the
|
|
106
|
+
file set in [Custom Elements](./reference/custom-elements.md) and the
|
|
107
|
+
"paired with nothing" rule in [CSS](./reference/css.md).
|
|
108
|
+
|
|
109
|
+
Most valuable against a large imported widget library, where an app uses a
|
|
110
|
+
small fraction of what ships. Revisit when a real app's `app.css` is big
|
|
111
|
+
enough to measure.
|
|
112
|
+
|
|
113
|
+
### Data binding utilities
|
|
114
|
+
|
|
115
|
+
If a dev team wishes to make their own widgets that identify `lb-query`,
|
|
116
|
+
`lb-key`, `lb-cell`, they must repeat the code that is present in the hub.
|
|
117
|
+
|
|
118
|
+
Perhaps a utility that can be called, like `getDataScope(el)`, to help
|
|
119
|
+
clean up the code in these cases.
|
|
120
|
+
|
|
121
|
+
### A language server
|
|
122
|
+
|
|
123
|
+
...for Loadbare HTML.
|
|
124
|
+
|
|
125
|
+
### I18N
|
|
126
|
+
|
|
127
|
+
Internationalization would require a potential extension to build-time
|
|
128
|
+
expansion allows a strings file. We could either preserve the fully
|
|
129
|
+
static build-time system and create multiple versions of `app.html`, or we
|
|
130
|
+
could add label hydration to the page navigation stage.
|
package/docs/testing.md
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Testing
|
|
2
|
+
|
|
3
|
+
A contributor's document, and a companion to [theory.md](./theory.md). It
|
|
4
|
+
describes how this package tests itself.
|
|
5
|
+
|
|
6
|
+
## Position
|
|
7
|
+
|
|
8
|
+
Loadbare App is four layers with four different relationships to the browser,
|
|
9
|
+
and a single testing strategy would have to be the weakest of them. So there
|
|
10
|
+
are four tiers, and each is tested by the cheapest thing that constitutes
|
|
11
|
+
evidence for it.
|
|
12
|
+
|
|
13
|
+
The ordering principle is that a test should fail for the reason it is named
|
|
14
|
+
after. A test of expansion that needs a browser has bought a second failure
|
|
15
|
+
mode it does not want, and a test of navigation that runs under a DOM
|
|
16
|
+
emulation has given up the only failure mode it was looking for.
|
|
17
|
+
|
|
18
|
+
| Tier | Covers | Environment |
|
|
19
|
+
|------|--------------------------------------------------|-------------|
|
|
20
|
+
| 1 | Expansion and the build — `build/` | node |
|
|
21
|
+
| 2 | The engine — `server/`, and the Express adapter | node |
|
|
22
|
+
| 3 | Landing — `hub/lb-apply.ts`, `hub/lb-rows.ts` | jsdom |
|
|
23
|
+
| 4 | The hub — `hub/lb-hub.ts` | jsdom |
|
|
24
|
+
|
|
25
|
+
All four tiers run under `npm test` today and need no dependency that is not
|
|
26
|
+
already installed. A fifth environment — a real browser — is discussed at the
|
|
27
|
+
end and is not built.
|
|
28
|
+
|
|
29
|
+
## The runner
|
|
30
|
+
|
|
31
|
+
`node:test`, run through `tsx`:
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
npm test
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
No test framework is installed, because none is needed. Node ships the
|
|
38
|
+
runner, `tsx` is already here, and jsdom is already here for expansion.
|
|
39
|
+
|
|
40
|
+
Tests live in `tests/`, one file per source file, named for it:
|
|
41
|
+
`tests/expand.test.ts` covers `build/expand.ts`.
|
|
42
|
+
|
|
43
|
+
Write a definition inline, beside the assertion that reads it — a two-line
|
|
44
|
+
definition is shorter than the reference that would point at it.
|
|
45
|
+
`tests/fixtures/` holds only what has to be a file: the checks that run while
|
|
46
|
+
definitions are being loaded, and the directory ordering that lets an
|
|
47
|
+
application override a built-in.
|
|
48
|
+
|
|
49
|
+
## The console is an interface
|
|
50
|
+
|
|
51
|
+
The browser half of Loadbare App reports every failure it survives through
|
|
52
|
+
`console.error` and `console.warn` — a query with no scope, a list widget
|
|
53
|
+
with no template, a change event with no coordinates. These are not
|
|
54
|
+
diagnostics. They are the framework's entire error channel on the client, and
|
|
55
|
+
the behavior under test is frequently *that Loadbare reported and carried on*
|
|
56
|
+
rather than that it produced a value.
|
|
57
|
+
|
|
58
|
+
So tests assert on that channel. `tests/helpers/console.ts` is the seam that
|
|
59
|
+
makes it pleasant rather than fiddly.
|
|
60
|
+
|
|
61
|
+
## Tier 1 — Expansion and the build
|
|
62
|
+
|
|
63
|
+
Expansion is string in, string out, with no data, no browser, and no clock.
|
|
64
|
+
It is also the layer whose failure mode is settled: it throws rather than
|
|
65
|
+
ships. That combination makes it both the easiest tier and the one carrying
|
|
66
|
+
the most of what Loadbare promises, so it is first.
|
|
67
|
+
|
|
68
|
+
**Rejections.** Every one of these is a build that must not produce output:
|
|
69
|
+
|
|
70
|
+
- an `lb-*` tag with no definition
|
|
71
|
+
- `exp-foo` supplied to a definition with no `{{foo}}`
|
|
72
|
+
- `{{camelCase}}` in a definition, which no tag could ever supply
|
|
73
|
+
- a cycle in the definition graph
|
|
74
|
+
- two `lb-template` destinations with one name
|
|
75
|
+
- two authored templates for one destination
|
|
76
|
+
- a template naming a destination the definition does not have
|
|
77
|
+
- content written inside a definition with no `lb-slot`
|
|
78
|
+
- more than one `lb-slot`
|
|
79
|
+
|
|
80
|
+
**Substitution.** An unset placeholder drops the attribute rather than
|
|
81
|
+
emitting it empty, which is what HTML's boolean attributes require and what
|
|
82
|
+
`readonly="{{readonly}}"` in `lb-input` depends on. Whitespace around a text
|
|
83
|
+
placeholder is preserved, because `{{label}} <input>` needs that space.
|
|
84
|
+
Placeholders inside `<template>` content are expanded, which is why
|
|
85
|
+
`lb-picker` can ship a row template of its own. `exp-` attributes survive on
|
|
86
|
+
the expanded tag, so what ships shows what was asked for beside what it
|
|
87
|
+
produced.
|
|
88
|
+
|
|
89
|
+
**Injection is impossible by construction**, because substitution goes
|
|
90
|
+
through `setAttribute` and node values rather than through a string. That
|
|
91
|
+
claim gets a test: a parameter whose value is markup ships as text. It exists
|
|
92
|
+
to fail if anyone ever reaches for string concatenation here.
|
|
93
|
+
|
|
94
|
+
**Golden files.** These live in `@loadbare/demo`, not here. Each demo
|
|
95
|
+
application is built by the installed `loadbare-app-build` and its assembled
|
|
96
|
+
`app.html` compared to a stored copy — the whole chain at once, including the
|
|
97
|
+
origin ordering that lets an application override a widget from a package,
|
|
98
|
+
exercised the way a consumer exercises it rather than by wiring this
|
|
99
|
+
package's own modules together. See
|
|
100
|
+
[`packages/demo/AGENTS.md`](../../demo/AGENTS.md).
|
|
101
|
+
|
|
102
|
+
Review the golden files rather than blessing them. `npm run test:golden -w
|
|
103
|
+
@loadbare/demo` rewrites them; the diff is the point, and a diff nobody read
|
|
104
|
+
is a test nobody ran.
|
|
105
|
+
|
|
106
|
+
The rest of the build — `assemble`, `elements`, `pages`, `styles`,
|
|
107
|
+
`package-css` — is tested the same way and in the same tier, since none of it
|
|
108
|
+
needs a browser either.
|
|
109
|
+
|
|
110
|
+
## Tier 2 — The engine
|
|
111
|
+
|
|
112
|
+
`createHub` takes a plain object and returns an object. Nothing in
|
|
113
|
+
`server/lb-server.ts` opens a socket, so the fixtures are counting stubs.
|
|
114
|
+
|
|
115
|
+
- `beforeGet` runs before any query, and the whole query set runs after it
|
|
116
|
+
- an unknown page answers `{}` and says so
|
|
117
|
+
- an unknown query name in a refresh set is skipped, and its siblings run
|
|
118
|
+
- an action the page did not declare is refused — the rule that keeps the
|
|
119
|
+
wire from reaching anything the page has not published
|
|
120
|
+
- the refresh set runs after the action, against the same context
|
|
121
|
+
- **what the action stated wins over what the refresh produced**, because
|
|
122
|
+
that is how a `patch` reaches the browser at all, and it is one spread
|
|
123
|
+
operator away from silently reversing
|
|
124
|
+
|
|
125
|
+
`tests/lb-express.test.ts` runs against a listening app: the page comes from
|
|
126
|
+
the query string, an undeclared operation is refused with a 400, and a
|
|
127
|
+
malformed body does not throw.
|
|
128
|
+
|
|
129
|
+
## Tier 3 — Landing
|
|
130
|
+
|
|
131
|
+
`applyData`, `applyTuple` and `applyRows` are the most intricate code in the
|
|
132
|
+
framework and the most likely to break in ways nobody notices. They are also
|
|
133
|
+
pure DOM: no fetch, no widget upgrade, no history. jsdom is real evidence
|
|
134
|
+
here.
|
|
135
|
+
|
|
136
|
+
- a native element receives its value as text, a hyphenated one as `lb-value`
|
|
137
|
+
- the root of a scope counts as a cell if it carries one, which is what makes
|
|
138
|
+
an `<option>` row possible
|
|
139
|
+
- a query with no scope is reported and skipped; several scopes for one query
|
|
140
|
+
are all filled; a projection landing on something that is not a list widget
|
|
141
|
+
is reported rather than thrown
|
|
142
|
+
- `rows` decides membership and order, so a key that did not arrive is gone
|
|
143
|
+
- `patch` disturbs only what it names, in contents and in position
|
|
144
|
+
- `data-rows` is counted from the DOM after reconciliation, so a set and a
|
|
145
|
+
patch ending in the same state report the same number
|
|
146
|
+
- the `place` callback is called for a fresh row always, and for an existing
|
|
147
|
+
row only under `rows`. That is today's behavior, not a decision — a patch
|
|
148
|
+
therefore never re-places a row whose sort key changed. The test states what
|
|
149
|
+
is true now, and is the one that flips if that changes.
|
|
150
|
+
|
|
151
|
+
**Two properties**, written as loops rather than with a library. Applying the
|
|
152
|
+
same `rows` twice is applying it once. And `rows(S)` reached through any
|
|
153
|
+
sequence of patches is `rows(S)` reached from empty. Convergence is the
|
|
154
|
+
actual contract of a reconciler, and those two say it better than twenty
|
|
155
|
+
examples.
|
|
156
|
+
|
|
157
|
+
## Tier 4 — The hub
|
|
158
|
+
|
|
159
|
+
The widget half of this tier lives in `@loadbare/widgets` and is tested
|
|
160
|
+
there, against the same jsdom harness described below — see
|
|
161
|
+
[`packages/widgets/AGENTS.md`](../../widgets/AGENTS.md). What follows applies
|
|
162
|
+
to both, and the hub is what remains here.
|
|
163
|
+
|
|
164
|
+
Widgets are small and their logic is local, so jsdom carries them: a value
|
|
165
|
+
reaches the control the widget owns, a change dispatches the declared action,
|
|
166
|
+
an absent control or absent action is reported rather than thrown,
|
|
167
|
+
`lb-options` turns a key into a value and removes an emptied `<optgroup>`,
|
|
168
|
+
`lb-table` groups and sorts, removes an emptied section heading, and takes
|
|
169
|
+
its `colSpan` from the row template the page wrote.
|
|
170
|
+
|
|
171
|
+
A widget extends `HTMLElement` and registers itself as its module loads, so
|
|
172
|
+
unlike tier 3 it needs a window before the module exists. `installWindow()`
|
|
173
|
+
puts one in place and the widget module is imported after it, dynamically.
|
|
174
|
+
One window per file, which is what running each file in its own process gives
|
|
175
|
+
for free — registration is global and permanent, so sharing a process across
|
|
176
|
+
widget files would mean sharing a registry.
|
|
177
|
+
|
|
178
|
+
What that buys is real upgrades: the constructor, `connectedCallback`, and
|
|
179
|
+
`attributeChangedCallback` for attributes already present. That last one is
|
|
180
|
+
the mechanism by which hydration and refresh are one operation, and it
|
|
181
|
+
behaves the same in jsdom as in a browser. What it does not buy is layout,
|
|
182
|
+
painting, or navigation.
|
|
183
|
+
|
|
184
|
+
## Not built: the browser tier
|
|
185
|
+
|
|
186
|
+
The hub is where jsdom stops being evidence. Fetch, `history.pushState`,
|
|
187
|
+
`popstate`, and the claim that insertion and hydration in one synchronous
|
|
188
|
+
block never paint an empty frame are all statements about a browser.
|
|
189
|
+
|
|
190
|
+
No browser test runner is installed and none of the following exists. They
|
|
191
|
+
are recorded here as the shape of the work, not as coverage:
|
|
192
|
+
|
|
193
|
+
1. a cold load fills `<main>` with no empty flash
|
|
194
|
+
2. `lb-nav-link` pushes state, swaps the host and lands data; `popstate`
|
|
195
|
+
reverses it; an ordinary anchor is not hijacked
|
|
196
|
+
3. a native action button produces the same event a widget produces
|
|
197
|
+
4. the wizard never displays a step the server has not confirmed
|
|
198
|
+
5. the view-source invariant
|
|
199
|
+
|
|
200
|
+
The last is the one worth a browser. Loadbare's central claim is that no
|
|
201
|
+
markup exists in the DOM that is not in view-source, and that the difference
|
|
202
|
+
between the two is exactly the dynamic half. That is a property, it is stated
|
|
203
|
+
precisely in [theory.md](./theory.md), and it should be enforced mechanically
|
|
204
|
+
rather than believed: fetch the shell as text, compare the element set
|
|
205
|
+
against the live document after interaction, and assert that the only
|
|
206
|
+
additions are clones of row templates.
|
|
207
|
+
|
|
208
|
+
See [TODO.md](../TODO.md) for this and the rest of the open work.
|
|
209
|
+
|
|
210
|
+
## Continuous integration
|
|
211
|
+
|
|
212
|
+
```
|
|
213
|
+
npm run typecheck && npm run format:check && npm test
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Add the browser specs as a separate job if they are ever written, so the four
|
|
217
|
+
tiers here stay fast enough to run on every save.
|
|
218
|
+
|
|
219
|
+
## One change the tests want
|
|
220
|
+
|
|
221
|
+
The client reports through `console.error` and `console.warn` directly, from
|
|
222
|
+
roughly twenty places across `hub/` and `@loadbare/widgets`. Routing those through a
|
|
223
|
+
single `report()` in `core/` would give the tests one seam to observe instead
|
|
224
|
+
of a global to mock per file, and would put the framework's error channel in
|
|
225
|
+
the same file as the rest of its vocabulary. It is a small change and it is
|
|
226
|
+
not urgent, but it is the only place where testing asks anything of the
|
|
227
|
+
design.
|
|
228
|
+
</content>
|