Skip to content

Commit bd3b4d1

Browse files
domfarolinochromium-wpt-export-bot
authored andcommittedApr 1, 2024·
DOM: Implement the switchMap() Observable operator
See WICG/observable#130 for the spec PR, and https://chromium-review.googlesource.com/c/chromium/src/+/5381640 for documentation about the `flatMap()` operator, which is structured in almost the same way as `switchMap()`. R=masonf@chromium.org Bug: 40282760 Change-Id: Id2b0de2d60dd985be843f154bebd66f8948f36f3 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5410391 Reviewed-by: Mason Freed <masonf@chromium.org> Commit-Queue: Dominic Farolino <dom@chromium.org> Cr-Commit-Position: refs/heads/main@{#1280836}
1 parent 0866462 commit bd3b4d1

File tree

1 file changed

+252
-0
lines changed

1 file changed

+252
-0
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
test(() => {
2+
const source = createTestSubject();
3+
const inner1 = createTestSubject();
4+
const inner2 = createTestSubject();
5+
6+
const result = source.switchMap((value, index) => {
7+
if (value === 1) {
8+
return inner1;
9+
}
10+
if (value === 2) {
11+
return inner2;
12+
}
13+
throw new Error("invalid ");
14+
});
15+
16+
const results = [];
17+
18+
result.subscribe({
19+
next: v => results.push(v),
20+
error: e => results.push(e),
21+
complete: () => results.push("complete"),
22+
});
23+
24+
assert_equals(source.subscriberCount(), 1,
25+
"source observable is subscribed to");
26+
27+
source.next(1);
28+
assert_equals(inner1.subscriberCount(), 1,
29+
"inner1 observable is subscribed to");
30+
31+
inner1.next("1a");
32+
assert_array_equals(results, ["1a"]);
33+
34+
inner1.next("1b");
35+
assert_array_equals(results, ["1a", "1b"]);
36+
37+
source.next(2);
38+
assert_equals(inner1.subscriberCount(), 0,
39+
"inner1 observable is unsubscribed from");
40+
assert_equals(inner2.subscriberCount(), 1,
41+
"inner2 observable is subscribed to");
42+
43+
inner2.next("2a");
44+
assert_array_equals(results, ["1a", "1b", "2a"]);
45+
46+
inner2.next("2b");
47+
assert_array_equals(results, ["1a", "1b", "2a", "2b"]);
48+
49+
inner2.complete();
50+
assert_array_equals(results, ["1a", "1b", "2a", "2b"]);
51+
52+
source.complete();
53+
assert_array_equals(results, ["1a", "1b", "2a", "2b", "complete"]);
54+
}, "switchMap(): result subscribes to one inner observable at a time, " +
55+
"unsubscribing from the previous active one when a new one replaces it");
56+
57+
test(() => {
58+
const source = createTestSubject();
59+
const inner = createTestSubject();
60+
61+
const result = source.switchMap(() => inner);
62+
63+
const results = [];
64+
65+
result.subscribe({
66+
next: v => results.push(v),
67+
error: e => results.push(e),
68+
complete: () => results.push("complete"),
69+
});
70+
71+
assert_equals(source.subscriberCount(), 1,
72+
"source observable is subscribed to");
73+
assert_equals(inner.subscriberCount(), 0,
74+
"inner observable is not subscribed to");
75+
76+
source.next(1);
77+
assert_equals(inner.subscriberCount(), 1,
78+
"inner observable is subscribed to");
79+
80+
inner.next("a");
81+
assert_array_equals(results, ["a"]);
82+
83+
inner.next("b");
84+
assert_array_equals(results, ["a", "b"]);
85+
86+
source.complete();
87+
assert_array_equals(results, ["a", "b"],
88+
"Result observable does not complete when source observable completes, " +
89+
"because inner is still active");
90+
91+
inner.next("c");
92+
assert_array_equals(results, ["a", "b", "c"]);
93+
94+
inner.complete();
95+
assert_array_equals(results, ["a", "b", "c", "complete"],
96+
"Result observable completes when inner observable completes, because " +
97+
"source is already complete");
98+
}, "switchMap(): result does not complete when the source observable " +
99+
"completes, if the inner observable is still active");
100+
101+
test(() => {
102+
const source = createTestSubject();
103+
104+
const e = new Error('thrown from mapper');
105+
const result = source.switchMap(() => {
106+
throw e;
107+
});
108+
109+
const results = [];
110+
111+
result.subscribe({
112+
next: v => results.push(v),
113+
error: e => results.push(e),
114+
complete: () => results.push("complete"),
115+
});
116+
117+
assert_equals(source.subscriberCount(), 1,
118+
"source observable is subscribed to");
119+
120+
source.next(1);
121+
assert_array_equals(results, [e]);
122+
assert_equals(source.subscriberCount(), 0,
123+
"source observable is unsubscribed from");
124+
}, "switchMap(): result emits an error if Mapper callback throws an error");
125+
126+
test(() => {
127+
const source = createTestSubject();
128+
const inner = createTestSubject();
129+
130+
const result = source.switchMap(() => inner);
131+
132+
const results = [];
133+
134+
result.subscribe({
135+
next: v => results.push(v),
136+
error: e => results.push(e),
137+
complete: () => results.push("complete"),
138+
});
139+
140+
source.next(1);
141+
inner.next("a");
142+
assert_array_equals(results, ["a"]);
143+
144+
const e = new Error('error from source');
145+
source.error(e);
146+
assert_array_equals(results, ["a", e],
147+
"switchMap result emits an error if the source emits an error");
148+
assert_equals(inner.subscriberCount(), 0,
149+
"inner observable is unsubscribed from");
150+
assert_equals(source.subscriberCount(), 0,
151+
"source observable is unsubscribed from");
152+
}, "switchMap(): result emits an error if the source observable emits an " +
153+
"error");
154+
155+
test(() => {
156+
const source = createTestSubject();
157+
const inner = createTestSubject();
158+
159+
const result = source.switchMap(() => inner);
160+
161+
const results = [];
162+
163+
result.subscribe({
164+
next: v => results.push(v),
165+
error: e => results.push(e),
166+
complete: () => results.push("complete"),
167+
});
168+
169+
source.next(1);
170+
inner.next("a");
171+
assert_array_equals(results, ["a"]);
172+
173+
const e = new Error("error from inner");
174+
inner.error(e);
175+
assert_array_equals(results, ["a", e],
176+
"result emits an error if the inner observable emits an error");
177+
assert_equals(inner.subscriberCount(), 0,
178+
"inner observable is unsubscribed from");
179+
assert_equals(source.subscriberCount(), 0,
180+
"source observable is unsubscribed from");
181+
}, "switchMap(): result emits an error if the inner observable emits an error");
182+
183+
test(() => {
184+
const results = [];
185+
const source = new Observable(subscriber => {
186+
subscriber.next(1);
187+
subscriber.addTeardown(() => {
188+
results.push('source teardown');
189+
});
190+
subscriber.signal.onabort = e => {
191+
results.push('source onabort');
192+
};
193+
});
194+
195+
const inner = new Observable(subscriber => {
196+
subscriber.addTeardown(() => {
197+
results.push('inner teardown');
198+
});
199+
subscriber.signal.onabort = () => {
200+
results.push('inner onabort');
201+
};
202+
});
203+
204+
const result = source.switchMap(() => inner);
205+
206+
const ac = new AbortController();
207+
result.subscribe({
208+
next: v => results.push(v),
209+
error: e => results.error(e),
210+
complete: () => results.complete("complete"),
211+
}, {signal: ac.signal});
212+
213+
ac.abort();
214+
assert_array_equals(results, [
215+
"source teardown",
216+
"source onabort",
217+
"inner teardown",
218+
"inner onabort",
219+
], "Unsubscription order is correct");
220+
}, "switchMap(): should unsubscribe in the correct order when user aborts " +
221+
"the subscription");
222+
223+
// A helper function to create an Observable that can be externally controlled
224+
// and examined for testing purposes.
225+
function createTestSubject() {
226+
const subscribers = new Set();
227+
const subject = new Observable(subscriber => {
228+
subscribers.add(subscriber);
229+
subscriber.addTeardown(() => subscribers.delete(subscriber));
230+
});
231+
232+
subject.next = value => {
233+
for (const subscriber of Array.from(subscribers)) {
234+
subscriber.next(value);
235+
}
236+
};
237+
subject.error = error => {
238+
for (const subscriber of Array.from(subscribers)) {
239+
subscriber.error(error);
240+
}
241+
};
242+
subject.complete = () => {
243+
for (const subscriber of Array.from(subscribers)) {
244+
subscriber.complete();
245+
}
246+
};
247+
subject.subscriberCount = () => {
248+
return subscribers.size;
249+
};
250+
251+
return subject;
252+
}

0 commit comments

Comments
 (0)
Please sign in to comment.