@alloy-js/core 0.23.0-dev.10 → 0.23.0-dev.11
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/devtools/index.html +29 -17
- package/dist/src/binder.d.ts.map +1 -1
- package/dist/src/binder.js +5 -0
- package/dist/src/binder.js.map +1 -1
- package/dist/src/components/For.d.ts.map +1 -1
- package/dist/src/components/For.js +1 -1
- package/dist/src/components/For.js.map +1 -1
- package/dist/src/components/List.d.ts.map +1 -1
- package/dist/src/components/List.js +1 -1
- package/dist/src/components/List.js.map +1 -1
- package/dist/src/components/Switch.d.ts.map +1 -1
- package/dist/src/components/Switch.js +1 -1
- package/dist/src/components/Switch.js.map +1 -1
- package/dist/src/debug/diagnostics.test.js +3 -2
- package/dist/src/debug/diagnostics.test.js.map +1 -1
- package/dist/src/debug/effects.d.ts +12 -4
- package/dist/src/debug/effects.d.ts.map +1 -1
- package/dist/src/debug/effects.js +182 -52
- package/dist/src/debug/effects.js.map +1 -1
- package/dist/src/debug/effects.test.js +213 -41
- package/dist/src/debug/effects.test.js.map +1 -1
- package/dist/src/debug/files.d.ts.map +1 -1
- package/dist/src/debug/files.js +7 -18
- package/dist/src/debug/files.js.map +1 -1
- package/dist/src/debug/files.test.js +13 -36
- package/dist/src/debug/files.test.js.map +1 -1
- package/dist/src/debug/index.d.ts +4 -2
- package/dist/src/debug/index.d.ts.map +1 -1
- package/dist/src/debug/index.js +4 -2
- package/dist/src/debug/index.js.map +1 -1
- package/dist/src/debug/message-format.test.d.ts +2 -0
- package/dist/src/debug/message-format.test.d.ts.map +1 -0
- package/dist/src/debug/message-format.test.js +700 -0
- package/dist/src/debug/message-format.test.js.map +1 -0
- package/dist/src/debug/render-tree-orphans.test.d.ts +2 -0
- package/dist/src/debug/render-tree-orphans.test.d.ts.map +1 -0
- package/dist/src/debug/render-tree-orphans.test.js +297 -0
- package/dist/src/debug/render-tree-orphans.test.js.map +1 -0
- package/dist/src/debug/render.d.ts.map +1 -1
- package/dist/src/debug/render.js +83 -130
- package/dist/src/debug/render.js.map +1 -1
- package/dist/src/debug/render.test.js +91 -128
- package/dist/src/debug/render.test.js.map +1 -1
- package/dist/src/debug/symbols.d.ts +6 -5
- package/dist/src/debug/symbols.d.ts.map +1 -1
- package/dist/src/debug/symbols.js +46 -23
- package/dist/src/debug/symbols.js.map +1 -1
- package/dist/src/debug/symbols.test.js +15 -26
- package/dist/src/debug/symbols.test.js.map +1 -1
- package/dist/src/debug/trace-writer.d.ts +55 -0
- package/dist/src/debug/trace-writer.d.ts.map +1 -0
- package/dist/src/debug/trace-writer.js +658 -0
- package/dist/src/debug/trace-writer.js.map +1 -0
- package/dist/src/debug/trace.d.ts +10 -10
- package/dist/src/debug/trace.d.ts.map +1 -1
- package/dist/src/debug/trace.js +23 -20
- package/dist/src/debug/trace.js.map +1 -1
- package/dist/src/devtools/devtools-protocol.d.ts +318 -161
- package/dist/src/devtools/devtools-protocol.d.ts.map +1 -1
- package/dist/src/devtools/devtools-server.browser.d.ts +0 -5
- package/dist/src/devtools/devtools-server.browser.d.ts.map +1 -1
- package/dist/src/devtools/devtools-server.browser.js +0 -3
- package/dist/src/devtools/devtools-server.browser.js.map +1 -1
- package/dist/src/devtools/devtools-server.d.ts +0 -6
- package/dist/src/devtools/devtools-server.d.ts.map +1 -1
- package/dist/src/devtools/devtools-server.js +212 -24
- package/dist/src/devtools/devtools-server.js.map +1 -1
- package/dist/src/devtools/devtools-transport.d.ts +2 -2
- package/dist/src/devtools/devtools-transport.d.ts.map +1 -1
- package/dist/src/devtools/devtools-transport.js +2 -2
- package/dist/src/devtools/devtools-transport.js.map +1 -1
- package/dist/src/devtools-entry.browser.d.ts +1 -1
- package/dist/src/devtools-entry.browser.d.ts.map +1 -1
- package/dist/src/devtools-entry.browser.js.map +1 -1
- package/dist/src/devtools-entry.d.ts +1 -1
- package/dist/src/devtools-entry.d.ts.map +1 -1
- package/dist/src/devtools-entry.js.map +1 -1
- package/dist/src/diagnostics.d.ts.map +1 -1
- package/dist/src/diagnostics.js +5 -5
- package/dist/src/diagnostics.js.map +1 -1
- package/dist/src/reactivity.d.ts +13 -2
- package/dist/src/reactivity.d.ts.map +1 -1
- package/dist/src/reactivity.js +96 -13
- package/dist/src/reactivity.js.map +1 -1
- package/dist/src/render.d.ts.map +1 -1
- package/dist/src/render.js +84 -30
- package/dist/src/render.js.map +1 -1
- package/dist/src/scheduler.d.ts +5 -0
- package/dist/src/scheduler.d.ts.map +1 -1
- package/dist/src/scheduler.js +94 -23
- package/dist/src/scheduler.js.map +1 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +11 -5
- package/dist/src/utils.js.map +1 -1
- package/dist/testing/devtools-utils.d.ts +12 -3
- package/dist/testing/devtools-utils.d.ts.map +1 -1
- package/dist/testing/devtools-utils.js +26 -4
- package/dist/testing/devtools-utils.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/binder.ts +47 -38
- package/src/components/For.tsx +14 -10
- package/src/components/List.tsx +7 -4
- package/src/components/Switch.tsx +11 -7
- package/src/debug/diagnostics.test.tsx +3 -2
- package/src/debug/effects.test.tsx +248 -36
- package/src/debug/effects.ts +276 -62
- package/src/debug/files.test.tsx +15 -35
- package/src/debug/files.ts +11 -11
- package/src/debug/index.ts +4 -0
- package/src/debug/message-format.test.tsx +759 -0
- package/src/debug/render-tree-orphans.test.tsx +344 -0
- package/src/debug/render.test.tsx +96 -118
- package/src/debug/render.ts +183 -124
- package/src/debug/symbols.test.tsx +19 -20
- package/src/debug/symbols.ts +106 -23
- package/src/debug/trace-writer.ts +969 -0
- package/src/debug/trace.ts +25 -28
- package/src/devtools/devtools-protocol.ts +361 -176
- package/src/devtools/devtools-server.browser.ts +0 -9
- package/src/devtools/devtools-server.ts +210 -32
- package/src/devtools/devtools-transport.ts +4 -4
- package/src/devtools-entry.browser.ts +11 -15
- package/src/devtools-entry.ts +9 -15
- package/src/diagnostics.ts +14 -5
- package/src/reactivity.ts +113 -17
- package/src/render.ts +104 -30
- package/src/scheduler.ts +145 -26
- package/src/utils.tsx +7 -4
- package/temp/api.json +142 -20
- package/testing/devtools-utils.ts +46 -4
package/package.json
CHANGED
package/src/binder.ts
CHANGED
|
@@ -643,54 +643,63 @@ export function createOutputBinder(options: BinderOptions = {}): Binder {
|
|
|
643
643
|
}
|
|
644
644
|
|
|
645
645
|
function notifySymbolCreated(symbol: OutputSymbol): void {
|
|
646
|
-
effect<Refkey[]>(
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
if (oldRefkeys) {
|
|
655
|
-
for (const refkey of oldRefkeys) {
|
|
656
|
-
if (!symbol.refkeys.includes(refkey)) {
|
|
657
|
-
// remove the old refkey from the known declarations
|
|
658
|
-
knownDeclarations.delete(refkey);
|
|
646
|
+
effect<Refkey[]>(
|
|
647
|
+
(oldRefkeys) => {
|
|
648
|
+
if (symbol.refkeys) {
|
|
649
|
+
debug.trace(
|
|
650
|
+
TracePhase.resolve.pending,
|
|
651
|
+
() => `Notifying resolutions for ${formatRefkeys(symbol.refkeys)}.`,
|
|
652
|
+
);
|
|
653
|
+
}
|
|
659
654
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
655
|
+
if (oldRefkeys) {
|
|
656
|
+
for (const refkey of oldRefkeys) {
|
|
657
|
+
if (!symbol.refkeys.includes(refkey)) {
|
|
658
|
+
// remove the old refkey from the known declarations
|
|
659
|
+
knownDeclarations.delete(refkey);
|
|
660
|
+
|
|
661
|
+
// reset any waiting declarations
|
|
662
|
+
if (waitingDeclarations.has(refkey)) {
|
|
663
|
+
const signal = waitingDeclarations.get(refkey)!;
|
|
664
|
+
signal.value = undefined;
|
|
665
|
+
}
|
|
664
666
|
}
|
|
665
667
|
}
|
|
666
668
|
}
|
|
667
|
-
}
|
|
668
669
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
670
|
+
for (const refkey of symbol.refkeys) {
|
|
671
|
+
// notify those waiting for this refkey
|
|
672
|
+
knownDeclarations.set(refkey, symbol);
|
|
673
|
+
if (waitingDeclarations.has(refkey)) {
|
|
674
|
+
const signal = waitingDeclarations.get(refkey)!;
|
|
675
|
+
signal.value = symbol;
|
|
676
|
+
}
|
|
676
677
|
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
678
|
+
const scope = symbol.scope;
|
|
679
|
+
if (!scope) {
|
|
680
|
+
continue;
|
|
681
|
+
}
|
|
681
682
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
683
|
+
// notify those waiting for this symbol name
|
|
684
|
+
const waitingScope = waitingSymbolNames.get(scope);
|
|
685
|
+
if (waitingScope) {
|
|
686
|
+
const waitingName = waitingScope.get(symbol.name);
|
|
687
|
+
if (waitingName) {
|
|
688
|
+
waitingName.value = symbol;
|
|
689
|
+
}
|
|
688
690
|
}
|
|
689
691
|
}
|
|
690
|
-
}
|
|
691
692
|
|
|
692
|
-
|
|
693
|
-
|
|
693
|
+
return [...symbol.refkeys];
|
|
694
|
+
},
|
|
695
|
+
undefined,
|
|
696
|
+
{
|
|
697
|
+
debug: {
|
|
698
|
+
name: "binder:notifySymbolCreated",
|
|
699
|
+
type: "binder",
|
|
700
|
+
},
|
|
701
|
+
},
|
|
702
|
+
);
|
|
694
703
|
}
|
|
695
704
|
}
|
|
696
705
|
|
package/src/components/For.tsx
CHANGED
|
@@ -80,15 +80,19 @@ export function For<
|
|
|
80
80
|
const cb = props.children;
|
|
81
81
|
const options = baseListPropsToMapJoinArgs(props);
|
|
82
82
|
options.skipFalsy = props.skipFalsy;
|
|
83
|
-
return memo(
|
|
84
|
-
|
|
83
|
+
return memo(
|
|
84
|
+
() => {
|
|
85
|
+
const maybeRef = props.each;
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
87
|
+
return (mapJoin as any)(
|
|
88
|
+
typeof maybeRef === "function" ? maybeRef : (
|
|
89
|
+
() => (isRef(maybeRef) ? maybeRef.value : maybeRef)
|
|
90
|
+
),
|
|
91
|
+
cb,
|
|
92
|
+
options,
|
|
93
|
+
);
|
|
94
|
+
},
|
|
95
|
+
undefined,
|
|
96
|
+
"For",
|
|
97
|
+
);
|
|
94
98
|
}
|
package/src/components/List.tsx
CHANGED
|
@@ -57,10 +57,13 @@ export interface ListProps extends BaseListProps {
|
|
|
57
57
|
*/
|
|
58
58
|
export function List(props: ListProps) {
|
|
59
59
|
const [rest, forProps] = splitProps(props, ["children"]);
|
|
60
|
-
const resolvedChildren = memo(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
const resolvedChildren = memo(
|
|
61
|
+
() =>
|
|
62
|
+
childrenArray(() => rest.children, {
|
|
63
|
+
preserveFragments: true,
|
|
64
|
+
}),
|
|
65
|
+
undefined,
|
|
66
|
+
"List:children",
|
|
64
67
|
);
|
|
65
68
|
return (
|
|
66
69
|
<For each={resolvedChildren} {...forProps} skipFalsy>
|
|
@@ -27,15 +27,19 @@ export function Switch(props: SwitchProps) {
|
|
|
27
27
|
const children = childrenArray(() => props.children);
|
|
28
28
|
const matches = findKeyedChildren(children, matchTag);
|
|
29
29
|
|
|
30
|
-
return memo(
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
return memo(
|
|
31
|
+
() => {
|
|
32
|
+
for (const match of matches) {
|
|
33
|
+
if (match.props.when || match.props.else) {
|
|
34
|
+
return match.props.children;
|
|
35
|
+
}
|
|
34
36
|
}
|
|
35
|
-
}
|
|
36
37
|
|
|
37
|
-
|
|
38
|
-
|
|
38
|
+
return undefined;
|
|
39
|
+
},
|
|
40
|
+
undefined,
|
|
41
|
+
"Switch",
|
|
42
|
+
);
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
export interface MatchProps {
|
|
@@ -34,7 +34,7 @@ afterEach(async () => {
|
|
|
34
34
|
});
|
|
35
35
|
|
|
36
36
|
it("emits diagnostics:report messages", async () => {
|
|
37
|
-
const collector = createMessageCollector(socket!);
|
|
37
|
+
const collector = await createMessageCollector(socket!);
|
|
38
38
|
const diagnostics = new DiagnosticsCollector();
|
|
39
39
|
|
|
40
40
|
diagnostics.emit({ message: "Test diagnostic", severity: "warning" });
|
|
@@ -50,6 +50,7 @@ it("emits diagnostics:report messages", async () => {
|
|
|
50
50
|
|
|
51
51
|
expect(diagnosticsMessages[0]).toMatchObject({
|
|
52
52
|
type: "diagnostics:report",
|
|
53
|
-
|
|
53
|
+
message: "Test diagnostic",
|
|
54
|
+
severity: "warning",
|
|
54
55
|
});
|
|
55
56
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { shallowReactive } from "@vue/reactivity";
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
3
|
import WebSocket from "ws";
|
|
3
4
|
import {
|
|
4
5
|
createMessageCollector,
|
|
@@ -9,8 +10,9 @@ import {
|
|
|
9
10
|
enableDevtools,
|
|
10
11
|
resetDevtoolsServerForTests,
|
|
11
12
|
} from "../devtools/devtools-server.js";
|
|
12
|
-
import { effect, ref } from "../reactivity.js";
|
|
13
|
+
import { effect, memo, ref } from "../reactivity.js";
|
|
13
14
|
import { renderAsync } from "../render.js";
|
|
15
|
+
import { flushJobsAsync } from "../scheduler.js";
|
|
14
16
|
import { debug } from "./index.js";
|
|
15
17
|
|
|
16
18
|
let socket: WebSocket | undefined;
|
|
@@ -36,54 +38,264 @@ afterEach(async () => {
|
|
|
36
38
|
debug.effect.reset();
|
|
37
39
|
});
|
|
38
40
|
|
|
39
|
-
it("emits effect, ref,
|
|
40
|
-
const collector = createMessageCollector(socket!);
|
|
41
|
+
it("emits effect, ref, and track messages", async () => {
|
|
42
|
+
const collector = await createMessageCollector(socket!);
|
|
41
43
|
const r1 = ref(0);
|
|
44
|
+
const r2 = ref(1);
|
|
42
45
|
|
|
43
|
-
// Create an effect that reads r1.
|
|
44
|
-
let observed = 0;
|
|
45
46
|
effect(() => {
|
|
46
|
-
|
|
47
|
+
r1.value = r2.value + 1;
|
|
47
48
|
});
|
|
48
49
|
|
|
49
|
-
// Mutate r1 to trigger the effect.
|
|
50
|
-
r1.value = 42;
|
|
51
|
-
|
|
52
50
|
await renderAsync(<Output>{"ok"}</Output>);
|
|
53
51
|
|
|
54
52
|
const messages = await collector.waitForRender();
|
|
55
53
|
const effectsMessages = filterEffectsMessages(messages);
|
|
56
54
|
collector.stop();
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
expect(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
expect(
|
|
69
|
-
|
|
56
|
+
expect(effectsMessages[0]).toMatchObject({
|
|
57
|
+
type: "ref:added",
|
|
58
|
+
id: expect.any(Number),
|
|
59
|
+
kind: "ref",
|
|
60
|
+
});
|
|
61
|
+
expect(effectsMessages[1]).toMatchObject({
|
|
62
|
+
type: "ref:added",
|
|
63
|
+
id: expect.any(Number),
|
|
64
|
+
kind: "ref",
|
|
65
|
+
});
|
|
66
|
+
expect(effectsMessages[2]).toMatchObject({
|
|
67
|
+
type: "effect:added",
|
|
68
|
+
id: expect.any(Number),
|
|
69
|
+
});
|
|
70
|
+
expect(effectsMessages[3]).toMatchObject({
|
|
71
|
+
type: "edge:track",
|
|
72
|
+
effect_id: expect.any(Number),
|
|
73
|
+
ref_id: expect.any(Number),
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe("unified trigger edges", () => {
|
|
78
|
+
it("ref trigger records consumer and producer (caused_by)", async () => {
|
|
79
|
+
const collector = await createMessageCollector(socket!);
|
|
80
|
+
const r = ref(0);
|
|
81
|
+
|
|
82
|
+
// Producer effect writes to the ref
|
|
83
|
+
const producerName = "test-producer";
|
|
84
|
+
effect(
|
|
85
|
+
() => {
|
|
86
|
+
r.value = 42;
|
|
87
|
+
},
|
|
88
|
+
undefined,
|
|
89
|
+
{ debug: { name: producerName } },
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Consumer effect reads the ref
|
|
93
|
+
const consumerName = "test-consumer";
|
|
94
|
+
effect(
|
|
95
|
+
() => {
|
|
96
|
+
void r.value;
|
|
97
|
+
},
|
|
98
|
+
undefined,
|
|
99
|
+
{ debug: { name: consumerName } },
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
await renderAsync(<Output>{"ok"}</Output>);
|
|
103
|
+
const renderMessages = await collector.waitForRender();
|
|
104
|
+
|
|
105
|
+
// Trigger the consumer by changing the ref
|
|
106
|
+
r.value = 99;
|
|
107
|
+
await flushJobsAsync();
|
|
108
|
+
const flushMessages = await collector.waitForFlush();
|
|
109
|
+
collector.stop();
|
|
110
|
+
|
|
111
|
+
// Find the trigger edge on the consumer
|
|
112
|
+
const triggerEdges = flushMessages.filter(
|
|
113
|
+
(m: any) => m.type === "edge:trigger" && m.ref_id !== undefined,
|
|
114
|
+
);
|
|
115
|
+
expect(triggerEdges.length).toBeGreaterThan(0);
|
|
116
|
+
|
|
117
|
+
// The trigger edge should be on the consumer effect
|
|
118
|
+
const effectsMessages = filterEffectsMessages(renderMessages);
|
|
119
|
+
const consumerEffect = effectsMessages.find(
|
|
120
|
+
(m: any) => m.type === "effect:added" && m.name === consumerName,
|
|
121
|
+
);
|
|
122
|
+
expect(consumerEffect).toBeDefined();
|
|
123
|
+
|
|
124
|
+
const consumerTrigger = triggerEdges.find(
|
|
125
|
+
(m: any) => m.effect_id === consumerEffect!.id,
|
|
126
|
+
);
|
|
127
|
+
expect(consumerTrigger).toBeDefined();
|
|
128
|
+
// caused_by is null since the mutation happens outside any effect (top-level r.value = 99)
|
|
129
|
+
expect(consumerTrigger!.caused_by).toBeNull();
|
|
70
130
|
});
|
|
71
|
-
|
|
72
|
-
|
|
131
|
+
|
|
132
|
+
it("reactive object trigger records consumer and producer", async () => {
|
|
133
|
+
const collector = await createMessageCollector(socket!);
|
|
134
|
+
const map = shallowReactive(new Map<string, number>());
|
|
135
|
+
|
|
136
|
+
// Consumer: memo reads the map
|
|
137
|
+
const sum = memo(() => {
|
|
138
|
+
let total = 0;
|
|
139
|
+
for (const v of map.values()) total += v;
|
|
140
|
+
return total;
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Producer: effect mutates the map
|
|
144
|
+
effect(
|
|
145
|
+
() => {
|
|
146
|
+
const _s = sum();
|
|
147
|
+
map.set("key", 42);
|
|
148
|
+
},
|
|
149
|
+
undefined,
|
|
150
|
+
{ debug: { name: "producer-effect" } },
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
await renderAsync(<Output>{"ok"}</Output>);
|
|
154
|
+
|
|
155
|
+
const messages = await collector.waitForRender();
|
|
156
|
+
const effectsMessages = filterEffectsMessages(messages);
|
|
157
|
+
collector.stop();
|
|
158
|
+
|
|
159
|
+
// Find trigger edges for the reactive map
|
|
160
|
+
const triggerEdges = effectsMessages.filter(
|
|
161
|
+
(m: any) => m.type === "edge:trigger" && m.target_id !== undefined,
|
|
162
|
+
);
|
|
163
|
+
expect(triggerEdges.length).toBeGreaterThan(0);
|
|
164
|
+
|
|
165
|
+
// The trigger should have caused_by (the producer effect)
|
|
166
|
+
const producerEffect = effectsMessages.find(
|
|
167
|
+
(m: any) => m.type === "effect:added" && m.name === "producer-effect",
|
|
168
|
+
);
|
|
169
|
+
expect(producerEffect).toBeDefined();
|
|
170
|
+
|
|
171
|
+
const withCause = triggerEdges.filter(
|
|
172
|
+
(m: any) => m.caused_by === producerEffect!.id,
|
|
173
|
+
);
|
|
174
|
+
expect(withCause.length).toBeGreaterThan(0);
|
|
175
|
+
expect(withCause[0]).toMatchObject({
|
|
176
|
+
type: "edge:trigger",
|
|
177
|
+
effect_id: expect.any(Number),
|
|
178
|
+
caused_by: producerEffect!.id,
|
|
179
|
+
target_id: expect.any(Number),
|
|
180
|
+
});
|
|
73
181
|
});
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
182
|
+
|
|
183
|
+
it("trigger outside any effect has no caused_by", async () => {
|
|
184
|
+
const collector = await createMessageCollector(socket!);
|
|
185
|
+
const r = ref(0);
|
|
186
|
+
|
|
187
|
+
// Consumer effect
|
|
188
|
+
effect(() => {
|
|
189
|
+
void r.value;
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
await renderAsync(<Output>{"ok"}</Output>);
|
|
193
|
+
await collector.waitForRender();
|
|
194
|
+
|
|
195
|
+
// Trigger from top-level (outside any effect)
|
|
196
|
+
r.value = 1;
|
|
197
|
+
await flushJobsAsync();
|
|
198
|
+
const flushMessages = await collector.waitForFlush();
|
|
199
|
+
collector.stop();
|
|
200
|
+
|
|
201
|
+
const triggerEdges = flushMessages.filter(
|
|
202
|
+
(m: any) => m.type === "edge:trigger",
|
|
203
|
+
);
|
|
204
|
+
expect(triggerEdges.length).toBeGreaterThan(0);
|
|
205
|
+
// caused_by should be null (no producer effect)
|
|
206
|
+
expect(triggerEdges[0].caused_by).toBeNull();
|
|
80
207
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
208
|
+
|
|
209
|
+
it("multiple consumers of same ref get separate trigger edges", async () => {
|
|
210
|
+
const collector = await createMessageCollector(socket!);
|
|
211
|
+
const r = ref(0);
|
|
212
|
+
|
|
213
|
+
// Two consumer effects
|
|
214
|
+
effect(
|
|
215
|
+
() => {
|
|
216
|
+
void r.value;
|
|
217
|
+
},
|
|
218
|
+
undefined,
|
|
219
|
+
{ debug: { name: "consumer-a" } },
|
|
220
|
+
);
|
|
221
|
+
effect(
|
|
222
|
+
() => {
|
|
223
|
+
void r.value;
|
|
224
|
+
},
|
|
225
|
+
undefined,
|
|
226
|
+
{ debug: { name: "consumer-b" } },
|
|
227
|
+
);
|
|
228
|
+
|
|
229
|
+
await renderAsync(<Output>{"ok"}</Output>);
|
|
230
|
+
const renderMessages = await collector.waitForRender();
|
|
231
|
+
|
|
232
|
+
// Find the effect IDs
|
|
233
|
+
const effectsMessages = filterEffectsMessages(renderMessages);
|
|
234
|
+
const consumerA = effectsMessages.find(
|
|
235
|
+
(m: any) => m.type === "effect:added" && m.name === "consumer-a",
|
|
236
|
+
);
|
|
237
|
+
const consumerB = effectsMessages.find(
|
|
238
|
+
(m: any) => m.type === "effect:added" && m.name === "consumer-b",
|
|
239
|
+
);
|
|
240
|
+
expect(consumerA).toBeDefined();
|
|
241
|
+
expect(consumerB).toBeDefined();
|
|
242
|
+
|
|
243
|
+
// Trigger both by changing the ref
|
|
244
|
+
r.value = 1;
|
|
245
|
+
await flushJobsAsync();
|
|
246
|
+
const flushMessages = await collector.waitForFlush();
|
|
247
|
+
collector.stop();
|
|
248
|
+
|
|
249
|
+
const triggerEdges = flushMessages.filter(
|
|
250
|
+
(m: any) => m.type === "edge:trigger",
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
// Each consumer gets its own trigger edge
|
|
254
|
+
const aTrigger = triggerEdges.find(
|
|
255
|
+
(m: any) => m.effect_id === consumerA!.id,
|
|
256
|
+
);
|
|
257
|
+
const bTrigger = triggerEdges.find(
|
|
258
|
+
(m: any) => m.effect_id === consumerB!.id,
|
|
259
|
+
);
|
|
260
|
+
expect(aTrigger).toBeDefined();
|
|
261
|
+
expect(bTrigger).toBeDefined();
|
|
262
|
+
// Both reference the same ref
|
|
263
|
+
expect(aTrigger!.ref_id).toBe(bTrigger!.ref_id);
|
|
86
264
|
});
|
|
87
265
|
|
|
88
|
-
|
|
266
|
+
it("no triggered-by or duplicate trigger edge types exist", async () => {
|
|
267
|
+
const collector = await createMessageCollector(socket!);
|
|
268
|
+
const r = ref(0);
|
|
269
|
+
const map = shallowReactive(new Map<string, number>());
|
|
270
|
+
|
|
271
|
+
effect(() => {
|
|
272
|
+
void r.value;
|
|
273
|
+
for (const _v of map.values()) {
|
|
274
|
+
/* track */
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
effect(() => {
|
|
279
|
+
r.value = 1;
|
|
280
|
+
map.set("x", 1);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
await renderAsync(<Output>{"ok"}</Output>);
|
|
284
|
+
const messages = await collector.waitForRender();
|
|
285
|
+
collector.stop();
|
|
286
|
+
|
|
287
|
+
// No edge:triggered-by messages should exist
|
|
288
|
+
const triggeredBy = messages.filter(
|
|
289
|
+
(m: any) => m.type === "edge:triggered-by",
|
|
290
|
+
);
|
|
291
|
+
expect(triggeredBy.length).toBe(0);
|
|
292
|
+
|
|
293
|
+
// All edge types should be edge:track or edge:trigger only
|
|
294
|
+
const edges = messages.filter(
|
|
295
|
+
(m: any) => typeof m.type === "string" && m.type.startsWith("edge:"),
|
|
296
|
+
);
|
|
297
|
+
for (const edge of edges) {
|
|
298
|
+
expect(["edge:track", "edge:trigger"]).toContain(edge.type);
|
|
299
|
+
}
|
|
300
|
+
});
|
|
89
301
|
});
|