@manyducks.co/dolla 0.67.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 +643 -0
- package/build.js +34 -0
- package/index.d.ts +12 -0
- package/jsx-dev-runtime.d.ts +1 -0
- package/jsx-runtime.d.ts +1 -0
- package/lib/app.d.ts +138 -0
- package/lib/classes/CrashCollector.d.ts +30 -0
- package/lib/classes/DebugHub.d.ts +60 -0
- package/lib/index.d.ts +23 -0
- package/lib/index.js +4062 -0
- package/lib/index.js.map +7 -0
- package/lib/jsx/jsx-dev-runtime.d.ts +3 -0
- package/lib/jsx/jsx-dev-runtime.js +20 -0
- package/lib/jsx/jsx-dev-runtime.js.map +7 -0
- package/lib/jsx/jsx-runtime.d.ts +10 -0
- package/lib/jsx/jsx-runtime.js +22 -0
- package/lib/jsx/jsx-runtime.js.map +7 -0
- package/lib/markup.d.ts +81 -0
- package/lib/nodes/cond.d.ts +28 -0
- package/lib/nodes/html.d.ts +30 -0
- package/lib/nodes/observer.d.ts +33 -0
- package/lib/nodes/outlet.d.ts +26 -0
- package/lib/nodes/portal.d.ts +22 -0
- package/lib/nodes/repeat.d.ts +36 -0
- package/lib/nodes/text.d.ts +20 -0
- package/lib/spring.d.ts +40 -0
- package/lib/state.d.ts +84 -0
- package/lib/store.d.ts +67 -0
- package/lib/stores/dialog.d.ts +30 -0
- package/lib/stores/document.d.ts +10 -0
- package/lib/stores/http.d.ts +60 -0
- package/lib/stores/language.d.ts +39 -0
- package/lib/stores/render.d.ts +18 -0
- package/lib/stores/router.d.ts +118 -0
- package/lib/testing/classes/MockHTTP.d.ts +10 -0
- package/lib/testing/index.d.ts +4 -0
- package/lib/testing/makeMockDOMNode.d.ts +10 -0
- package/lib/testing/makeMockFetch.d.ts +36 -0
- package/lib/testing/makeMockFetch.test.d.ts +1 -0
- package/lib/testing/stores/dialog.d.ts +6 -0
- package/lib/testing/stores/http.d.ts +13 -0
- package/lib/testing/stores/page.d.ts +7 -0
- package/lib/testing/stores/router.d.ts +12 -0
- package/lib/testing/wrapStore.d.ts +8 -0
- package/lib/testing/wrapStore.test.d.ts +1 -0
- package/lib/testing/wrapView.d.ts +0 -0
- package/lib/types.d.ts +3388 -0
- package/lib/utils.d.ts +14 -0
- package/lib/view.d.ts +80 -0
- package/lib/views/fragment.d.ts +2 -0
- package/lib/views/store-scope.d.ts +10 -0
- package/package.json +56 -0
- package/tests/state.test.js +290 -0
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare function deepEqual(one: any, two: any): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Takes an old value and a new value. Returns a merged copy if both are objects, otherwise returns the new value.
|
|
4
|
+
*/
|
|
5
|
+
export declare function merge(one: unknown, two: unknown): any;
|
|
6
|
+
/**
|
|
7
|
+
* Returns a new object without the specified keys.
|
|
8
|
+
* If called without object, returns a function that takes an object
|
|
9
|
+
* and returns a version with the original keys omitted.
|
|
10
|
+
*
|
|
11
|
+
* @param keys - An array of keys to omit.
|
|
12
|
+
* @param object - An object to clone without the omitted keys.
|
|
13
|
+
*/
|
|
14
|
+
export declare function omit<O extends Record<any, any>>(keys: (keyof O)[], object: O): Record<any, any>;
|
package/lib/view.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type AppContext, type ElementContext } from "./app.js";
|
|
2
|
+
import { type DebugChannel } from "./classes/DebugHub.js";
|
|
3
|
+
import { type DOMHandle, type Markup } from "./markup.js";
|
|
4
|
+
import { type Readable, type ReadableValues } from "./state.js";
|
|
5
|
+
import { type Store } from "./store.js";
|
|
6
|
+
import type { BuiltInStores } from "./types.js";
|
|
7
|
+
/**
|
|
8
|
+
* Any valid value that a View can return.
|
|
9
|
+
*/
|
|
10
|
+
export type ViewResult = Node | Readable<any> | Markup | Markup[] | null;
|
|
11
|
+
export type View<P> = (props: P, context: ViewContext) => ViewResult;
|
|
12
|
+
export interface ViewContext extends DebugChannel {
|
|
13
|
+
/**
|
|
14
|
+
* A string ID unique to this view.
|
|
15
|
+
*/
|
|
16
|
+
readonly uniqueId: string;
|
|
17
|
+
/**
|
|
18
|
+
* Returns the shared instance of `store`.
|
|
19
|
+
*/
|
|
20
|
+
getStore<T extends Store<any, any>>(store: T): ReturnType<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the shared instance of a built-in store.
|
|
23
|
+
*/
|
|
24
|
+
getStore<N extends keyof BuiltInStores>(name: N): BuiltInStores[N];
|
|
25
|
+
/**
|
|
26
|
+
* Runs `callback` just before this view is connected. DOM nodes are not yet attached to the page.
|
|
27
|
+
*/
|
|
28
|
+
beforeConnect(callback: () => void): void;
|
|
29
|
+
/**
|
|
30
|
+
* Runs `callback` after this view is connected. DOM nodes are now attached to the page.
|
|
31
|
+
*/
|
|
32
|
+
onConnected(callback: () => void): void;
|
|
33
|
+
/**
|
|
34
|
+
* Runs `callback` just before this view is disconnected. DOM nodes are still attached to the page.
|
|
35
|
+
*/
|
|
36
|
+
beforeDisconnect(callback: () => void): void;
|
|
37
|
+
/**
|
|
38
|
+
* Runs `callback` after this view is disconnected. DOM nodes are no longer attached to the page.
|
|
39
|
+
*/
|
|
40
|
+
onDisconnected(callback: () => void): void;
|
|
41
|
+
/**
|
|
42
|
+
* The name of this view for logging and debugging purposes.
|
|
43
|
+
*/
|
|
44
|
+
name: string;
|
|
45
|
+
/**
|
|
46
|
+
* Takes an Error object, unmounts the app and displays its crash page.
|
|
47
|
+
*/
|
|
48
|
+
crash(error: Error): void;
|
|
49
|
+
/**
|
|
50
|
+
* Observes a readable value while this view is connected. Calls `callback` each time the value changes.
|
|
51
|
+
*/
|
|
52
|
+
observe<T>(readable: Readable<T>, callback: (currentValue: T, previousValue: T) => void): void;
|
|
53
|
+
/**
|
|
54
|
+
* Observes a set of readable values while this view is connected.
|
|
55
|
+
* Calls `callback` with each value in the same order as `readables` each time any of their values change.
|
|
56
|
+
*/
|
|
57
|
+
observe<T extends Readable<any>[]>(readables: [...T], callback: (currentValues: ReadableValues<T>, previousValues: ReadableValues<T>) => void): void;
|
|
58
|
+
/**
|
|
59
|
+
* Returns a Markup element that displays this view's children.
|
|
60
|
+
*/
|
|
61
|
+
outlet(): Markup;
|
|
62
|
+
}
|
|
63
|
+
export interface ViewContextSecrets {
|
|
64
|
+
appContext: AppContext;
|
|
65
|
+
elementContext: ElementContext;
|
|
66
|
+
}
|
|
67
|
+
export declare function getViewSecrets(ctx: ViewContext): ViewContextSecrets;
|
|
68
|
+
export declare function view<P>(callback: View<P>): View<P>;
|
|
69
|
+
/**
|
|
70
|
+
* Parameters passed to the makeView function.
|
|
71
|
+
*/
|
|
72
|
+
interface ViewConfig<P> {
|
|
73
|
+
view: View<P>;
|
|
74
|
+
appContext: AppContext;
|
|
75
|
+
elementContext: ElementContext;
|
|
76
|
+
props: P;
|
|
77
|
+
children?: Markup[];
|
|
78
|
+
}
|
|
79
|
+
export declare function initView<P>(config: ViewConfig<P>): DOMHandle;
|
|
80
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Store } from "../store.js";
|
|
2
|
+
import { ViewContext } from "../view.js";
|
|
3
|
+
export interface StoreScopeProps<O, E> {
|
|
4
|
+
store: Store<O, E>;
|
|
5
|
+
options?: O;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Creates an instance of a store available only to children of this StoreScope.
|
|
9
|
+
*/
|
|
10
|
+
export declare function StoreScope<O, E>(props: StoreScopeProps<O, E>, ctx: ViewContext): import("../markup.js").Markup;
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@manyducks.co/dolla",
|
|
3
|
+
"version": "0.67.0",
|
|
4
|
+
"description": "Front-end components, routing and state management.",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"types": "./index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"repository": "https://github.com/manyducksco/dolla",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "tsc && node --test",
|
|
12
|
+
"build": "tsc && node build.js",
|
|
13
|
+
"start": "tsc --watch",
|
|
14
|
+
"prepublishOnly": "NODE_ENV=production npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"web app",
|
|
18
|
+
"front end framework",
|
|
19
|
+
"functional",
|
|
20
|
+
"reactive state"
|
|
21
|
+
],
|
|
22
|
+
"author": "tony@manyducks.co",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"import": "./lib/index.js",
|
|
27
|
+
"types": "./index.d.ts"
|
|
28
|
+
},
|
|
29
|
+
"./testing": {
|
|
30
|
+
"import": "./lib/testing/index.js"
|
|
31
|
+
},
|
|
32
|
+
"./jsx-runtime": {
|
|
33
|
+
"import": "./lib/jsx/jsx-runtime.js",
|
|
34
|
+
"types": "./jsx-runtime.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./jsx-dev-runtime": {
|
|
37
|
+
"import": "./lib/jsx/jsx-dev-runtime.js",
|
|
38
|
+
"types": "./jsx-dev-runtime.d.ts"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@borf/bedrock": "^3.1.0",
|
|
43
|
+
"fetch-ponyfill": "^7.1.0",
|
|
44
|
+
"history": "^5.3.0",
|
|
45
|
+
"nanoid": "^4.0.2",
|
|
46
|
+
"simple-color-hash": "^1.0.2"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "^18.17.6",
|
|
50
|
+
"csstype": "^3.1.2",
|
|
51
|
+
"esbuild": "^0.19.2",
|
|
52
|
+
"prettier": "^2.8.8",
|
|
53
|
+
"typescript": "^5.2.2",
|
|
54
|
+
"zod": "^3.22.2"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert";
|
|
3
|
+
import { proxy, observe, readable, writable, computed, unwrap, isReadable, isWritable } from "../lib/index.js";
|
|
4
|
+
|
|
5
|
+
test("isReadable, isWritable: returns correct results", (t) => {
|
|
6
|
+
const $$writable = writable(5);
|
|
7
|
+
const $readable = readable($$writable);
|
|
8
|
+
const $computed = computed($readable, (x) => x * 2);
|
|
9
|
+
|
|
10
|
+
assert.strictEqual(isWritable($$writable), true);
|
|
11
|
+
assert.strictEqual(isReadable($$writable), true);
|
|
12
|
+
|
|
13
|
+
assert.strictEqual(isWritable($readable), false);
|
|
14
|
+
assert.strictEqual(isReadable($readable), true);
|
|
15
|
+
|
|
16
|
+
assert.strictEqual(isWritable($computed), false);
|
|
17
|
+
assert.strictEqual(isReadable($computed), true);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
test("readable, writable, computed: basic functionality", (t) => {
|
|
21
|
+
const $$writable = writable("writable");
|
|
22
|
+
const $readable = readable("test");
|
|
23
|
+
const $x = computed($readable, (v) => v.toUpperCase());
|
|
24
|
+
const $y = readable($$writable);
|
|
25
|
+
|
|
26
|
+
const compute = t.mock.fn(([x, y]) => {
|
|
27
|
+
return x + y;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
const $computed = computed([$x, $y], compute);
|
|
31
|
+
|
|
32
|
+
assert.strictEqual($computed.get(), "TESTwritable");
|
|
33
|
+
|
|
34
|
+
const observer = t.mock.fn();
|
|
35
|
+
const stop = observe($$writable, observer); // Observer is called immediately with the current value.
|
|
36
|
+
|
|
37
|
+
$$writable.set("new value"); // First observed change (second call)
|
|
38
|
+
$$writable.set("new value 2"); // Second observed change (third call)
|
|
39
|
+
|
|
40
|
+
assert.strictEqual($$writable.get(), "new value 2");
|
|
41
|
+
assert.strictEqual($computed.get(), "TESTnew value 2");
|
|
42
|
+
|
|
43
|
+
assert.strictEqual(unwrap($$writable), "new value 2");
|
|
44
|
+
assert.strictEqual(unwrap("value"), "value");
|
|
45
|
+
|
|
46
|
+
stop();
|
|
47
|
+
|
|
48
|
+
$$writable.set("can you hear me now?"); // Sets value but is not observed.
|
|
49
|
+
|
|
50
|
+
assert.strictEqual($$writable.get(), "can you hear me now?");
|
|
51
|
+
assert.strictEqual($computed.get(), "TESTcan you hear me now?");
|
|
52
|
+
|
|
53
|
+
assert.strictEqual(observer.mock.calls.length, 3);
|
|
54
|
+
assert.deepEqual(observer.mock.calls[0].arguments, ["writable", undefined]);
|
|
55
|
+
assert.deepEqual(observer.mock.calls[1].arguments, ["new value", "writable"]);
|
|
56
|
+
assert.deepEqual(observer.mock.calls[2].arguments, ["new value 2", "new value"]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("writable -> readable -> readable: chained transforms", (t) => {
|
|
60
|
+
const $$number = writable(5);
|
|
61
|
+
const $doubled = computed($$number, (x) => x * 2);
|
|
62
|
+
const $quadrupled = computed($doubled, (x) => x * 2);
|
|
63
|
+
const observer = t.mock.fn();
|
|
64
|
+
|
|
65
|
+
assert.strictEqual($doubled.get(), 10);
|
|
66
|
+
assert.strictEqual($quadrupled.get(), 20);
|
|
67
|
+
|
|
68
|
+
const stop = observe($quadrupled, observer);
|
|
69
|
+
|
|
70
|
+
assert.strictEqual(observer.mock.calls.length, 1);
|
|
71
|
+
assert.deepEqual(observer.mock.calls[0].arguments, [20, undefined]);
|
|
72
|
+
|
|
73
|
+
$$number.set(50);
|
|
74
|
+
|
|
75
|
+
assert.strictEqual(observer.mock.calls.length, 2);
|
|
76
|
+
assert.deepEqual(observer.mock.calls[1].arguments, [200, 20]);
|
|
77
|
+
|
|
78
|
+
stop();
|
|
79
|
+
|
|
80
|
+
$$number.set(100);
|
|
81
|
+
|
|
82
|
+
assert.strictEqual($quadrupled.get(), 400);
|
|
83
|
+
|
|
84
|
+
// Not called again after stop().
|
|
85
|
+
assert.strictEqual(observer.mock.calls.length, 2);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test("writable: update", (t) => {
|
|
89
|
+
const $$numbers = writable(["one", "two", "three"]);
|
|
90
|
+
|
|
91
|
+
const original = $$numbers.get();
|
|
92
|
+
|
|
93
|
+
$$numbers.update((list) => [...list, "four"]);
|
|
94
|
+
|
|
95
|
+
const updated = $$numbers.get();
|
|
96
|
+
|
|
97
|
+
assert.notDeepEqual(original, updated);
|
|
98
|
+
assert.strictEqual(updated.length, 4);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("readable, writable, computed: observer called with initial value when registered", (t) => {
|
|
102
|
+
const $$value = writable(1);
|
|
103
|
+
const $value = readable(5);
|
|
104
|
+
const $doubled = computed($value, (x) => x * 2);
|
|
105
|
+
const $multi = computed([$value, $doubled], ([x, y]) => x + y);
|
|
106
|
+
|
|
107
|
+
// Writable
|
|
108
|
+
const wObserver = t.mock.fn();
|
|
109
|
+
const wStop = observe($$value, wObserver);
|
|
110
|
+
assert.strictEqual(wObserver.mock.calls.length, 1);
|
|
111
|
+
assert.deepEqual(wObserver.mock.calls[0].arguments, [1, undefined]);
|
|
112
|
+
wStop();
|
|
113
|
+
|
|
114
|
+
// Readable
|
|
115
|
+
const rObserver = t.mock.fn();
|
|
116
|
+
const rStop = observe($value, rObserver);
|
|
117
|
+
assert.strictEqual(rObserver.mock.calls.length, 1);
|
|
118
|
+
assert.deepEqual(rObserver.mock.calls[0].arguments, [5, undefined]);
|
|
119
|
+
rStop();
|
|
120
|
+
|
|
121
|
+
// Computed (single source)
|
|
122
|
+
const cObserver1 = t.mock.fn();
|
|
123
|
+
const cStop1 = observe($doubled, cObserver1);
|
|
124
|
+
assert.strictEqual(cObserver1.mock.calls.length, 1);
|
|
125
|
+
assert.deepEqual(cObserver1.mock.calls[0].arguments, [10, undefined]);
|
|
126
|
+
cStop1();
|
|
127
|
+
|
|
128
|
+
// Computed (multi source)
|
|
129
|
+
const cObserver2 = t.mock.fn();
|
|
130
|
+
const cStop2 = observe($multi, cObserver2);
|
|
131
|
+
assert.strictEqual(cObserver2.mock.calls.length, 1);
|
|
132
|
+
assert.deepEqual(cObserver2.mock.calls[0].arguments, [15, undefined]);
|
|
133
|
+
cStop2();
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("computed: basic functionality", (t) => {
|
|
137
|
+
const $$one = writable(2);
|
|
138
|
+
const $$two = writable(4);
|
|
139
|
+
const $$three = writable(8);
|
|
140
|
+
|
|
141
|
+
const joinFirst = t.mock.fn(([one, two]) => {
|
|
142
|
+
return one + two;
|
|
143
|
+
});
|
|
144
|
+
const $first = computed([$$one, $$two], joinFirst);
|
|
145
|
+
|
|
146
|
+
const joinSecond = t.mock.fn(([one, two, three]) => {
|
|
147
|
+
return one + two + three;
|
|
148
|
+
});
|
|
149
|
+
const $second = computed([$$one, $$two, $$three], joinSecond);
|
|
150
|
+
|
|
151
|
+
assert.strictEqual($first.get(), 6);
|
|
152
|
+
assert.strictEqual($second.get(), 14);
|
|
153
|
+
|
|
154
|
+
assert.strictEqual(joinFirst.mock.callCount(), 1);
|
|
155
|
+
assert.strictEqual(joinSecond.mock.callCount(), 1);
|
|
156
|
+
|
|
157
|
+
const observer = t.mock.fn();
|
|
158
|
+
const stop = observe($second, observer);
|
|
159
|
+
|
|
160
|
+
assert.strictEqual(observer.mock.callCount(), 1);
|
|
161
|
+
assert.deepEqual(observer.mock.calls[0].arguments, [14, undefined]); // Observer receives initial value.
|
|
162
|
+
assert.strictEqual(joinSecond.mock.callCount(), 2);
|
|
163
|
+
|
|
164
|
+
$$two.set(16);
|
|
165
|
+
|
|
166
|
+
assert.strictEqual(joinFirst.mock.callCount(), 1);
|
|
167
|
+
assert.strictEqual(joinSecond.mock.callCount(), 3);
|
|
168
|
+
|
|
169
|
+
assert.strictEqual($first.get(), 18);
|
|
170
|
+
assert.strictEqual($second.get(), 26);
|
|
171
|
+
|
|
172
|
+
assert.strictEqual(joinFirst.mock.callCount(), 2);
|
|
173
|
+
assert.strictEqual(joinSecond.mock.callCount(), 3);
|
|
174
|
+
|
|
175
|
+
assert.strictEqual(observer.mock.callCount(), 2); // Observer received value.
|
|
176
|
+
assert.deepEqual(observer.mock.calls[1].arguments, [26, 14]);
|
|
177
|
+
|
|
178
|
+
stop();
|
|
179
|
+
|
|
180
|
+
$$one.set(32);
|
|
181
|
+
|
|
182
|
+
assert.strictEqual($first.get(), 48);
|
|
183
|
+
assert.strictEqual($second.get(), 56);
|
|
184
|
+
|
|
185
|
+
assert.strictEqual(joinFirst.mock.callCount(), 3);
|
|
186
|
+
assert.strictEqual(joinSecond.mock.callCount(), 4);
|
|
187
|
+
|
|
188
|
+
assert.strictEqual(observer.mock.callCount(), 2); // Not called after stop()
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("computed: observers received value when undefined", (t) => {
|
|
192
|
+
const $$one = writable(true);
|
|
193
|
+
const $$two = writable(false);
|
|
194
|
+
|
|
195
|
+
const $joined = computed([$$one, $$two], ([one, two]) => {
|
|
196
|
+
if (one && two) {
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
if (!one && !two) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
assert.strictEqual($joined.get(), undefined);
|
|
205
|
+
|
|
206
|
+
const observer = t.mock.fn();
|
|
207
|
+
const stop = observe($joined, observer);
|
|
208
|
+
|
|
209
|
+
assert.strictEqual(observer.mock.calls.length, 1);
|
|
210
|
+
assert.deepEqual(observer.mock.calls[0].arguments, [undefined, undefined]);
|
|
211
|
+
|
|
212
|
+
$$two.set(true);
|
|
213
|
+
|
|
214
|
+
assert.strictEqual($joined.get(), true);
|
|
215
|
+
assert.strictEqual(observer.mock.calls.length, 2);
|
|
216
|
+
assert.deepEqual(observer.mock.calls[1].arguments, [true, undefined]);
|
|
217
|
+
|
|
218
|
+
$$one.set(false);
|
|
219
|
+
|
|
220
|
+
assert.strictEqual($joined.get(), undefined);
|
|
221
|
+
assert.strictEqual(observer.mock.calls.length, 3);
|
|
222
|
+
assert.deepEqual(observer.mock.calls[2].arguments, [undefined, true]);
|
|
223
|
+
|
|
224
|
+
stop();
|
|
225
|
+
|
|
226
|
+
$$two.set(false);
|
|
227
|
+
|
|
228
|
+
assert.strictEqual($joined.get(), false);
|
|
229
|
+
|
|
230
|
+
assert.strictEqual(observer.mock.calls.length, 3); // Not called again after stop()
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("computed: observer only gets new values when they are different", (t) => {
|
|
234
|
+
const $$source = writable({ name: "Jimbo Jones", age: 346 });
|
|
235
|
+
const $age = computed($$source, (x) => x.age);
|
|
236
|
+
|
|
237
|
+
const ageObserver = t.mock.fn();
|
|
238
|
+
const stop = observe($age, ageObserver);
|
|
239
|
+
|
|
240
|
+
assert.strictEqual(ageObserver.mock.calls.length, 1);
|
|
241
|
+
assert.deepEqual(ageObserver.mock.calls[0].arguments, [346, undefined]);
|
|
242
|
+
|
|
243
|
+
$$source.update((current) => {
|
|
244
|
+
return { ...current, name: "Not Jimbo Jones" };
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
assert.strictEqual(ageObserver.mock.calls.length, 1); // Age has not changed, so shouldn't be observed again.
|
|
248
|
+
|
|
249
|
+
$$source.update((current) => {
|
|
250
|
+
return { ...current, age: 347 };
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
assert.strictEqual(ageObserver.mock.calls.length, 2); // Age change should have been observed.
|
|
254
|
+
assert.deepEqual(ageObserver.mock.calls[1].arguments, [347, 346]);
|
|
255
|
+
|
|
256
|
+
stop();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("proxy", (t) => {
|
|
260
|
+
const $$numbers = writable([1, 2, 3]);
|
|
261
|
+
const $$hasTwo = proxy($$numbers, {
|
|
262
|
+
get(source) {
|
|
263
|
+
return source.get().includes(2);
|
|
264
|
+
},
|
|
265
|
+
set(source, value) {
|
|
266
|
+
source.update((numbers) => {
|
|
267
|
+
if (value && !numbers.includes(2)) {
|
|
268
|
+
return [...numbers, 2].sort();
|
|
269
|
+
}
|
|
270
|
+
if (!value && numbers.includes(2)) {
|
|
271
|
+
return numbers.filter((n) => n !== 2);
|
|
272
|
+
}
|
|
273
|
+
return numbers;
|
|
274
|
+
});
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
assert.deepEqual($$numbers.get(), [1, 2, 3]);
|
|
279
|
+
assert.strictEqual($$hasTwo.get(), true, "numbers should contain 2");
|
|
280
|
+
|
|
281
|
+
$$hasTwo.set(false);
|
|
282
|
+
|
|
283
|
+
assert.deepEqual($$numbers.get(), [1, 3]);
|
|
284
|
+
assert.strictEqual($$hasTwo.get(), false, "numbers should not contain 2");
|
|
285
|
+
|
|
286
|
+
$$hasTwo.set(true);
|
|
287
|
+
|
|
288
|
+
assert.deepEqual($$numbers.get(), [1, 2, 3]);
|
|
289
|
+
assert.strictEqual($$hasTwo.get(), true, "numbers should again contain 2");
|
|
290
|
+
});
|