synstate 6.2.1 → 7.0.1
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/core/class/child-observable-class.d.mts +16 -14
- package/dist/core/class/child-observable-class.d.mts.map +1 -1
- package/dist/core/class/child-observable-class.mjs +39 -12
- package/dist/core/class/child-observable-class.mjs.map +1 -1
- package/dist/core/class/create-child-observable.d.mts +1 -1
- package/dist/core/class/create-child-observable.d.mts.map +1 -1
- package/dist/core/class/create-child-observable.mjs +6 -8
- package/dist/core/class/create-child-observable.mjs.map +1 -1
- package/dist/core/class/create-root-observable.d.mts +1 -1
- package/dist/core/class/create-root-observable.d.mts.map +1 -1
- package/dist/core/class/create-root-observable.mjs +1 -5
- package/dist/core/class/create-root-observable.mjs.map +1 -1
- package/dist/core/class/propagation-state.mjs +1 -1
- package/dist/core/class/propagation-state.mjs.map +1 -1
- package/dist/core/combine/derive.d.mts.map +1 -1
- package/dist/core/combine/derive.mjs +2 -1
- package/dist/core/combine/derive.mjs.map +1 -1
- package/dist/core/combine/zip.d.mts.map +1 -1
- package/dist/core/combine/zip.mjs +5 -2
- package/dist/core/combine/zip.mjs.map +1 -1
- package/dist/core/create/counter.mjs +2 -2
- package/dist/core/create/counter.mjs.map +1 -1
- package/dist/core/create/from-abortable-promise.d.mts.map +1 -1
- package/dist/core/create/from-abortable-promise.mjs +10 -3
- package/dist/core/create/from-abortable-promise.mjs.map +1 -1
- package/dist/core/create/from-promise.d.mts.map +1 -1
- package/dist/core/create/from-promise.mjs +10 -3
- package/dist/core/create/from-promise.mjs.map +1 -1
- package/dist/core/create/source.d.mts.map +1 -1
- package/dist/core/create/source.mjs +2 -1
- package/dist/core/create/source.mjs.map +1 -1
- package/dist/core/operators/filter.d.mts +1 -1
- package/dist/core/operators/filter.d.mts.map +1 -1
- package/dist/core/operators/filter.mjs +2 -3
- package/dist/core/operators/filter.mjs.map +1 -1
- package/dist/core/operators/merge-map.d.mts +6 -2
- package/dist/core/operators/merge-map.d.mts.map +1 -1
- package/dist/core/operators/merge-map.mjs +47 -16
- package/dist/core/operators/merge-map.mjs.map +1 -1
- package/dist/core/operators/skip-while.d.mts +1 -1
- package/dist/core/operators/skip-while.d.mts.map +1 -1
- package/dist/core/operators/skip-while.mjs +9 -6
- package/dist/core/operators/skip-while.mjs.map +1 -1
- package/dist/core/operators/switch-map.d.mts +4 -0
- package/dist/core/operators/switch-map.d.mts.map +1 -1
- package/dist/core/operators/switch-map.mjs +16 -6
- package/dist/core/operators/switch-map.mjs.map +1 -1
- package/dist/core/operators/take-while.d.mts +2 -2
- package/dist/core/operators/take-while.d.mts.map +1 -1
- package/dist/core/operators/take-while.mjs +10 -8
- package/dist/core/operators/take-while.mjs.map +1 -1
- package/dist/core/operators/throttle.mjs +3 -2
- package/dist/core/operators/throttle.mjs.map +1 -1
- package/dist/core/operators/with-buffered-from.d.mts.map +1 -1
- package/dist/core/operators/with-buffered-from.mjs +13 -3
- package/dist/core/operators/with-buffered-from.mjs.map +1 -1
- package/dist/core/predefined/operators/attach-index.d.mts +1 -1
- package/dist/core/predefined/operators/attach-index.d.mts.map +1 -1
- package/dist/core/predefined/operators/attach-index.mjs +2 -3
- package/dist/core/predefined/operators/attach-index.mjs.map +1 -1
- package/dist/core/predefined/operators/skip.d.mts.map +1 -1
- package/dist/core/predefined/operators/skip.mjs +2 -2
- package/dist/core/predefined/operators/skip.mjs.map +1 -1
- package/dist/core/utils/index.d.mts +2 -0
- package/dist/core/utils/index.d.mts.map +1 -1
- package/dist/core/utils/index.mjs +2 -0
- package/dist/core/utils/index.mjs.map +1 -1
- package/dist/core/utils/is-development-build.d.mts +15 -0
- package/dist/core/utils/is-development-build.d.mts.map +1 -0
- package/dist/core/utils/is-development-build.mjs +23 -0
- package/dist/core/utils/is-development-build.mjs.map +1 -0
- package/dist/core/utils/rethrow-out-of-promise.d.mts +10 -0
- package/dist/core/utils/rethrow-out-of-promise.d.mts.map +1 -0
- package/dist/core/utils/rethrow-out-of-promise.mjs +17 -0
- package/dist/core/utils/rethrow-out-of-promise.mjs.map +1 -0
- package/package.json +12 -11
- package/src/core/class/child-observable-class.mts +53 -17
- package/src/core/class/circular-dependency-comparison.test.mts +36 -40
- package/src/core/class/circular-dependency.test.mts +6 -37
- package/src/core/class/create-child-observable.mts +8 -5
- package/src/core/class/create-root-observable.mts +2 -1
- package/src/core/class/propagation-state.mts +1 -1
- package/src/core/class/write-during-propagation.test.mts +75 -4
- package/src/core/combine/derive.mts +2 -1
- package/src/core/combine/zip.mts +5 -2
- package/src/core/create/counter.mts +2 -2
- package/src/core/create/from-abortable-promise.mts +33 -23
- package/src/core/create/from-promise.mts +28 -18
- package/src/core/create/from-promise.test.mts +97 -0
- package/src/core/create/source.mts +2 -1
- package/src/core/operators/filter.mts +2 -3
- package/src/core/operators/flat-map-initial-value.test.mts +269 -0
- package/src/core/operators/flat-map-sync-inner.test.mts +4 -2
- package/src/core/operators/inner-ownership.test.mts +145 -2
- package/src/core/operators/merge-map.mts +56 -26
- package/src/core/operators/skip-while.mts +12 -8
- package/src/core/operators/skip-while.test.mts +59 -0
- package/src/core/operators/switch-map.mts +21 -10
- package/src/core/operators/take-while.mts +15 -17
- package/src/core/operators/take-while.test.mts +51 -0
- package/src/core/operators/throttle.mts +4 -3
- package/src/core/operators/with-buffered-from.mts +16 -3
- package/src/core/operators/with-buffered-from.test.mts +27 -0
- package/src/core/predefined/operators/attach-index.mts +2 -3
- package/src/core/predefined/operators/skip.mts +2 -2
- package/src/core/utils/index.mts +2 -0
- package/src/core/utils/is-development-build.mts +32 -0
- package/src/core/utils/is-development-build.test.mts +44 -0
- package/src/core/utils/rethrow-out-of-promise.mts +16 -0
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { Optional, Result } from 'ts-data-forge';
|
|
9
9
|
import { RootObservableClass } from '../class/index.mjs';
|
|
10
10
|
import type { FromPromiseObservable } from '../types/index.mjs';
|
|
11
|
+
import { rethrowOutOfPromise } from '../utils/index.mjs';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Creates an observable from a Promise factory that receives an `AbortSignal`.
|
|
@@ -96,35 +97,44 @@ class FromAbortablePromiseObservableClass<A, E = unknown>
|
|
|
96
97
|
|
|
97
98
|
const promise = factory(this.#abortController.signal);
|
|
98
99
|
|
|
100
|
+
// `onRejected` beside `onFulfilled`, so that only the promise's own
|
|
101
|
+
// rejection becomes an Err: what a subscriber throws on the Ok value is not
|
|
102
|
+
// the promise's (#2931). A `.catch` after `.then` would take both, and is
|
|
103
|
+
// also a step later.
|
|
99
104
|
promise
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
105
|
+
// eslint-disable-next-line unicorn/prefer-then-catch -- its suggestion, `.then(onFulfilled).catch(onRejected)`, would take what a subscriber throws too
|
|
106
|
+
.then(
|
|
107
|
+
(value) => {
|
|
108
|
+
if (this.isCompleted) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
104
111
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
this.startUpdate(Result.ok(value));
|
|
113
|
+
},
|
|
114
|
+
(error: unknown) => {
|
|
115
|
+
if (this.isCompleted) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
111
118
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
// Silently ignore AbortError — it means the observable was
|
|
120
|
+
// intentionally completed (e.g., by switchMap).
|
|
121
|
+
if (error instanceof DOMException && error.name === 'AbortError') {
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
117
124
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
+
this.startUpdate(
|
|
126
|
+
Result.err(
|
|
127
|
+
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
128
|
+
error as E,
|
|
129
|
+
),
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
)
|
|
133
|
+
.catch(rethrowOutOfPromise)
|
|
125
134
|
.finally(() => {
|
|
126
135
|
this.complete();
|
|
127
|
-
})
|
|
136
|
+
})
|
|
137
|
+
.catch(rethrowOutOfPromise);
|
|
128
138
|
}
|
|
129
139
|
|
|
130
140
|
protected override teardown(): void {
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import { Optional, Result } from 'ts-data-forge';
|
|
8
8
|
import { RootObservableClass } from '../class/index.mjs';
|
|
9
9
|
import type { FromPromiseObservable } from '../types/index.mjs';
|
|
10
|
+
import { rethrowOutOfPromise } from '../utils/index.mjs';
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* Creates an observable from a Promise.
|
|
@@ -66,28 +67,37 @@ class FromPromiseObservableClass<A, E = unknown>
|
|
|
66
67
|
constructor(promise: Promise<A>) {
|
|
67
68
|
super({ initialValue: Optional.none });
|
|
68
69
|
|
|
70
|
+
// `onRejected` beside `onFulfilled`, so that only the promise's own
|
|
71
|
+
// rejection becomes an Err: what a subscriber throws on the Ok value is not
|
|
72
|
+
// the promise's (#2931). A `.catch` after `.then` would take both, and is
|
|
73
|
+
// also a step later.
|
|
69
74
|
promise
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
// eslint-disable-next-line unicorn/prefer-then-catch -- its suggestion, `.then(onFulfilled).catch(onRejected)`, would take what a subscriber throws too
|
|
76
|
+
.then(
|
|
77
|
+
(value) => {
|
|
78
|
+
if (this.isCompleted) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
74
81
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
this.startUpdate(Result.ok(value));
|
|
83
|
+
},
|
|
84
|
+
(error: unknown) => {
|
|
85
|
+
if (this.isCompleted) {
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
this.startUpdate(
|
|
90
|
+
Result.err(
|
|
91
|
+
// @sumi-expect-error banned-syntax/no-unsafe-type-assertion
|
|
92
|
+
error as E,
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
},
|
|
96
|
+
)
|
|
97
|
+
.catch(rethrowOutOfPromise)
|
|
89
98
|
.finally(() => {
|
|
90
99
|
this.complete();
|
|
91
|
-
})
|
|
100
|
+
})
|
|
101
|
+
.catch(rethrowOutOfPromise);
|
|
92
102
|
}
|
|
93
103
|
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Optional, Result } from 'ts-data-forge';
|
|
2
|
+
import { panic } from 'ts-std-forge';
|
|
3
|
+
import { fromAbortablePromise } from './from-abortable-promise.mjs';
|
|
4
|
+
import { fromPromise } from './from-promise.mjs';
|
|
5
|
+
|
|
6
|
+
describe('fromPromise and fromAbortablePromise report only the promise’s own rejection as an Err (#2931)', () => {
|
|
7
|
+
test.each([
|
|
8
|
+
['fromPromise', () => fromPromise(Promise.resolve(1))],
|
|
9
|
+
[
|
|
10
|
+
'fromAbortablePromise',
|
|
11
|
+
() => fromAbortablePromise(() => Promise.resolve(1)),
|
|
12
|
+
],
|
|
13
|
+
] as const)(
|
|
14
|
+
'%s: a subscriber that throws on the Ok value gets no Err',
|
|
15
|
+
async (_, create) => {
|
|
16
|
+
vi.useFakeTimers();
|
|
17
|
+
|
|
18
|
+
onTestFinished(() => {
|
|
19
|
+
vi.useRealTimers();
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const mut_events: string[] = [];
|
|
23
|
+
|
|
24
|
+
const result$ = create();
|
|
25
|
+
|
|
26
|
+
result$.subscribe(
|
|
27
|
+
(r) => {
|
|
28
|
+
mut_events.push(Result.isOk(r) ? `ok ${r.value}` : 'err');
|
|
29
|
+
|
|
30
|
+
if (Result.isOk(r)) {
|
|
31
|
+
panic('bug in a subscriber');
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
() => {
|
|
35
|
+
mut_events.push('complete');
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
await settlePromises();
|
|
40
|
+
|
|
41
|
+
assert.deepStrictEqual(mut_events, ['ok 1', 'complete']);
|
|
42
|
+
|
|
43
|
+
// The subscriber's exception is not swallowed: it is thrown again from a
|
|
44
|
+
// task of its own.
|
|
45
|
+
expect(() => {
|
|
46
|
+
vi.runOnlyPendingTimers();
|
|
47
|
+
}).toThrow('bug in a subscriber');
|
|
48
|
+
},
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const rejection = new Error('rejected');
|
|
52
|
+
|
|
53
|
+
test.each([
|
|
54
|
+
['fromPromise', () => fromPromise(Promise.reject(rejection))],
|
|
55
|
+
[
|
|
56
|
+
'fromAbortablePromise',
|
|
57
|
+
() => fromAbortablePromise(() => Promise.reject(rejection)),
|
|
58
|
+
],
|
|
59
|
+
] as const)('%s: a rejection is an Err', async (_, create) => {
|
|
60
|
+
const result$ = create();
|
|
61
|
+
|
|
62
|
+
const mut_events: string[] = [];
|
|
63
|
+
|
|
64
|
+
result$.subscribe(
|
|
65
|
+
(r) => {
|
|
66
|
+
mut_events.push(
|
|
67
|
+
Result.isErr(r) && r.value === rejection ? 'err rejected' : 'ok',
|
|
68
|
+
);
|
|
69
|
+
},
|
|
70
|
+
() => {
|
|
71
|
+
mut_events.push('complete');
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
await settlePromises();
|
|
76
|
+
|
|
77
|
+
assert.deepStrictEqual(mut_events, ['err rejected', 'complete']);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test('fromPromise holds the value by the time an await of the same promise resumes', async () => {
|
|
81
|
+
const promise = Promise.resolve(1);
|
|
82
|
+
|
|
83
|
+
const result$ = fromPromise(promise);
|
|
84
|
+
|
|
85
|
+
await promise;
|
|
86
|
+
|
|
87
|
+
assert.deepStrictEqual(result$.getSnapshot(), Optional.some(Result.ok(1)));
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
/** Lets the promise chains already started run to their end. */
|
|
92
|
+
const settlePromises = async (): Promise<void> => {
|
|
93
|
+
for (const _ of Array.from({ length: 10 })) {
|
|
94
|
+
// eslint-disable-next-line no-await-in-loop -- each await lets the chains take one more step
|
|
95
|
+
await Promise.resolve();
|
|
96
|
+
}
|
|
97
|
+
};
|
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
// @sumi-expect-error-file classes/no-class
|
|
5
5
|
// @sumi-expect-error-file banned-syntax/no-this
|
|
6
6
|
// @sumi-expect-error-file functions/prefer-arrow-function
|
|
7
|
-
import {
|
|
7
|
+
import { Optional } from 'ts-data-forge';
|
|
8
|
+
import * as Arr from 'ts-data-forge/array';
|
|
8
9
|
import { RootObservableClass } from '../class/index.mjs';
|
|
9
10
|
import type {
|
|
10
11
|
InitializedSourceObservable,
|
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
isString,
|
|
13
13
|
Optional,
|
|
14
14
|
pipe,
|
|
15
|
-
SafeUint,
|
|
15
|
+
type SafeUint,
|
|
16
16
|
} from 'ts-data-forge';
|
|
17
17
|
import { SyncChildObservableClass } from '../class/index.mjs';
|
|
18
18
|
import { source } from '../create/index.mjs';
|
|
@@ -132,8 +132,7 @@ class FilterObservableClass<A>
|
|
|
132
132
|
return; // skip update
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
this.#mut_index =
|
|
136
|
-
this.#mut_index === -1 ? asSafeUint(0) : SafeUint.add(1, this.#mut_index);
|
|
135
|
+
this.#mut_index = asSafeUint(this.#mut_index + 1);
|
|
137
136
|
|
|
138
137
|
if (this.#predicate(sn.value, this.#mut_index)) {
|
|
139
138
|
this.setNext(sn.value, updateToken);
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { Optional } from 'ts-data-forge';
|
|
2
|
+
import { panic } from 'ts-std-forge';
|
|
3
|
+
import { toNode } from '../class/index.mjs';
|
|
4
|
+
import { just, source } from '../create/index.mjs';
|
|
5
|
+
import { onScopeDispose } from '../scope/index.mjs';
|
|
6
|
+
import type { Observable } from '../types/index.mjs';
|
|
7
|
+
import { filter } from './filter.mjs';
|
|
8
|
+
import { map } from './map.mjs';
|
|
9
|
+
import { mergeMap } from './merge-map.mjs';
|
|
10
|
+
import { skipWhile } from './skip-while.mjs';
|
|
11
|
+
import { switchMap } from './switch-map.mjs';
|
|
12
|
+
|
|
13
|
+
describe('the value the source holds when the operator is created is projected', () => {
|
|
14
|
+
test('switchMap: a sync inner gives the operator its value at once', () => {
|
|
15
|
+
const a$ = source<number>(1);
|
|
16
|
+
|
|
17
|
+
const s$ = a$.pipe(switchMap((x) => just(x * 10)));
|
|
18
|
+
|
|
19
|
+
assert.deepStrictEqual(s$.getSnapshot(), Optional.some(10));
|
|
20
|
+
|
|
21
|
+
const mut_history: number[] = [];
|
|
22
|
+
|
|
23
|
+
s$.subscribe((v) => {
|
|
24
|
+
mut_history.push(v);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
a$.next(2);
|
|
28
|
+
|
|
29
|
+
assert.deepStrictEqual(mut_history, [10, 20]);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('switchMap: a later value of the first inner is passed on', () => {
|
|
33
|
+
const a$ = source<number>(1);
|
|
34
|
+
|
|
35
|
+
const inner$ = source<string>();
|
|
36
|
+
|
|
37
|
+
const mut_projected: number[] = [];
|
|
38
|
+
|
|
39
|
+
const s$ = a$.pipe(
|
|
40
|
+
switchMap((x) => {
|
|
41
|
+
mut_projected.push(x);
|
|
42
|
+
|
|
43
|
+
return inner$;
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
assert.deepStrictEqual(mut_projected, [1]);
|
|
48
|
+
|
|
49
|
+
const mut_history: string[] = [];
|
|
50
|
+
|
|
51
|
+
s$.subscribe((v) => {
|
|
52
|
+
mut_history.push(v);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
inner$.next('a');
|
|
56
|
+
|
|
57
|
+
assert.deepStrictEqual(mut_history, ['a']);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('switchMap: the next value of the source switches away from the first inner', () => {
|
|
61
|
+
const a$ = source<number>(1);
|
|
62
|
+
|
|
63
|
+
const first$ = source<string>();
|
|
64
|
+
|
|
65
|
+
const second$ = source<string>();
|
|
66
|
+
|
|
67
|
+
const s$ = a$.pipe(switchMap((x) => (x === 1 ? first$ : second$)));
|
|
68
|
+
|
|
69
|
+
const mut_history: string[] = [];
|
|
70
|
+
|
|
71
|
+
s$.subscribe((v) => {
|
|
72
|
+
mut_history.push(v);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
a$.next(2);
|
|
76
|
+
|
|
77
|
+
first$.next('stale');
|
|
78
|
+
|
|
79
|
+
second$.next('b');
|
|
80
|
+
|
|
81
|
+
assert.deepStrictEqual(mut_history, ['b']);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('switchMap: what the first projection created is disposed when it is switched away from', () => {
|
|
85
|
+
const a$ = source<number>(1);
|
|
86
|
+
|
|
87
|
+
const mut_disposed: number[] = [];
|
|
88
|
+
|
|
89
|
+
const s$ = a$.pipe(
|
|
90
|
+
switchMap((x) => {
|
|
91
|
+
onScopeDispose(() => {
|
|
92
|
+
mut_disposed.push(x);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
return just(x);
|
|
96
|
+
}),
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
s$.subscribe(() => undefined);
|
|
100
|
+
|
|
101
|
+
assert.deepStrictEqual(mut_disposed, []);
|
|
102
|
+
|
|
103
|
+
a$.next(2);
|
|
104
|
+
|
|
105
|
+
assert.deepStrictEqual(mut_disposed, [1]);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('switchMap: a source with no value projects nothing until it emits', () => {
|
|
109
|
+
const a$ = source<number>();
|
|
110
|
+
|
|
111
|
+
const mut_projected: number[] = [];
|
|
112
|
+
|
|
113
|
+
const s$ = a$.pipe(
|
|
114
|
+
switchMap((x) => {
|
|
115
|
+
mut_projected.push(x);
|
|
116
|
+
|
|
117
|
+
return just(x);
|
|
118
|
+
}),
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
assert.isTrue(Optional.isNone(s$.getSnapshot()));
|
|
122
|
+
|
|
123
|
+
a$.next(1);
|
|
124
|
+
|
|
125
|
+
assert.deepStrictEqual(mut_projected, [1]);
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
test('switchMap: a value carried through filter and map is projected', () => {
|
|
129
|
+
const a$ = source<Optional<number>>(Optional.some(42));
|
|
130
|
+
|
|
131
|
+
const s$ = a$
|
|
132
|
+
.pipe(filter(Optional.isSome))
|
|
133
|
+
.pipe(map((some) => some.value))
|
|
134
|
+
.pipe(switchMap((id) => just(`item ${id.toString()}`)));
|
|
135
|
+
|
|
136
|
+
assert.deepStrictEqual(s$.getSnapshot(), Optional.some('item 42'));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('switchMap: a completed source projects nothing and completes', () => {
|
|
140
|
+
const a$ = just(1);
|
|
141
|
+
|
|
142
|
+
const mut_projected: number[] = [];
|
|
143
|
+
|
|
144
|
+
const s$ = a$.pipe(
|
|
145
|
+
switchMap((x) => {
|
|
146
|
+
mut_projected.push(x);
|
|
147
|
+
|
|
148
|
+
return just(x);
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
assert.deepStrictEqual(mut_projected, []);
|
|
153
|
+
|
|
154
|
+
assert.isTrue(s$.isCompleted);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test('mergeMap: a completed source projects nothing and completes', () => {
|
|
158
|
+
const a$ = just(1);
|
|
159
|
+
|
|
160
|
+
const mut_projected: number[] = [];
|
|
161
|
+
|
|
162
|
+
const s$ = a$.pipe(
|
|
163
|
+
mergeMap((x) => {
|
|
164
|
+
mut_projected.push(x);
|
|
165
|
+
|
|
166
|
+
return just(x);
|
|
167
|
+
}),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
assert.deepStrictEqual(mut_projected, []);
|
|
171
|
+
|
|
172
|
+
assert.isTrue(s$.isCompleted);
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test('mergeMap: the first inner and the later ones are merged', () => {
|
|
176
|
+
const a$ = source<number>(1);
|
|
177
|
+
|
|
178
|
+
const first$ = source<string>();
|
|
179
|
+
|
|
180
|
+
const second$ = source<string>();
|
|
181
|
+
|
|
182
|
+
const s$ = a$.pipe(mergeMap((x) => (x === 1 ? first$ : second$)));
|
|
183
|
+
|
|
184
|
+
const mut_history: string[] = [];
|
|
185
|
+
|
|
186
|
+
s$.subscribe((v) => {
|
|
187
|
+
mut_history.push(v);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
a$.next(2);
|
|
191
|
+
|
|
192
|
+
first$.next('a');
|
|
193
|
+
|
|
194
|
+
second$.next('b');
|
|
195
|
+
|
|
196
|
+
assert.deepStrictEqual(mut_history, ['a', 'b']);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test('mergeMap: a sync inner gives the operator its value at once', () => {
|
|
200
|
+
const a$ = source<number>(1);
|
|
201
|
+
|
|
202
|
+
const s$ = a$.pipe(mergeMap((x) => just(x * 10)));
|
|
203
|
+
|
|
204
|
+
assert.deepStrictEqual(s$.getSnapshot(), Optional.some(10));
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
test('switchMap: a first projection that throws leaves nothing attached to the source', () => {
|
|
208
|
+
const a$ = source<number>(1);
|
|
209
|
+
|
|
210
|
+
const mut_projected: number[] = [];
|
|
211
|
+
|
|
212
|
+
assert.throws(() => {
|
|
213
|
+
a$.pipe(
|
|
214
|
+
switchMap((x) => {
|
|
215
|
+
mut_projected.push(x);
|
|
216
|
+
|
|
217
|
+
// Made in the projection's scope before it throws.
|
|
218
|
+
a$.pipe(map((v) => v + 1));
|
|
219
|
+
|
|
220
|
+
if (x === 1) {
|
|
221
|
+
panic('first');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return just(x);
|
|
225
|
+
}),
|
|
226
|
+
);
|
|
227
|
+
}, /first/u);
|
|
228
|
+
|
|
229
|
+
assert.isFalse(toNode(a$).hasChild);
|
|
230
|
+
|
|
231
|
+
a$.next(2);
|
|
232
|
+
|
|
233
|
+
assert.deepStrictEqual(mut_projected, [1]);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('mergeMap: a first projection that throws leaves nothing attached to the source', () => {
|
|
237
|
+
const a$ = source<number>(1);
|
|
238
|
+
|
|
239
|
+
assert.throws(() => {
|
|
240
|
+
a$.pipe(
|
|
241
|
+
mergeMap((): Observable<number> => {
|
|
242
|
+
a$.pipe(map((v) => v + 1));
|
|
243
|
+
|
|
244
|
+
panic('first');
|
|
245
|
+
}),
|
|
246
|
+
);
|
|
247
|
+
}, /first/u);
|
|
248
|
+
|
|
249
|
+
assert.isFalse(toNode(a$).hasChild);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test('switchMap: skipWhile on the held value before it keeps that value from being projected', () => {
|
|
253
|
+
const a$ = source<number>(1);
|
|
254
|
+
|
|
255
|
+
const mut_projected: number[] = [];
|
|
256
|
+
|
|
257
|
+
a$.pipe(skipWhile((_, index) => index === -1)).pipe(
|
|
258
|
+
switchMap((x) => {
|
|
259
|
+
mut_projected.push(x);
|
|
260
|
+
|
|
261
|
+
return just(x);
|
|
262
|
+
}),
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
a$.next(2);
|
|
266
|
+
|
|
267
|
+
assert.deepStrictEqual(mut_projected, [2]);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -24,7 +24,8 @@ describe('an inner observable that holds a value is passed on in the update that
|
|
|
24
24
|
|
|
25
25
|
a$.next(3);
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
// 'm1,s1' from the value `a$` held when the operators were created.
|
|
28
|
+
assert.deepStrictEqual(mut_history, ['m1,s1', 'm2,s2', 'm3,s3']);
|
|
28
29
|
});
|
|
29
30
|
|
|
30
31
|
test('switchMap: a child of both it and its parent emits once per update', () => {
|
|
@@ -43,6 +44,7 @@ describe('an inner observable that holds a value is passed on in the update that
|
|
|
43
44
|
a$.next(3);
|
|
44
45
|
|
|
45
46
|
assert.deepStrictEqual(mut_history, [
|
|
47
|
+
[1, 10],
|
|
46
48
|
[2, 20],
|
|
47
49
|
[3, 30],
|
|
48
50
|
]);
|
|
@@ -85,6 +87,6 @@ describe('an inner observable that holds a value is passed on in the update that
|
|
|
85
87
|
|
|
86
88
|
a$.next(2);
|
|
87
89
|
|
|
88
|
-
assert.deepStrictEqual(mut_history, ['m2,s2']);
|
|
90
|
+
assert.deepStrictEqual(mut_history, ['m1,s1', 'm2,s2']);
|
|
89
91
|
});
|
|
90
92
|
});
|