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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tutuca",
3
- "version": "0.9.70",
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
  }
@@ -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 *Conventional Module Exports* shape (see
5
- `core.md`). Reach this file when you need command/flag/exit-code
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
- *Verifying changes* in `core.md` (run `lint`, then `test` for
8
- behavior changes, then `render --title "<your example>"`) is enough.
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).to.be.instanceOf(Counter.Class);
160
- expect(next.count).to.equal(1);
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).to.equal(7); // immutability
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).to.equal(-1);
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).to.equal(0);
179
+ expect(Counter.input.dec.call(Counter.make().inc()).count).toBe(0);
178
180
  });
179
181
  });
180
182
  }
@@ -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`, `@when`), iteration (`@each`,
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 `advanced.md` | `*theme` |
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.
@@ -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).to.equal(1);
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).to.deep.equal([
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).to.equal("Ada");
215
- expect(MyComp.input.setCount.call(MyComp.make(), 42).count).to.equal(42);
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).to.equal(1);
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).to.equal(7);
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).to.equal(-1);
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).to.equal(42);
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).to.equal(0);
270
+ expect(Counter.input.dec.call(Counter.make().inc()).count).toBe(0);
265
271
  });
266
272
  });
267
273
  }