@lankajs/solid 0.1.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/LICENSE +21 -0
- package/README.md +43 -0
- package/dist/chunk-5WRI5ZAA.js +31 -0
- package/dist/chunk-5WRI5ZAA.js.map +1 -0
- package/dist/index.d.ts +109 -0
- package/dist/index.js +63 -0
- package/dist/index.js.map +1 -0
- package/dist/testing.d.ts +46 -0
- package/dist/testing.js +12638 -0
- package/dist/testing.js.map +1 -0
- package/package.json +56 -0
- package/skills/lanka-solid/SKILL.md +157 -0
- package/skills/lanka-solid/reference.md +222 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 lankajs contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @lankajs/solid
|
|
2
|
+
|
|
3
|
+
**▸ module** · Solid binding
|
|
4
|
+
|
|
5
|
+
> One function — `useLankaVM` — over a signal, and the access tracking core already does.
|
|
6
|
+
|
|
7
|
+
A library in the same box. The app imports and calls it; core does not know it exists.
|
|
8
|
+
|
|
9
|
+
**Runs in:** the browser.
|
|
10
|
+
|
|
11
|
+
**Requires:** Solid. Enforced by `check-runtime.mjs`, which refuses an import of any other.
|
|
12
|
+
|
|
13
|
+
**How to use it:** [GUIDE.md](./GUIDE.md) — the user guide, with examples. **How to change it:** [SKILL.md](./SKILL.md).
|
|
14
|
+
|
|
15
|
+
## Contents
|
|
16
|
+
|
|
17
|
+
- `useLankaVM` — the one name, and the same one every member of this shelf publishes
|
|
18
|
+
- `renderWithLanka` (from `@lankajs/solid/testing`) — a render with a bootstrapped framework
|
|
19
|
+
|
|
20
|
+
## What a Solid call answers
|
|
21
|
+
|
|
22
|
+
An `Accessor`: `state().todos`. Solid has no re-render — a component function runs
|
|
23
|
+
ONCE and what updates is the DOM node that read the signal — so `renders()` in the
|
|
24
|
+
conformance suite counts what the reading effect ran, which is the closest thing
|
|
25
|
+
this framework has to the question every other binding answers directly.
|
|
26
|
+
|
|
27
|
+
## Access tracking still earns its place, for a different reason
|
|
28
|
+
|
|
29
|
+
Solid already skips work a signal did not feed, so a coarse binding would be less
|
|
30
|
+
wrong here than elsewhere. It would still be wrong: without tracking, every change
|
|
31
|
+
writes a new object into the signal and every effect reading ANY part of it re-runs.
|
|
32
|
+
The tracker is what keeps the signal unchanged when nothing a reader looked at moved.
|
|
33
|
+
|
|
34
|
+
## `onCleanup`, and the one case it is not there
|
|
35
|
+
|
|
36
|
+
Inside a component or a root, Solid releases the subscription with the owner. Called
|
|
37
|
+
outside one there is no owner, so the accessor carries `stop()` and the caller owns
|
|
38
|
+
it — the same seam `@lankajs/vue` has for the same reason, and neither framework
|
|
39
|
+
warns loudly enough for a note to be sufficient.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
Repository map: [../../../README.md](../../../README.md)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
19
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
20
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
21
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
22
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
23
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
24
|
+
mod
|
|
25
|
+
));
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
__commonJS,
|
|
29
|
+
__toESM
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=chunk-5WRI5ZAA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { ILankaReadableVM } from 'lanka/viewmodel';
|
|
2
|
+
import { Accessor } from 'solid-js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A ViewModel read the way Solid reads shared state.
|
|
6
|
+
*
|
|
7
|
+
* The state's own members, directly, plus the one meta member a caller needs.
|
|
8
|
+
* `$`-prefixed so it cannot collide with a state key, which is the same reason
|
|
9
|
+
* Solid's own stores keep their helpers off the object.
|
|
10
|
+
*
|
|
11
|
+
* Called a ViewModel and not a store, deliberately: `createStore` is Solid's
|
|
12
|
+
* noun for the shape, and what holds the state, the actions and the scenario
|
|
13
|
+
* bindings is the ViewModel.
|
|
14
|
+
*/
|
|
15
|
+
type TLankaSolidVM<TState extends object> = TState & {
|
|
16
|
+
/** Releases the subscription. Rarely needed: an owner does it. */
|
|
17
|
+
$stop: () => void;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Reads a ViewModel the way Solid reads an object, with no call on the outside.
|
|
21
|
+
*
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const todos = toLankaSolidVM(todosVM);
|
|
24
|
+
*
|
|
25
|
+
* <For each={todos.rows}>{(row) => <li>{row}</li>}</For>
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* ## Why this exists beside `useLankaVM`
|
|
29
|
+
*
|
|
30
|
+
* `useLankaVM` answers an `Accessor`, which is Solid's own shape for a value and
|
|
31
|
+
* the one every other binding on the shelf parallels: `state().rows`. It is also
|
|
32
|
+
* not how Solid holds an OBJECT. `createStore` gives a proxy read as `state.rows`
|
|
33
|
+
* — no call, and the read itself is the subscription — and that is what a Solid
|
|
34
|
+
* codebase has in it. A consumer with that habit writes `todos.rows`, gets
|
|
35
|
+
* `undefined`, and learns that lanka is a foreign object.
|
|
36
|
+
*
|
|
37
|
+
* ## The read is the subscription, in both graphs at once
|
|
38
|
+
*
|
|
39
|
+
* A read goes through the access tracker, which records the key, AND through the
|
|
40
|
+
* signal, which registers the surrounding computation with Solid. One access,
|
|
41
|
+
* two graphs — the same alignment the Svelte binding gets from getters, and the
|
|
42
|
+
* reason neither of them needs a diff.
|
|
43
|
+
*
|
|
44
|
+
* The signal holds a VERSION rather than the state. A tracked read hands back the
|
|
45
|
+
* same proxy while the state object is unchanged, and Solid compares by identity,
|
|
46
|
+
* so a signal holding the state would be a no-op exactly when the tracker did its
|
|
47
|
+
* job — and a signal holding the state is also a SNAPSHOT, which is wrong for
|
|
48
|
+
* something read from ordinary code at arbitrary moments. `store.load()` followed
|
|
49
|
+
* by `store.rows` is the shape that settles it.
|
|
50
|
+
*
|
|
51
|
+
* ## What it is not
|
|
52
|
+
*
|
|
53
|
+
* Not `createStore`. Solid's store is a write path as well as a read one, and a
|
|
54
|
+
* ViewModel's writes belong to its actions — `setStore` beside them would be a
|
|
55
|
+
* second place state changes. This is the read half, which is the half a screen
|
|
56
|
+
* has.
|
|
57
|
+
*/
|
|
58
|
+
declare const toLankaSolidVM: <TState extends object>(viewModel: ILankaReadableVM<TState>) => TLankaSolidVM<TState>;
|
|
59
|
+
|
|
60
|
+
/** A ViewModel read from Solid: an accessor, and a way to stop reading it. */
|
|
61
|
+
type TLankaVMAccessor<TValue> = Accessor<TValue> & {
|
|
62
|
+
/**
|
|
63
|
+
* Releases the subscription.
|
|
64
|
+
*
|
|
65
|
+
* Called for you by `onCleanup` inside a component or a root. It is published
|
|
66
|
+
* because a read made where there is no owner — module level, a test — has
|
|
67
|
+
* nobody to call it, and Solid warns about that case rather than handling it.
|
|
68
|
+
*/
|
|
69
|
+
stop: () => void;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Reads a ViewModel from Solid.
|
|
73
|
+
*
|
|
74
|
+
* ```tsx
|
|
75
|
+
* export const TodoScreen = () => {
|
|
76
|
+
* const state = useLankaVM(todoVM);
|
|
77
|
+
*
|
|
78
|
+
* return <For each={state().todos}>{(todo) => <li>{todo.title}</li>}</For>;
|
|
79
|
+
* };
|
|
80
|
+
* ```
|
|
81
|
+
*
|
|
82
|
+
* Without a selector the accessor answers a Proxy that records which keys were
|
|
83
|
+
* read, and the signal changes only when one of THOSE moves. With a selector the
|
|
84
|
+
* selector decides and tracking is bypassed.
|
|
85
|
+
*
|
|
86
|
+
* ## Why tracking still earns its place here
|
|
87
|
+
*
|
|
88
|
+
* Solid already skips work a signal did not feed, so a coarse binding would be
|
|
89
|
+
* less wrong here than elsewhere. It would still be wrong: without tracking every
|
|
90
|
+
* change writes a new object into the signal, and every effect reading ANY part
|
|
91
|
+
* of it re-runs. The tracker is what keeps the signal UNCHANGED when nothing a
|
|
92
|
+
* reader looked at moved — and an unchanged signal is work Solid never starts.
|
|
93
|
+
*
|
|
94
|
+
* ## What "a render" means in a framework that has none
|
|
95
|
+
*
|
|
96
|
+
* A Solid component runs once; what updates is the DOM node that read the signal.
|
|
97
|
+
* So there is nothing here that corresponds to a re-render, and the conformance
|
|
98
|
+
* suite's `renders()` counts the reading effect's runs instead — which is the
|
|
99
|
+
* closest question this framework can be asked.
|
|
100
|
+
*
|
|
101
|
+
* ## What this function does NOT contain
|
|
102
|
+
*
|
|
103
|
+
* The recording, the comparison and the blind-spot warning are in core. If this
|
|
104
|
+
* file ever needs more than the port gives it, the port has the defect.
|
|
105
|
+
*/
|
|
106
|
+
declare function useLankaVM<TState extends object>(viewModel: ILankaReadableVM<TState>): TLankaVMAccessor<TState>;
|
|
107
|
+
declare function useLankaVM<TState extends object, TSelected>(viewModel: ILankaReadableVM<TState>, selector: (state: TState) => TSelected): TLankaVMAccessor<TSelected>;
|
|
108
|
+
|
|
109
|
+
export { type TLankaSolidVM, type TLankaVMAccessor, toLankaSolidVM, useLankaVM };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import "./chunk-5WRI5ZAA.js";
|
|
2
|
+
|
|
3
|
+
// src/to-lanka-solid-vm/toLankaSolidVM.ts
|
|
4
|
+
import { createSignal, getOwner, onCleanup } from "solid-js";
|
|
5
|
+
import { createLankaViewSubscription } from "lanka/extend";
|
|
6
|
+
var readsTheViewModel = (current, stop) => ({
|
|
7
|
+
get: (_target, key) => key === "$stop" ? stop : Reflect.get(current(), key),
|
|
8
|
+
has: (_target, key) => key === "$stop" || key in current(),
|
|
9
|
+
ownKeys: () => Reflect.ownKeys(current()),
|
|
10
|
+
/*
|
|
11
|
+
* Reported as configurable, always.
|
|
12
|
+
*
|
|
13
|
+
* A Proxy must not claim a non-configurable descriptor its target lacks — the
|
|
14
|
+
* runtime throws. The target here is a bare object while the keys live on the
|
|
15
|
+
* state, so every descriptor this hands back is invented and must say it can be
|
|
16
|
+
* redefined. Without it `{ ...store }` and `Object.keys(store)` throw rather
|
|
17
|
+
* than read, and a devtool does one of them on sight.
|
|
18
|
+
*/
|
|
19
|
+
getOwnPropertyDescriptor: (_target, key) => key === "$stop" ? { value: stop, configurable: true, enumerable: false, writable: false } : { ...Reflect.getOwnPropertyDescriptor(current(), key), configurable: true }
|
|
20
|
+
});
|
|
21
|
+
var toLankaSolidVM = (viewModel) => {
|
|
22
|
+
const [version, setVersion] = createSignal(0);
|
|
23
|
+
const view = createLankaViewSubscription(viewModel, () => {
|
|
24
|
+
setVersion((seen) => seen + 1);
|
|
25
|
+
});
|
|
26
|
+
if (getOwner()) onCleanup(view.stop);
|
|
27
|
+
const current = () => {
|
|
28
|
+
void version();
|
|
29
|
+
return view.read();
|
|
30
|
+
};
|
|
31
|
+
return new Proxy({}, readsTheViewModel(current, view.stop));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// src/use-lanka-vm/useLankaVM.ts
|
|
35
|
+
import { createSignal as createSignal2, getOwner as getOwner2, onCleanup as onCleanup2 } from "solid-js";
|
|
36
|
+
import { createLankaAccessTracker } from "lanka/extend";
|
|
37
|
+
function useLankaVM(viewModel, selector) {
|
|
38
|
+
const tracker = createLankaAccessTracker(viewModel);
|
|
39
|
+
const read = () => selector ? selector(viewModel.getState()) : tracker.read();
|
|
40
|
+
const [state, setState] = createSignal2(read(), { equals: false });
|
|
41
|
+
const stop = viewModel.subscribe((next, prev) => {
|
|
42
|
+
if (selector) {
|
|
43
|
+
const picked = read();
|
|
44
|
+
if (Object.is(picked, state())) return;
|
|
45
|
+
setState(() => picked);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (!tracker.shouldNotify(next, prev)) {
|
|
49
|
+
tracker.reportSkipped(next, prev);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
setState(() => read());
|
|
53
|
+
});
|
|
54
|
+
const accessor = state;
|
|
55
|
+
accessor.stop = stop;
|
|
56
|
+
if (getOwner2()) onCleanup2(stop);
|
|
57
|
+
return accessor;
|
|
58
|
+
}
|
|
59
|
+
export {
|
|
60
|
+
toLankaSolidVM,
|
|
61
|
+
useLankaVM
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/to-lanka-solid-vm/toLankaSolidVM.ts","../src/use-lanka-vm/useLankaVM.ts"],"sourcesContent":["import { createSignal, getOwner, onCleanup } from \"solid-js\";\nimport { createLankaViewSubscription } from \"lanka/extend\";\nimport type { ILankaReadableVM } from \"lanka/viewmodel\";\n\n/**\n * A ViewModel read the way Solid reads shared state.\n *\n * The state's own members, directly, plus the one meta member a caller needs.\n * `$`-prefixed so it cannot collide with a state key, which is the same reason\n * Solid's own stores keep their helpers off the object.\n *\n * Called a ViewModel and not a store, deliberately: `createStore` is Solid's\n * noun for the shape, and what holds the state, the actions and the scenario\n * bindings is the ViewModel.\n */\nexport type TLankaSolidVM<TState extends object> = TState & {\n\t/** Releases the subscription. Rarely needed: an owner does it. */\n\t$stop: () => void;\n};\n\n/**\n * How the store answers for the ViewModel behind it.\n *\n * Its own function, because the traps are the whole mechanism and the factory\n * above is then the subscription and the Proxy. Read together they were sixty\n * lines whose shape said \"a function doing two things\", which is what the\n * composition canon calls it.\n */\nconst readsTheViewModel = <TState extends object, TStore extends object>(\n\tcurrent: () => TState,\n\tstop: () => void,\n): ProxyHandler<TStore> => ({\n\tget: (_target, key) => (key === \"$stop\" ? stop : Reflect.get(current(), key)),\n\n\thas: (_target, key) => key === \"$stop\" || key in current(),\n\n\townKeys: () => Reflect.ownKeys(current()),\n\n\t/*\n\t * Reported as configurable, always.\n\t *\n\t * A Proxy must not claim a non-configurable descriptor its target lacks — the\n\t * runtime throws. The target here is a bare object while the keys live on the\n\t * state, so every descriptor this hands back is invented and must say it can be\n\t * redefined. Without it `{ ...store }` and `Object.keys(store)` throw rather\n\t * than read, and a devtool does one of them on sight.\n\t */\n\tgetOwnPropertyDescriptor: (_target, key) =>\n\t\tkey === \"$stop\"\n\t\t\t? { value: stop, configurable: true, enumerable: false, writable: false }\n\t\t\t: { ...Reflect.getOwnPropertyDescriptor(current(), key), configurable: true },\n});\n\n/**\n * Reads a ViewModel the way Solid reads an object, with no call on the outside.\n *\n * ```tsx\n * const todos = toLankaSolidVM(todosVM);\n *\n * <For each={todos.rows}>{(row) => <li>{row}</li>}</For>\n * ```\n *\n * ## Why this exists beside `useLankaVM`\n *\n * `useLankaVM` answers an `Accessor`, which is Solid's own shape for a value and\n * the one every other binding on the shelf parallels: `state().rows`. It is also\n * not how Solid holds an OBJECT. `createStore` gives a proxy read as `state.rows`\n * — no call, and the read itself is the subscription — and that is what a Solid\n * codebase has in it. A consumer with that habit writes `todos.rows`, gets\n * `undefined`, and learns that lanka is a foreign object.\n *\n * ## The read is the subscription, in both graphs at once\n *\n * A read goes through the access tracker, which records the key, AND through the\n * signal, which registers the surrounding computation with Solid. One access,\n * two graphs — the same alignment the Svelte binding gets from getters, and the\n * reason neither of them needs a diff.\n *\n * The signal holds a VERSION rather than the state. A tracked read hands back the\n * same proxy while the state object is unchanged, and Solid compares by identity,\n * so a signal holding the state would be a no-op exactly when the tracker did its\n * job — and a signal holding the state is also a SNAPSHOT, which is wrong for\n * something read from ordinary code at arbitrary moments. `store.load()` followed\n * by `store.rows` is the shape that settles it.\n *\n * ## What it is not\n *\n * Not `createStore`. Solid's store is a write path as well as a read one, and a\n * ViewModel's writes belong to its actions — `setStore` beside them would be a\n * second place state changes. This is the read half, which is the half a screen\n * has.\n */\nexport const toLankaSolidVM = <TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n): TLankaSolidVM<TState> => {\n\tconst [version, setVersion] = createSignal(0);\n\n\tconst view = createLankaViewSubscription(viewModel, () => {\n\t\tsetVersion((seen) => seen + 1);\n\t});\n\n\t// An owner is a component or a `createRoot`. Outside one there is nothing to\n\t// attach to and `onCleanup` would warn, so the caller keeps `$stop`.\n\tif (getOwner()) onCleanup(view.stop);\n\n\tconst current = (): TState => {\n\t\t// Read for the DEPENDENCY, discard the number. A computation reading\n\t\t// `store.rows` must re-run when the version moves, and the version is the\n\t\t// only signal in here.\n\t\tvoid version();\n\n\t\treturn view.read();\n\t};\n\n\treturn new Proxy({} as TLankaSolidVM<TState>, readsTheViewModel(current, view.stop));\n};\n","import { createSignal, getOwner, onCleanup } from \"solid-js\";\nimport { createLankaAccessTracker } from \"lanka/extend\";\nimport type { Accessor } from \"solid-js\";\nimport type { ILankaReadableVM } from \"lanka/viewmodel\";\n\n/** A ViewModel read from Solid: an accessor, and a way to stop reading it. */\nexport type TLankaVMAccessor<TValue> = Accessor<TValue> & {\n\t/**\n\t * Releases the subscription.\n\t *\n\t * Called for you by `onCleanup` inside a component or a root. It is published\n\t * because a read made where there is no owner — module level, a test — has\n\t * nobody to call it, and Solid warns about that case rather than handling it.\n\t */\n\tstop: () => void;\n};\n\n/**\n * Reads a ViewModel from Solid.\n *\n * ```tsx\n * export const TodoScreen = () => {\n * \tconst state = useLankaVM(todoVM);\n *\n * \treturn <For each={state().todos}>{(todo) => <li>{todo.title}</li>}</For>;\n * };\n * ```\n *\n * Without a selector the accessor answers a Proxy that records which keys were\n * read, and the signal changes only when one of THOSE moves. With a selector the\n * selector decides and tracking is bypassed.\n *\n * ## Why tracking still earns its place here\n *\n * Solid already skips work a signal did not feed, so a coarse binding would be\n * less wrong here than elsewhere. It would still be wrong: without tracking every\n * change writes a new object into the signal, and every effect reading ANY part\n * of it re-runs. The tracker is what keeps the signal UNCHANGED when nothing a\n * reader looked at moved — and an unchanged signal is work Solid never starts.\n *\n * ## What \"a render\" means in a framework that has none\n *\n * A Solid component runs once; what updates is the DOM node that read the signal.\n * So there is nothing here that corresponds to a re-render, and the conformance\n * suite's `renders()` counts the reading effect's runs instead — which is the\n * closest question this framework can be asked.\n *\n * ## What this function does NOT contain\n *\n * The recording, the comparison and the blind-spot warning are in core. If this\n * file ever needs more than the port gives it, the port has the defect.\n */\nexport function useLankaVM<TState extends object>(\n\tviewModel: ILankaReadableVM<TState>,\n): TLankaVMAccessor<TState>;\n\nexport function useLankaVM<TState extends object, TSelected>(\n\tviewModel: ILankaReadableVM<TState>,\n\tselector: (state: TState) => TSelected,\n): TLankaVMAccessor<TSelected>;\n\nexport function useLankaVM<TState extends object, TSelected>(\n\tviewModel: ILankaReadableVM<TState>,\n\tselector?: (state: TState) => TSelected,\n): TLankaVMAccessor<TState | TSelected> {\n\tconst tracker = createLankaAccessTracker(viewModel);\n\tconst read = (): TState | TSelected =>\n\t\tselector ? selector(viewModel.getState()) : tracker.read();\n\n\t// `equals: false` because a tracked read hands back the SAME proxy while the\n\t// state object is unchanged, and Solid compares by identity — so setting it\n\t// would be a no-op exactly when the tracker did its job. What decides whether\n\t// anything happens is `shouldNotify` below, which is the framework's answer\n\t// rather than the signal's.\n\tconst [state, setState] = createSignal<TState | TSelected>(read(), { equals: false });\n\n\tconst stop = viewModel.subscribe((next, prev) => {\n\t\tif (selector) {\n\t\t\tconst picked = read();\n\n\t\t\t// Only when the SELECTION moved. The signal is `equals: false`, so\n\t\t\t// setting it always wakes — which is right for a tracked read and wrong\n\t\t\t// for a selected one, and is what the suite's selector scenes refuse.\n\t\t\tif (Object.is(picked, state())) return;\n\n\t\t\tsetState(() => picked);\n\n\t\t\treturn;\n\t\t}\n\n\t\tif (!tracker.shouldNotify(next, prev)) {\n\t\t\t// No update will follow. If the changed key is linked to this reader\n\t\t\t// through a getter it read, the screen froze — and in development core\n\t\t\t// says so by name.\n\t\t\ttracker.reportSkipped(next, prev);\n\t\t\treturn;\n\t\t}\n\n\t\tsetState(() => read());\n\t});\n\tconst accessor = state as TLankaVMAccessor<TState | TSelected>;\n\taccessor.stop = stop;\n\n\t// Inside a component or a root, Solid owns the lifetime and the subscription\n\t// goes with it. Outside one there is no owner, and `onCleanup` would warn —\n\t// so the caller keeps `stop`.\n\tif (getOwner()) onCleanup(stop);\n\n\treturn accessor;\n}\n"],"mappings":";;;AAAA,SAAS,cAAc,UAAU,iBAAiB;AAClD,SAAS,mCAAmC;AA2B5C,IAAM,oBAAoB,CACzB,SACA,UAC2B;AAAA,EAC3B,KAAK,CAAC,SAAS,QAAS,QAAQ,UAAU,OAAO,QAAQ,IAAI,QAAQ,GAAG,GAAG;AAAA,EAE3E,KAAK,CAAC,SAAS,QAAQ,QAAQ,WAAW,OAAO,QAAQ;AAAA,EAEzD,SAAS,MAAM,QAAQ,QAAQ,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWxC,0BAA0B,CAAC,SAAS,QACnC,QAAQ,UACL,EAAE,OAAO,MAAM,cAAc,MAAM,YAAY,OAAO,UAAU,MAAM,IACtE,EAAE,GAAG,QAAQ,yBAAyB,QAAQ,GAAG,GAAG,GAAG,cAAc,KAAK;AAC/E;AAyCO,IAAM,iBAAiB,CAC7B,cAC2B;AAC3B,QAAM,CAAC,SAAS,UAAU,IAAI,aAAa,CAAC;AAE5C,QAAM,OAAO,4BAA4B,WAAW,MAAM;AACzD,eAAW,CAAC,SAAS,OAAO,CAAC;AAAA,EAC9B,CAAC;AAID,MAAI,SAAS,EAAG,WAAU,KAAK,IAAI;AAEnC,QAAM,UAAU,MAAc;AAI7B,SAAK,QAAQ;AAEb,WAAO,KAAK,KAAK;AAAA,EAClB;AAEA,SAAO,IAAI,MAAM,CAAC,GAA4B,kBAAkB,SAAS,KAAK,IAAI,CAAC;AACpF;;;ACnHA,SAAS,gBAAAA,eAAc,YAAAC,WAAU,aAAAC,kBAAiB;AAClD,SAAS,gCAAgC;AA4DlC,SAAS,WACf,WACA,UACuC;AACvC,QAAM,UAAU,yBAAyB,SAAS;AAClD,QAAM,OAAO,MACZ,WAAW,SAAS,UAAU,SAAS,CAAC,IAAI,QAAQ,KAAK;AAO1D,QAAM,CAAC,OAAO,QAAQ,IAAIF,cAAiC,KAAK,GAAG,EAAE,QAAQ,MAAM,CAAC;AAEpF,QAAM,OAAO,UAAU,UAAU,CAAC,MAAM,SAAS;AAChD,QAAI,UAAU;AACb,YAAM,SAAS,KAAK;AAKpB,UAAI,OAAO,GAAG,QAAQ,MAAM,CAAC,EAAG;AAEhC,eAAS,MAAM,MAAM;AAErB;AAAA,IACD;AAEA,QAAI,CAAC,QAAQ,aAAa,MAAM,IAAI,GAAG;AAItC,cAAQ,cAAc,MAAM,IAAI;AAChC;AAAA,IACD;AAEA,aAAS,MAAM,KAAK,CAAC;AAAA,EACtB,CAAC;AACD,QAAM,WAAW;AACjB,WAAS,OAAO;AAKhB,MAAIC,UAAS,EAAG,CAAAC,WAAU,IAAI;AAE9B,SAAO;AACR;","names":["createSignal","getOwner","onCleanup"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { render } from '@solidjs/testing-library';
|
|
2
|
+
import { IPrepareLankaRenderOptions } from '@lankajs/tool-testing';
|
|
3
|
+
import { ILankaInstance } from 'lanka';
|
|
4
|
+
|
|
5
|
+
type TSolidRender = typeof render;
|
|
6
|
+
interface IRenderWithLankaOptions extends Omit<NonNullable<Parameters<TSolidRender>[1]>, "wrapper">, IPrepareLankaRenderOptions {
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* What a render with a bootstrapped framework ADDS to the library's own result.
|
|
10
|
+
*
|
|
11
|
+
* An interface over the addition rather than over the whole result, and the same
|
|
12
|
+
* in all five bindings: Svelte Testing Library's result carries a string index
|
|
13
|
+
* signature for its bound queries, so a named member added by extension has to
|
|
14
|
+
* satisfy it — and `lanka` is an instance, not a query. Describing only the
|
|
15
|
+
* addition is true of every library and needs no cast anywhere.
|
|
16
|
+
*/
|
|
17
|
+
interface IRenderWithLankaResult {
|
|
18
|
+
/** The instance the render used. */
|
|
19
|
+
lanka: ILankaInstance;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Rendering a Solid tree with a bootstrapped framework.
|
|
23
|
+
*
|
|
24
|
+
* ## Why
|
|
25
|
+
*
|
|
26
|
+
* A component reading a ViewModel needs a live instance: without one the first
|
|
27
|
+
* scenario or locator access fails. Assembling bootstrap in every component test
|
|
28
|
+
* is twenty lines of preamble that diverge between files silently.
|
|
29
|
+
*
|
|
30
|
+
* ## What is here, and what is in the kit
|
|
31
|
+
*
|
|
32
|
+
* The five bindings publish this name and differ only in which `render` they
|
|
33
|
+
* call. Everything else — a fresh instance, the doubles, the caller's setup and
|
|
34
|
+
* the scenario layer brought up in that order — is `prepareLankaRender` in
|
|
35
|
+
* `@lankajs/tool-testing`, which is the one place all five already look.
|
|
36
|
+
*
|
|
37
|
+
* ## The option and result types are DERIVED, not named
|
|
38
|
+
*
|
|
39
|
+
* Solid Testing Library publishes no `RenderOptions` or `RenderResult` type to
|
|
40
|
+
* import — it exports functions and lets inference do the rest. Reading them off
|
|
41
|
+
* `render` itself is therefore the honest spelling, and it cannot drift from the
|
|
42
|
+
* library the way a hand-copied interface would.
|
|
43
|
+
*/
|
|
44
|
+
declare const renderWithLanka: (ui: Parameters<TSolidRender>[0], options?: IRenderWithLankaOptions) => ReturnType<TSolidRender> & IRenderWithLankaResult;
|
|
45
|
+
|
|
46
|
+
export { type IRenderWithLankaOptions, type IRenderWithLankaResult, renderWithLanka };
|