@alloy-js/core 0.23.0-dev.8 → 0.23.0-dev.9
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/src/components/Prose.js +2 -2
- package/dist/src/components/Prose.js.map +1 -1
- package/dist/src/components/Scope.d.ts.map +1 -1
- package/dist/src/components/Scope.js +2 -0
- package/dist/src/components/Scope.js.map +1 -1
- package/dist/src/components/SourceDirectory.d.ts.map +1 -1
- package/dist/src/components/SourceDirectory.js +1 -2
- package/dist/src/components/SourceDirectory.js.map +1 -1
- package/dist/src/content-slot.js +2 -2
- package/dist/src/content-slot.js.map +1 -1
- package/dist/src/context.js +2 -2
- package/dist/src/context.js.map +1 -1
- package/dist/src/debug/effects.d.ts +4 -0
- package/dist/src/debug/effects.d.ts.map +1 -1
- package/dist/src/debug/effects.js.map +1 -1
- package/dist/src/debug/effects.test.js +22 -24
- package/dist/src/debug/effects.test.js.map +1 -1
- package/dist/src/debug/index.d.ts +2 -1
- package/dist/src/debug/index.d.ts.map +1 -1
- package/dist/src/debug/index.js +2 -1
- package/dist/src/debug/index.js.map +1 -1
- package/dist/src/debug/symbols.d.ts +6 -0
- package/dist/src/debug/symbols.d.ts.map +1 -1
- package/dist/src/debug/symbols.js +9 -0
- package/dist/src/debug/symbols.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/reactive-union-set.d.ts.map +1 -1
- package/dist/src/reactive-union-set.js +13 -3
- package/dist/src/reactive-union-set.js.map +1 -1
- package/dist/src/reactivity.d.ts +34 -6
- package/dist/src/reactivity.d.ts.map +1 -1
- package/dist/src/reactivity.js +161 -123
- package/dist/src/reactivity.js.map +1 -1
- package/dist/src/render-stack.d.ts +1 -0
- package/dist/src/render-stack.d.ts.map +1 -1
- package/dist/src/render-stack.js +4 -0
- package/dist/src/render-stack.js.map +1 -1
- package/dist/src/render.d.ts.map +1 -1
- package/dist/src/render.js +15 -13
- 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 +24 -1
- package/dist/src/scheduler.js.map +1 -1
- package/dist/src/symbols/output-scope.d.ts.map +1 -1
- package/dist/src/symbols/output-scope.js +2 -2
- package/dist/src/symbols/output-scope.js.map +1 -1
- package/dist/src/symbols/output-symbol.d.ts.map +1 -1
- package/dist/src/symbols/output-symbol.js +2 -2
- package/dist/src/symbols/output-symbol.js.map +1 -1
- package/dist/src/symbols/symbol-flow.d.ts.map +1 -1
- package/dist/src/symbols/symbol-flow.js +2 -2
- package/dist/src/symbols/symbol-flow.js.map +1 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js +2 -5
- package/dist/src/utils.js.map +1 -1
- package/dist/test/lazy-isempty.test.d.ts +2 -0
- package/dist/test/lazy-isempty.test.d.ts.map +1 -0
- package/dist/test/lazy-isempty.test.js +89 -0
- package/dist/test/lazy-isempty.test.js.map +1 -0
- package/dist/test/reactive-union-set-disposers.test.d.ts +2 -0
- package/dist/test/reactive-union-set-disposers.test.d.ts.map +1 -0
- package/dist/test/reactive-union-set-disposers.test.js +98 -0
- package/dist/test/reactive-union-set-disposers.test.js.map +1 -0
- package/dist/test/reactivity/shallow-reactive.test.d.ts +2 -0
- package/dist/test/reactivity/shallow-reactive.test.d.ts.map +1 -0
- package/dist/test/reactivity/shallow-reactive.test.js +52 -0
- package/dist/test/reactivity/shallow-reactive.test.js.map +1 -0
- package/dist/test/scheduler-extended.test.d.ts +2 -0
- package/dist/test/scheduler-extended.test.d.ts.map +1 -0
- package/dist/test/scheduler-extended.test.js +96 -0
- package/dist/test/scheduler-extended.test.js.map +1 -0
- package/dist/test/scheduler.test.d.ts +2 -0
- package/dist/test/scheduler.test.d.ts.map +1 -0
- package/dist/test/scheduler.test.js +46 -0
- package/dist/test/scheduler.test.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/components/Prose.tsx +1 -1
- package/src/components/Scope.tsx +2 -0
- package/src/components/SourceDirectory.tsx +1 -2
- package/src/content-slot.tsx +2 -2
- package/src/context.ts +3 -3
- package/src/debug/effects.test.tsx +24 -31
- package/src/debug/effects.ts +4 -0
- package/src/debug/index.ts +2 -0
- package/src/debug/symbols.ts +9 -0
- package/src/index.ts +0 -1
- package/src/reactive-union-set.ts +14 -3
- package/src/reactivity.ts +189 -130
- package/src/render-stack.ts +5 -0
- package/src/render.ts +16 -14
- package/src/scheduler.ts +25 -1
- package/src/symbols/output-scope.ts +1 -2
- package/src/symbols/output-symbol.ts +1 -2
- package/src/symbols/symbol-flow.ts +8 -2
- package/src/utils.tsx +2 -4
- package/temp/api.json +425 -14
- package/test/lazy-isempty.test.tsx +106 -0
- package/test/reactive-union-set-disposers.test.tsx +112 -0
- package/test/reactivity/shallow-reactive.test.tsx +56 -0
- package/test/scheduler-extended.test.tsx +122 -0
- package/test/scheduler.test.tsx +50 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ref } from "@vue/reactivity";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { Show } from "../src/components/Show.jsx";
|
|
4
|
+
import { createContentSlot } from "../src/content-slot.jsx";
|
|
5
|
+
import { Context, ensureIsEmpty, getContext } from "../src/reactivity.js";
|
|
6
|
+
import { printTree, renderTree } from "../src/render.js";
|
|
7
|
+
import "../testing/extend-expect.js";
|
|
8
|
+
|
|
9
|
+
describe("lazy isEmpty / _lastEmpty", () => {
|
|
10
|
+
it("context starts without isEmpty ref allocated", () => {
|
|
11
|
+
let ctx: Context | null = null;
|
|
12
|
+
|
|
13
|
+
function Capture() {
|
|
14
|
+
ctx = getContext()!;
|
|
15
|
+
return "content";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
renderTree(<Capture />);
|
|
19
|
+
|
|
20
|
+
// The isEmpty ref should NOT be allocated unless someone observes it.
|
|
21
|
+
expect(ctx).not.toBeNull();
|
|
22
|
+
expect(ctx!.isEmpty).toBeUndefined();
|
|
23
|
+
expect(ctx!._lastEmpty).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("ensureIsEmpty lazily allocates the isEmpty ref", () => {
|
|
27
|
+
let ctx: Context | null = null;
|
|
28
|
+
|
|
29
|
+
function Capture() {
|
|
30
|
+
ctx = getContext()!;
|
|
31
|
+
return "content";
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
renderTree(<Capture />);
|
|
35
|
+
|
|
36
|
+
expect(ctx!.isEmpty).toBeUndefined();
|
|
37
|
+
const isEmptyRef = ensureIsEmpty(ctx!);
|
|
38
|
+
expect(ctx!.isEmpty).toBeDefined();
|
|
39
|
+
expect(isEmptyRef).toBe(ctx!.isEmpty);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("_lastEmpty is true for empty component, false for non-empty", () => {
|
|
43
|
+
let emptyCtx: Context | null = null;
|
|
44
|
+
let fullCtx: Context | null = null;
|
|
45
|
+
|
|
46
|
+
function EmptyCapture() {
|
|
47
|
+
emptyCtx = getContext()!;
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function FullCapture() {
|
|
52
|
+
fullCtx = getContext()!;
|
|
53
|
+
return "has content";
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
renderTree(
|
|
57
|
+
<>
|
|
58
|
+
<EmptyCapture />
|
|
59
|
+
<FullCapture />
|
|
60
|
+
</>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(emptyCtx!._lastEmpty).toBe(true);
|
|
64
|
+
expect(fullCtx!._lastEmpty).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("ContentSlot triggers ensureIsEmpty and tracks reactively", () => {
|
|
68
|
+
const ContentSlot = createContentSlot();
|
|
69
|
+
const showContent = ref(false);
|
|
70
|
+
|
|
71
|
+
const tree = renderTree(
|
|
72
|
+
<>
|
|
73
|
+
{ContentSlot.isEmpty && "empty"}
|
|
74
|
+
<ContentSlot>
|
|
75
|
+
<Show when={showContent.value}>content</Show>
|
|
76
|
+
</ContentSlot>
|
|
77
|
+
</>,
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
expect(printTree(tree)).toBe("empty");
|
|
81
|
+
|
|
82
|
+
showContent.value = true;
|
|
83
|
+
expect(printTree(tree)).toBe("content");
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it("propagates empty state up through parent contexts", () => {
|
|
87
|
+
const OuterSlot = createContentSlot();
|
|
88
|
+
const showContent = ref(false);
|
|
89
|
+
|
|
90
|
+
const tree = renderTree(
|
|
91
|
+
<>
|
|
92
|
+
{OuterSlot.isEmpty && "outer-empty"}
|
|
93
|
+
<OuterSlot>
|
|
94
|
+
<Show when={showContent.value}>content</Show>
|
|
95
|
+
</OuterSlot>
|
|
96
|
+
</>,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Outer should be empty initially.
|
|
100
|
+
expect(printTree(tree)).toBe("outer-empty");
|
|
101
|
+
|
|
102
|
+
// Show content — outer should become non-empty.
|
|
103
|
+
showContent.value = true;
|
|
104
|
+
expect(printTree(tree)).toBe("content");
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { reactive } from "@vue/reactivity";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { ReactiveUnionSet } from "../src/reactive-union-set.js";
|
|
4
|
+
import { effect } from "../src/reactivity.js";
|
|
5
|
+
import { flushJobs } from "../src/scheduler.js";
|
|
6
|
+
|
|
7
|
+
describe("ReactiveUnionSet: per-item disposers via addSubset", () => {
|
|
8
|
+
it("calls onDelete when item is removed from subset", () => {
|
|
9
|
+
const parentSet = new ReactiveUnionSet<string>();
|
|
10
|
+
const subset = reactive(new Set<string>());
|
|
11
|
+
const deleted: string[] = [];
|
|
12
|
+
|
|
13
|
+
parentSet.addSubset(subset, {
|
|
14
|
+
onDelete(value) {
|
|
15
|
+
deleted.push(value);
|
|
16
|
+
},
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
subset.add("a");
|
|
20
|
+
subset.add("b");
|
|
21
|
+
flushJobs();
|
|
22
|
+
expect(parentSet.has("a")).toBe(true);
|
|
23
|
+
expect(parentSet.has("b")).toBe(true);
|
|
24
|
+
|
|
25
|
+
subset.delete("a");
|
|
26
|
+
flushJobs();
|
|
27
|
+
expect(parentSet.has("a")).toBe(false);
|
|
28
|
+
expect(deleted).toContain("a");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("calls onDelete for all items when subset is cleared", () => {
|
|
32
|
+
const parentSet = new ReactiveUnionSet<string>();
|
|
33
|
+
const subset = reactive(new Set<string>());
|
|
34
|
+
const deleted: string[] = [];
|
|
35
|
+
|
|
36
|
+
parentSet.addSubset(subset, {
|
|
37
|
+
onDelete(value) {
|
|
38
|
+
deleted.push(value);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
subset.add("a");
|
|
43
|
+
subset.add("b");
|
|
44
|
+
subset.add("c");
|
|
45
|
+
flushJobs();
|
|
46
|
+
expect(parentSet.size).toBe(3);
|
|
47
|
+
|
|
48
|
+
subset.clear();
|
|
49
|
+
flushJobs();
|
|
50
|
+
expect(parentSet.size).toBe(0);
|
|
51
|
+
expect(deleted.sort()).toEqual(["a", "b", "c"]);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("disposes root scopes created by onAdd when item is removed", () => {
|
|
55
|
+
const parentSet = new ReactiveUnionSet<string>();
|
|
56
|
+
const subset = reactive(new Set<string>());
|
|
57
|
+
let disposeCount = 0;
|
|
58
|
+
|
|
59
|
+
// Use the constructor-level onAdd so items are still added to the set,
|
|
60
|
+
// plus per-subset onAdd with root scope tracking.
|
|
61
|
+
parentSet.addSubset(subset, {
|
|
62
|
+
onAdd(value) {
|
|
63
|
+
// The onAdd in addSubset wraps in root() internally.
|
|
64
|
+
// Side-effects created here are cleaned up when the item is removed.
|
|
65
|
+
effect(() => {
|
|
66
|
+
void value; // track nothing real, just proving the effect exists
|
|
67
|
+
});
|
|
68
|
+
return value;
|
|
69
|
+
},
|
|
70
|
+
onDelete() {
|
|
71
|
+
disposeCount++;
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
subset.add("x");
|
|
76
|
+
flushJobs();
|
|
77
|
+
|
|
78
|
+
// Remove item — root scope (and its effects) should be disposed.
|
|
79
|
+
subset.delete("x");
|
|
80
|
+
flushJobs();
|
|
81
|
+
expect(disposeCount).toBe(1);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("re-adding after delete triggers fresh onAdd", () => {
|
|
85
|
+
const parentSet = new ReactiveUnionSet<string>();
|
|
86
|
+
const subset = reactive(new Set<string>());
|
|
87
|
+
let addCount = 0;
|
|
88
|
+
const deleted: string[] = [];
|
|
89
|
+
|
|
90
|
+
parentSet.addSubset(subset, {
|
|
91
|
+
onAdd(value) {
|
|
92
|
+
addCount++;
|
|
93
|
+
return value;
|
|
94
|
+
},
|
|
95
|
+
onDelete(value) {
|
|
96
|
+
deleted.push(value);
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
subset.add("a");
|
|
101
|
+
flushJobs();
|
|
102
|
+
expect(addCount).toBe(1);
|
|
103
|
+
|
|
104
|
+
subset.delete("a");
|
|
105
|
+
flushJobs();
|
|
106
|
+
expect(deleted).toEqual(["a"]);
|
|
107
|
+
|
|
108
|
+
subset.add("a");
|
|
109
|
+
flushJobs();
|
|
110
|
+
expect(addCount).toBe(2);
|
|
111
|
+
});
|
|
112
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
getReactiveCreationLocation,
|
|
4
|
+
shallowReactive,
|
|
5
|
+
} from "../../src/reactivity.js";
|
|
6
|
+
|
|
7
|
+
describe("shallowReactive creation location", () => {
|
|
8
|
+
let origDebug: string | undefined;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
origDebug = process.env.ALLOY_DEBUG;
|
|
12
|
+
process.env.ALLOY_DEBUG = "1";
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
if (origDebug === undefined) {
|
|
17
|
+
delete process.env.ALLOY_DEBUG;
|
|
18
|
+
} else {
|
|
19
|
+
process.env.ALLOY_DEBUG = origDebug;
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("stores creation location keyed by raw target when debug enabled", () => {
|
|
24
|
+
const raw = { x: 1 };
|
|
25
|
+
shallowReactive(raw);
|
|
26
|
+
|
|
27
|
+
const location = getReactiveCreationLocation(raw);
|
|
28
|
+
// When ALLOY_DEBUG is set, captureSourceLocation should return something.
|
|
29
|
+
expect(location).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("does not store location when debug is disabled", () => {
|
|
33
|
+
delete process.env.ALLOY_DEBUG;
|
|
34
|
+
const raw = { y: 2 };
|
|
35
|
+
shallowReactive(raw);
|
|
36
|
+
|
|
37
|
+
const location = getReactiveCreationLocation(raw);
|
|
38
|
+
expect(location).toBeUndefined();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("location is keyed by raw target, not proxy", () => {
|
|
42
|
+
const raw = { z: 3 };
|
|
43
|
+
const proxy = shallowReactive(raw);
|
|
44
|
+
|
|
45
|
+
// Proxy and raw are different objects.
|
|
46
|
+
expect(proxy).not.toBe(raw);
|
|
47
|
+
|
|
48
|
+
// Location should be on the raw target.
|
|
49
|
+
const location = getReactiveCreationLocation(raw);
|
|
50
|
+
expect(location).toBeDefined();
|
|
51
|
+
|
|
52
|
+
// The proxy itself should NOT have a location stored (WeakMap keyed by raw).
|
|
53
|
+
// Note: Vue may unwrap the proxy to raw internally, but our code explicitly
|
|
54
|
+
// stores on `target` (the raw object passed to shallowReactive).
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ref, stop, effect as vueEffect } from "@vue/reactivity";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import {
|
|
4
|
+
flushJobs,
|
|
5
|
+
flushJobsAsync,
|
|
6
|
+
queueJob,
|
|
7
|
+
scheduler,
|
|
8
|
+
} from "../src/scheduler.js";
|
|
9
|
+
|
|
10
|
+
describe("scheduler: isJobActive additional coverage", () => {
|
|
11
|
+
it("does not run stopped effects in async flush", async () => {
|
|
12
|
+
const source = ref(0);
|
|
13
|
+
let runCount = 0;
|
|
14
|
+
|
|
15
|
+
const runner = vueEffect(
|
|
16
|
+
() => {
|
|
17
|
+
runCount++;
|
|
18
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
19
|
+
source.value;
|
|
20
|
+
},
|
|
21
|
+
{ scheduler: scheduler() },
|
|
22
|
+
);
|
|
23
|
+
expect(runCount).toBe(1);
|
|
24
|
+
|
|
25
|
+
source.value = 1;
|
|
26
|
+
stop(runner);
|
|
27
|
+
await flushJobsAsync();
|
|
28
|
+
|
|
29
|
+
expect(runCount).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("does not run stopped immediate effects", () => {
|
|
33
|
+
const source = ref(0);
|
|
34
|
+
let runCount = 0;
|
|
35
|
+
|
|
36
|
+
const runner = vueEffect(
|
|
37
|
+
() => {
|
|
38
|
+
runCount++;
|
|
39
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
40
|
+
source.value;
|
|
41
|
+
},
|
|
42
|
+
{ scheduler: scheduler(true) },
|
|
43
|
+
);
|
|
44
|
+
expect(runCount).toBe(1);
|
|
45
|
+
|
|
46
|
+
source.value = 1;
|
|
47
|
+
stop(runner);
|
|
48
|
+
flushJobs();
|
|
49
|
+
|
|
50
|
+
expect(runCount).toBe(1);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("runs effect in first flush but not after stop", () => {
|
|
54
|
+
const source = ref(0);
|
|
55
|
+
let runCount = 0;
|
|
56
|
+
|
|
57
|
+
const runner = vueEffect(
|
|
58
|
+
() => {
|
|
59
|
+
runCount++;
|
|
60
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
61
|
+
source.value;
|
|
62
|
+
},
|
|
63
|
+
{ scheduler: scheduler() },
|
|
64
|
+
);
|
|
65
|
+
expect(runCount).toBe(1);
|
|
66
|
+
|
|
67
|
+
// First flush: effect is active, should run.
|
|
68
|
+
source.value = 1;
|
|
69
|
+
flushJobs();
|
|
70
|
+
expect(runCount).toBe(2);
|
|
71
|
+
|
|
72
|
+
// Stop, then trigger again — should not run.
|
|
73
|
+
stop(runner);
|
|
74
|
+
source.value = 2;
|
|
75
|
+
flushJobs();
|
|
76
|
+
expect(runCount).toBe(2);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("skips multiple stopped effects in the same flush", () => {
|
|
80
|
+
const source = ref(0);
|
|
81
|
+
let runCountA = 0;
|
|
82
|
+
let runCountB = 0;
|
|
83
|
+
|
|
84
|
+
const runnerA = vueEffect(
|
|
85
|
+
() => {
|
|
86
|
+
runCountA++;
|
|
87
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
88
|
+
source.value;
|
|
89
|
+
},
|
|
90
|
+
{ scheduler: scheduler() },
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
const runnerB = vueEffect(
|
|
94
|
+
() => {
|
|
95
|
+
runCountB++;
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
97
|
+
source.value;
|
|
98
|
+
},
|
|
99
|
+
{ scheduler: scheduler() },
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
expect(runCountA).toBe(1);
|
|
103
|
+
expect(runCountB).toBe(1);
|
|
104
|
+
|
|
105
|
+
source.value = 1;
|
|
106
|
+
stop(runnerA);
|
|
107
|
+
stop(runnerB);
|
|
108
|
+
flushJobs();
|
|
109
|
+
|
|
110
|
+
expect(runCountA).toBe(1);
|
|
111
|
+
expect(runCountB).toBe(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("handles plain function jobs that lack flags", () => {
|
|
115
|
+
let ran = false;
|
|
116
|
+
queueJob(() => {
|
|
117
|
+
ran = true;
|
|
118
|
+
});
|
|
119
|
+
flushJobs();
|
|
120
|
+
expect(ran).toBe(true);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ref, stop, effect as vueEffect } from "@vue/reactivity";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { effect } from "../src/reactivity.js";
|
|
4
|
+
import { flushJobs, scheduler } from "../src/scheduler.js";
|
|
5
|
+
|
|
6
|
+
describe("scheduler", () => {
|
|
7
|
+
it("does not run stopped effects from the queue", () => {
|
|
8
|
+
const source = ref(0);
|
|
9
|
+
let runCount = 0;
|
|
10
|
+
|
|
11
|
+
// Create an effect that tracks source via alloy's scheduler.
|
|
12
|
+
const runner = vueEffect(
|
|
13
|
+
() => {
|
|
14
|
+
runCount++;
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
16
|
+
source.value;
|
|
17
|
+
},
|
|
18
|
+
{ scheduler: scheduler() },
|
|
19
|
+
);
|
|
20
|
+
expect(runCount).toBe(1);
|
|
21
|
+
|
|
22
|
+
// Trigger the effect — it gets queued in the scheduler.
|
|
23
|
+
source.value = 1;
|
|
24
|
+
// Stop the effect before flushing.
|
|
25
|
+
stop(runner);
|
|
26
|
+
flushJobs();
|
|
27
|
+
|
|
28
|
+
// The stopped effect must not have run.
|
|
29
|
+
expect(runCount).toBe(1);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("runs active effects normally", () => {
|
|
33
|
+
const source = ref(0);
|
|
34
|
+
let runCount = 0;
|
|
35
|
+
|
|
36
|
+
effect(() => {
|
|
37
|
+
runCount++;
|
|
38
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-expressions
|
|
39
|
+
source.value;
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
expect(runCount).toBe(1);
|
|
43
|
+
source.value = 1;
|
|
44
|
+
flushJobs();
|
|
45
|
+
expect(runCount).toBe(2);
|
|
46
|
+
source.value = 2;
|
|
47
|
+
flushJobs();
|
|
48
|
+
expect(runCount).toBe(3);
|
|
49
|
+
});
|
|
50
|
+
});
|