tutuca 0.9.70 → 0.9.72
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/dist/chai.js +3389 -0
- package/dist/immutable.js +4333 -0
- package/dist/tutuca-cli.js +141 -2
- package/dist/tutuca-dev.ext.js +113 -3392
- package/dist/tutuca-dev.js +25 -0
- package/dist/tutuca-dev.min.js +2 -2
- package/package.json +9 -1
- package/skill/tutuca/cli.md +11 -9
- package/skill/tutuca/core.md +32 -3
- package/skill/tutuca/testing.md +16 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tutuca",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.72",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Zero-dependency SPA framework with immutable state and virtual DOM",
|
|
6
6
|
"main": "./dist/tutuca.js",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"./ext": "./dist/tutuca.ext.js",
|
|
14
14
|
"./extra-ext": "./dist/tutuca-extra.ext.js",
|
|
15
15
|
"./dev-ext": "./dist/tutuca-dev.ext.js",
|
|
16
|
+
"./immutable": "./dist/immutable.js",
|
|
17
|
+
"./chai": "./dist/chai.js",
|
|
16
18
|
"./package.json": "./package.json"
|
|
17
19
|
},
|
|
18
20
|
"bin": {
|
|
@@ -49,6 +51,8 @@
|
|
|
49
51
|
"dist/tutuca.ext.js",
|
|
50
52
|
"dist/tutuca-extra.ext.js",
|
|
51
53
|
"dist/tutuca-dev.ext.js",
|
|
54
|
+
"dist/immutable.js",
|
|
55
|
+
"dist/chai.js",
|
|
52
56
|
"skill"
|
|
53
57
|
],
|
|
54
58
|
"dependencies": {
|
|
@@ -56,9 +60,13 @@
|
|
|
56
60
|
"prettier": "^3.0.0"
|
|
57
61
|
},
|
|
58
62
|
"peerDependencies": {
|
|
63
|
+
"chai": "^6.2.2",
|
|
59
64
|
"immutable": "*"
|
|
60
65
|
},
|
|
61
66
|
"peerDependenciesMeta": {
|
|
67
|
+
"chai": {
|
|
68
|
+
"optional": true
|
|
69
|
+
},
|
|
62
70
|
"immutable": {
|
|
63
71
|
"optional": true
|
|
64
72
|
}
|
package/skill/tutuca/cli.md
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
# Tutuca CLI Reference
|
|
2
2
|
|
|
3
3
|
The `tutuca` CLI inspects, documents, lints, tests, and renders any
|
|
4
|
-
module that follows the
|
|
5
|
-
|
|
4
|
+
module that follows the
|
|
5
|
+
[Conventional Module Exports](./core.md#conventional-module-exports)
|
|
6
|
+
shape. Reach this file when you need command/flag/exit-code
|
|
6
7
|
details, or when reading a lint code out of `lint` output. Otherwise
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
[Verifying changes](./core.md#verifying-changes) in `core.md` (run
|
|
9
|
+
`lint`, then `test` for behavior changes, then
|
|
10
|
+
`render --title "<your example>"`) is enough.
|
|
9
11
|
|
|
10
12
|
## Install / invoke
|
|
11
13
|
|
|
@@ -156,25 +158,25 @@ export function getTests({ describe, test, expect }) {
|
|
|
156
158
|
describe("inc()", () => { // method
|
|
157
159
|
test("returns a Counter with count + 1", () => {
|
|
158
160
|
const next = Counter.make().inc();
|
|
159
|
-
expect(next).
|
|
160
|
-
expect(next.count).
|
|
161
|
+
expect(next).toBeInstanceOf(Counter.Class);
|
|
162
|
+
expect(next.count).toBe(1);
|
|
161
163
|
});
|
|
162
164
|
test("does not mutate the original instance", () => {
|
|
163
165
|
const c = Counter.make({ count: 7 });
|
|
164
166
|
c.inc();
|
|
165
|
-
expect(c.count).
|
|
167
|
+
expect(c.count).toBe(7); // immutability
|
|
166
168
|
});
|
|
167
169
|
});
|
|
168
170
|
|
|
169
171
|
describe("dec()", () => { // input handler
|
|
170
172
|
test("returns a Counter with count - 1", () => {
|
|
171
173
|
const next = Counter.input.dec.call(Counter.make());
|
|
172
|
-
expect(next.count).
|
|
174
|
+
expect(next.count).toBe(-1);
|
|
173
175
|
});
|
|
174
176
|
});
|
|
175
177
|
|
|
176
178
|
test("inc and dec round-trip", () => { // untagged path
|
|
177
|
-
expect(Counter.input.dec.call(Counter.make().inc()).count).
|
|
179
|
+
expect(Counter.input.dec.call(Counter.make().inc()).count).toBe(0);
|
|
178
180
|
});
|
|
179
181
|
});
|
|
180
182
|
}
|
package/skill/tutuca/core.md
CHANGED
|
@@ -189,8 +189,8 @@ separate.
|
|
|
189
189
|
|
|
190
190
|
Views are name-based: there is no arithmetic expression syntax in
|
|
191
191
|
values, and no Vue- or Mustache-style `{{ … }}` placeholders. Every
|
|
192
|
-
value slot — conditions (`@show`, `@if
|
|
193
|
-
`render-each`), enrichment (`@enrich-with`, `@loop-with`), template
|
|
192
|
+
value slot — conditions (`@show`, `@if`), iteration (`@each`,
|
|
193
|
+
`render-each`, `@when`), enrichment (`@enrich-with`, `@loop-with`), template
|
|
194
194
|
expansion (`{…}`, `:attr`, `@text`) — names a field, method, macro, or
|
|
195
195
|
handler defined on the component (or registered with the app). Logic
|
|
196
196
|
lives in `methods` / `alter` / `input` / `bubble` / `receive` /
|
|
@@ -217,7 +217,7 @@ literal with spaces (escape an interior quote as `\'`).
|
|
|
217
217
|
| `@x` | local binding (loop / scope) | `@key`, `@value` |
|
|
218
218
|
| `^x` | macro parameter | `^label` |
|
|
219
219
|
| `!x` | request handler | `!loadData` |
|
|
220
|
-
| `*x` | dynamic binding — see
|
|
220
|
+
| `*x` | dynamic binding — see [advanced.md](./advanced.md) | `*theme` |
|
|
221
221
|
| `Name` | component type (PascalCase) | `Item`, `JsonNull` |
|
|
222
222
|
| `name` | bare identifier — meaning depends on slot | `dec`, `value` |
|
|
223
223
|
| `'str'` | string literal | `'btn btn-success'` |
|
|
@@ -419,6 +419,11 @@ statics: {
|
|
|
419
419
|
<x text="@value"></x> <!-- loop binding -->
|
|
420
420
|
```
|
|
421
421
|
|
|
422
|
+
Use `@text` when you already have a host element to put the text in; use
|
|
423
|
+
`<x text=…>` for bare text with no wrapping element (e.g. text interleaved with
|
|
424
|
+
other inline content, or a loop binding). Both take the same value forms
|
|
425
|
+
(`.field`, `$method`, `@binding`).
|
|
426
|
+
|
|
422
427
|
## Attribute Binding
|
|
423
428
|
|
|
424
429
|
```html
|
|
@@ -509,6 +514,17 @@ input: { onPick(detail) { return this.setCurrent(detail.unicode); } }
|
|
|
509
514
|
view: html`<emoji-picker @on.emoji-click="onPick value"></emoji-picker>`,
|
|
510
515
|
```
|
|
511
516
|
|
|
517
|
+
Handle these events declaratively with `@on.<event-name>` in the view —
|
|
518
|
+
don't grab the node from host/glue code and `addEventListener` on it. A
|
|
519
|
+
listener attached from outside the component runs outside the handler
|
|
520
|
+
model: no `return this.set…()`, no transactor batching, and the mutation
|
|
521
|
+
is invisible to the component that owns the state (the same hazard as
|
|
522
|
+
reaching into `app.state` directly). For any event with a real element in
|
|
523
|
+
the tree, `@on.` is the only entry point you need. Genuinely external
|
|
524
|
+
inbound sources (WebSocket, `postMessage`, timers) have no element to bind
|
|
525
|
+
— route those through `app.sendAtRoot` instead (see
|
|
526
|
+
[request-response.md](./request-response.md)).
|
|
527
|
+
|
|
512
528
|
Pitfall: binding camelCase JS properties on a custom element silently
|
|
513
529
|
fails. `:mapId=".id"` does *not* invoke a `set mapId` setter
|
|
514
530
|
— the HTML parser lowercased the attribute name, so the framework assigns
|
|
@@ -899,3 +915,16 @@ export function getExamples() {
|
|
|
899
915
|
}
|
|
900
916
|
export function getTests({ describe, test, expect }) { /*...*/ } // optional — see cli.md
|
|
901
917
|
```
|
|
918
|
+
|
|
919
|
+
## See also
|
|
920
|
+
|
|
921
|
+
- [request-response.md](./request-response.md) — `bubble` / `send`-`receive` /
|
|
922
|
+
`request`-`response` channels, the `ctx.at` `PathBuilder`, `$unknown`, and
|
|
923
|
+
request-handler registration.
|
|
924
|
+
- [advanced.md](./advanced.md) — dynamic bindings (`*x`), pseudo-`@x` for
|
|
925
|
+
`<select>` / `<table>` / `<tr>`, drag & drop, custom seq types, Tailwind /
|
|
926
|
+
MargaUI compilation.
|
|
927
|
+
- [testing.md](./testing.md) — `getTests` shape and the handler calling
|
|
928
|
+
convention for tests.
|
|
929
|
+
- [cli.md](./cli.md) — commands, flags, exit codes, and the full linter rule
|
|
930
|
+
list.
|
package/skill/tutuca/testing.md
CHANGED
|
@@ -16,13 +16,19 @@ A module opts into `tutuca test` by exporting `getTests`:
|
|
|
16
16
|
export function getTests({ describe, test, expect }) {
|
|
17
17
|
describe(MyComp, () => {
|
|
18
18
|
test("does the thing", () => {
|
|
19
|
-
expect(MyComp.make().doTheThing().count).
|
|
19
|
+
expect(MyComp.make().doTheThing().count).toBe(1);
|
|
20
20
|
});
|
|
21
21
|
});
|
|
22
22
|
}
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
- `expect` is chai
|
|
25
|
+
- `expect` is chai, extended with **jest-style matchers** (`toBe`,
|
|
26
|
+
`toEqual`, `toContain`, `toThrow`, `.not.toBe`, …) — the recommended
|
|
27
|
+
style. Chai's BDD chain (`expect(x).to.equal(1)`) still works for those
|
|
28
|
+
who prefer it. Run `tutuca help` for the full matcher list (it's
|
|
29
|
+
surfaced from code). Asymmetric/mock matchers
|
|
30
|
+
(`expect.objectContaining`, `toHaveBeenCalled…`, `toMatchSnapshot`) are
|
|
31
|
+
**not** available — tutuca has no mocking layer.
|
|
26
32
|
- `test` and `describe` are **Tutuca's own** subset of the common
|
|
27
33
|
Mocha/Bun-style API, injected by `tutuca test` — not Bun's built-ins.
|
|
28
34
|
Available calls: `describe(title, fn)`, `describe(Component, fn)`,
|
|
@@ -147,7 +153,7 @@ test("filters and enriches", () => {
|
|
|
147
153
|
when: "keepEven",
|
|
148
154
|
enrichWith: "addLabel",
|
|
149
155
|
});
|
|
150
|
-
expect(r).
|
|
156
|
+
expect(r).toEqual([
|
|
151
157
|
{ key: 0, value: 10, label: "0/4: 10" },
|
|
152
158
|
{ key: 2, value: 30, label: "2/4: 30" },
|
|
153
159
|
]);
|
|
@@ -211,8 +217,8 @@ input: { setCount(n) { return this.setCount(n); } }
|
|
|
211
217
|
At test time, the "good" forms become trivial:
|
|
212
218
|
|
|
213
219
|
```js
|
|
214
|
-
expect(MyComp.make().setName("Ada").name).
|
|
215
|
-
expect(MyComp.input.setCount.call(MyComp.make(), 42).count).
|
|
220
|
+
expect(MyComp.make().setName("Ada").name).toBe("Ada");
|
|
221
|
+
expect(MyComp.input.setCount.call(MyComp.make(), 42).count).toBe(42);
|
|
216
222
|
```
|
|
217
223
|
|
|
218
224
|
The "bad" forms force every test to construct
|
|
@@ -237,31 +243,31 @@ export function getTests({ describe, test, expect }) {
|
|
|
237
243
|
describe(Counter, () => {
|
|
238
244
|
describe("inc()", () => { // method
|
|
239
245
|
test("returns a Counter with count + 1", () => {
|
|
240
|
-
expect(Counter.make().inc().count).
|
|
246
|
+
expect(Counter.make().inc().count).toBe(1);
|
|
241
247
|
});
|
|
242
248
|
test("does not mutate the original instance", () => {
|
|
243
249
|
const c = Counter.make({ count: 7 });
|
|
244
250
|
c.inc();
|
|
245
|
-
expect(c.count).
|
|
251
|
+
expect(c.count).toBe(7);
|
|
246
252
|
});
|
|
247
253
|
});
|
|
248
254
|
|
|
249
255
|
describe("dec()", () => { // input handler, no args
|
|
250
256
|
test("returns a Counter with count - 1", () => {
|
|
251
257
|
const next = Counter.input.dec.call(Counter.make());
|
|
252
|
-
expect(next.count).
|
|
258
|
+
expect(next.count).toBe(-1);
|
|
253
259
|
});
|
|
254
260
|
});
|
|
255
261
|
|
|
256
262
|
describe("setCount()", () => { // input handler, valueAsInt
|
|
257
263
|
test("sets the count from a parsed int", () => {
|
|
258
264
|
const next = Counter.input.setCount.call(Counter.make(), 42);
|
|
259
|
-
expect(next.count).
|
|
265
|
+
expect(next.count).toBe(42);
|
|
260
266
|
});
|
|
261
267
|
});
|
|
262
268
|
|
|
263
269
|
test("inc and dec round-trip", () => { // untagged, inherits Counter
|
|
264
|
-
expect(Counter.input.dec.call(Counter.make().inc()).count).
|
|
270
|
+
expect(Counter.input.dec.call(Counter.make().inc()).count).toBe(0);
|
|
265
271
|
});
|
|
266
272
|
});
|
|
267
273
|
}
|