Skip to content

Commit 717180d

Browse files
authored
Merge pull request #234 from preactjs/more-tests
Add tests for core & refactor addDependency for increased code coverage
2 parents dafa95b + 8c3a94a commit 717180d

File tree

2 files changed

+116
-17
lines changed

2 files changed

+116
-17
lines changed

packages/core/src/index.ts

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -132,25 +132,21 @@ function addDependency(signal: Signal): Node | undefined {
132132
}
133133
return node;
134134
} else if (node._version === -1) {
135-
// `signal` is an existing dependency from a previous evaluation. Reuse the dependency
136-
// node and move it to the front of the evaluation context's dependency list.
135+
// `signal` is an existing dependency from a previous evaluation. Reuse it.
137136
node._version = 0;
138137

139-
const head = evalContext._sources;
140-
if (node !== head) {
141-
const prev = node._prevSource;
142-
const next = node._nextSource;
143-
if (prev !== undefined) {
144-
prev._nextSource = next;
145-
}
146-
if (next !== undefined) {
147-
next._prevSource = prev;
148-
}
149-
if (head !== undefined) {
150-
head._prevSource = node;
138+
// If `node` is not already the current head of the dependency list (i.e.
139+
// there is a previous node in the list), then make `node` the new head.
140+
if (node._prevSource !== undefined) {
141+
node._prevSource._nextSource = node._nextSource;
142+
if (node._nextSource !== undefined) {
143+
node._nextSource._prevSource = node._prevSource;
151144
}
152145
node._prevSource = undefined;
153-
node._nextSource = head;
146+
node._nextSource = evalContext._sources;
147+
// evalCotext._sources must be !== undefined (and !== node), because
148+
// `node` was originally pointing to some previous node.
149+
evalContext._sources!._prevSource = node;
154150
evalContext._sources = node;
155151
}
156152

packages/core/test/signal.test.tsx

Lines changed: 105 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,28 @@ describe("signal", () => {
2929
expect(a + b).to.equal(3);
3030
});
3131

32+
it("should notify other listeners of changes after one listener is disposed", () => {
33+
const s = signal(0);
34+
const spy1 = sinon.spy(() => s.value);
35+
const spy2 = sinon.spy(() => s.value);
36+
const spy3 = sinon.spy(() => s.value);
37+
38+
effect(spy1);
39+
const dispose = effect(spy2);
40+
effect(spy3);
41+
42+
expect(spy1).to.be.calledOnce;
43+
expect(spy2).to.be.calledOnce;
44+
expect(spy3).to.be.calledOnce;
45+
46+
dispose();
47+
48+
s.value = 1;
49+
expect(spy1).to.be.calledTwice;
50+
expect(spy2).to.be.calledOnce;
51+
expect(spy3).to.be.calledTwice;
52+
});
53+
3254
describe(".peek()", () => {
3355
it("should get value", () => {
3456
const s = signal(1);
@@ -865,7 +887,7 @@ describe("computed()", () => {
865887
expect(() => effect(() => a.value)).to.not.throw();
866888
});
867889

868-
it("should store failures and recompute only after a dependency changes", () => {
890+
it("should store thrown errors and recompute only after a dependency changes", () => {
869891
const a = signal(0);
870892
const spy = sinon.spy(() => {
871893
a.value;
@@ -880,6 +902,39 @@ describe("computed()", () => {
880902
expect(spy).to.be.calledTwice;
881903
});
882904

905+
it("should store thrown non-errors and recompute only after a dependency changes", () => {
906+
const a = signal(0);
907+
const spy = sinon.spy();
908+
const c = computed(() => {
909+
a.value;
910+
spy();
911+
throw undefined;
912+
});
913+
914+
try {
915+
c.value;
916+
expect.fail();
917+
} catch (err) {
918+
expect(err).to.be.undefined;
919+
}
920+
try {
921+
c.value;
922+
expect.fail();
923+
} catch (err) {
924+
expect(err).to.be.undefined;
925+
}
926+
expect(spy).to.be.calledOnce;
927+
928+
a.value = 1;
929+
try {
930+
c.value;
931+
expect.fail();
932+
} catch (err) {
933+
expect(err).to.be.undefined;
934+
}
935+
expect(spy).to.be.calledTwice;
936+
});
937+
883938
it("should conditionally unsubscribe from signals", () => {
884939
const a = signal("a");
885940
const b = signal("b");
@@ -967,6 +1022,30 @@ describe("computed()", () => {
9671022
expect(c.value).to.equal(1);
9681023
});
9691024

1025+
it("should propagate notification to other listeners after one listener is disposed", () => {
1026+
const s = signal(0);
1027+
const c = computed(() => s.value);
1028+
1029+
const spy1 = sinon.spy(() => c.value);
1030+
const spy2 = sinon.spy(() => c.value);
1031+
const spy3 = sinon.spy(() => c.value);
1032+
1033+
effect(spy1);
1034+
const dispose = effect(spy2);
1035+
effect(spy3);
1036+
1037+
expect(spy1).to.be.calledOnce;
1038+
expect(spy2).to.be.calledOnce;
1039+
expect(spy3).to.be.calledOnce;
1040+
1041+
dispose();
1042+
1043+
s.value = 1;
1044+
expect(spy1).to.be.calledTwice;
1045+
expect(spy2).to.be.calledOnce;
1046+
expect(spy3).to.be.calledTwice;
1047+
});
1048+
9701049
it("should not recompute dependencies out of order", () => {
9711050
const a = signal(1);
9721051
const b = signal(1);
@@ -1042,6 +1121,19 @@ describe("computed()", () => {
10421121
expect(b.peek()).to.equal(2);
10431122
});
10441123

1124+
it("should detect simple dependency cycles", () => {
1125+
const a: Signal = computed(() => a.peek());
1126+
expect(() => a.peek()).to.throw(/Cycle detected/);
1127+
});
1128+
1129+
it("should detect deep dependency cycles", () => {
1130+
const a: Signal = computed(() => b.value);
1131+
const b: Signal = computed(() => c.value);
1132+
const c: Signal = computed(() => d.value);
1133+
const d: Signal = computed(() => a.peek());
1134+
expect(() => a.peek()).to.throw(/Cycle detected/);
1135+
});
1136+
10451137
it("should not make surrounding effect depend on the computed", () => {
10461138
const s = signal(1);
10471139
const c = computed(() => s.value);
@@ -1516,14 +1608,25 @@ describe("batch/transaction", () => {
15161608
expect(batch(() => 1)).to.equal(1);
15171609
});
15181610

1519-
it("should throw the error raised from the callback", () => {
1611+
it("should throw errors throws from the callback", () => {
15201612
expect(() =>
15211613
batch(() => {
15221614
throw Error("hello");
15231615
})
15241616
).to.throw("hello");
15251617
});
15261618

1619+
it("should throw non-errors thrown from the callback", () => {
1620+
try {
1621+
batch(() => {
1622+
throw undefined;
1623+
});
1624+
expect.fail();
1625+
} catch (err) {
1626+
expect(err).to.be.undefined;
1627+
}
1628+
});
1629+
15271630
it("should delay writes", () => {
15281631
const a = signal("a");
15291632
const b = signal("b");

0 commit comments

Comments
 (0)