···
1
-
import { Source, Sink, Operator, Signal, SignalKind, TalkbackKind, TalkbackFn } from '../types';
1
+
import { Source, Sink, Signal, SignalKind, TalkbackKind, TalkbackFn } from '../types';
import { push, start } from '../helpers';
11
+
passesSourcePushThenEnd,
12
+
passesAsyncSequence,
14
+
} from './compliance';
import * as sources from '../sources';
import * as sinks from '../sinks';
import * as operators from '../operators';
8
-
/* This tests a noop operator for passive Pull talkback signals.
9
-
A Pull will be sent from the sink upwards and should pass through
10
-
the operator until the source receives it, which then pushes a
12
-
const passesPassivePull = (operator: Operator<any, any>, output: any = 0) => {
13
-
it('responds to Pull talkback signals (spec)', () => {
14
-
let talkback: TalkbackFn | null = null;
16
-
const values: any[] = [];
18
-
const source: Source<any> = sink => {
21
-
if (!pushes && signal === TalkbackKind.Pull) {
29
-
const sink: Sink<any> = signal => {
30
-
expect(signal).not.toBe(SignalKind.End);
31
-
if (signal === SignalKind.End) {
33
-
} else if (signal.tag === SignalKind.Push) {
34
-
values.push(signal[0]);
36
-
talkback = signal[0];
40
-
operator(source)(sink);
41
-
// The Start signal should always come in immediately
42
-
expect(talkback).not.toBe(null);
43
-
// No Push signals should be issued initially
44
-
expect(values).toEqual([]);
46
-
// When pulling a value we expect an immediate response
47
-
talkback!(TalkbackKind.Pull);
48
-
jest.runAllTimers();
49
-
expect(values).toEqual([output]);
53
-
/* This tests a noop operator for regular, active Push signals.
54
-
A Push will be sent downwards from the source, through the
55
-
operator to the sink. Pull events should be let through from
56
-
the sink after every Push event. */
57
-
const passesActivePush = (operator: Operator<any, any>, result: any = 0) => {
58
-
it('responds to eager Push signals (spec)', () => {
59
-
const values: any[] = [];
60
-
let talkback: TalkbackFn | null = null;
61
-
let sink: Sink<any> | null = null;
64
-
const source: Source<any> = _sink => {
67
-
if (signal === TalkbackKind.Pull) pulls++;
72
-
operator(source)(signal => {
73
-
expect(signal).not.toBe(SignalKind.End);
74
-
if (signal === SignalKind.End) {
76
-
} else if (signal.tag === SignalKind.Start) {
77
-
talkback = signal[0];
78
-
} else if (signal.tag === SignalKind.Push) {
79
-
values.push(signal[0]);
80
-
talkback!(TalkbackKind.Pull);
84
-
// No Pull signals should be issued initially
85
-
expect(pulls).toBe(0);
87
-
// When pushing a value we expect an immediate response
89
-
jest.runAllTimers();
90
-
expect(values).toEqual([result]);
91
-
// Subsequently the Pull signal should have travelled upwards
92
-
expect(pulls).toBe(1);
96
-
/* This tests a noop operator for Close talkback signals from the sink.
97
-
A Close signal will be sent, which should be forwarded to the source,
98
-
which then ends the communication without sending an End signal. */
99
-
const passesSinkClose = (operator: Operator<any, any>) => {
100
-
it('responds to Close signals from sink (spec)', () => {
101
-
let talkback: TalkbackFn | null = null;
104
-
const source: Source<any> = sink => {
107
-
if (signal === TalkbackKind.Pull && !closing) {
109
-
} else if (signal === TalkbackKind.Close) {
116
-
const sink: Sink<any> = signal => {
117
-
expect(signal).not.toBe(SignalKind.End);
118
-
if (signal === SignalKind.End) {
120
-
} else if (signal.tag === SignalKind.Push) {
121
-
talkback!(TalkbackKind.Close);
123
-
talkback = signal[0];
127
-
operator(source)(sink);
129
-
// When pushing a value we expect an immediate close signal
130
-
talkback!(TalkbackKind.Pull);
131
-
jest.runAllTimers();
132
-
expect(closing).toBe(1);
136
-
/* This tests a noop operator for End signals from the source.
137
-
A Push and End signal will be sent after the first Pull talkback
138
-
signal from the sink, which shouldn't lead to any extra Close or Pull
139
-
talkback signals. */
140
-
const passesSourceEnd = (operator: Operator<any, any>, result: any = 0) => {
141
-
it('passes on immediate Push then End signals from source (spec)', () => {
142
-
const signals: Signal<any>[] = [];
143
-
let talkback: TalkbackFn | null = null;
147
-
const source: Source<any> = sink => {
150
-
expect(signal).not.toBe(TalkbackKind.Close);
151
-
if (signal === TalkbackKind.Pull) {
155
-
sink(SignalKind.End);
162
-
const sink: Sink<any> = signal => {
163
-
if (signal === SignalKind.End) {
164
-
signals.push(signal);
166
-
} else if (signal.tag === SignalKind.Push) {
167
-
signals.push(signal);
169
-
talkback = signal[0];
173
-
operator(source)(sink);
175
-
// When pushing a value we expect an immediate Push then End signal
176
-
talkback!(TalkbackKind.Pull);
177
-
jest.runAllTimers();
178
-
expect(ending).toBe(1);
179
-
expect(signals).toEqual([push(result), SignalKind.End]);
180
-
// Also no additional pull event should be created by the operator
181
-
expect(pulls).toBe(1);
185
-
/* This tests a noop operator for End signals from the source
186
-
after the first pull in response to another.
187
-
This is similar to passesSourceEnd but more well behaved since
188
-
mergeMap/switchMap/concatMap are eager operators. */
189
-
const passesSourcePushThenEnd = (operator: Operator<any, any>, result: any = 0) => {
190
-
it('passes on End signals from source (spec)', () => {
191
-
const signals: Signal<any>[] = [];
192
-
let talkback: TalkbackFn | null = null;
196
-
const source: Source<any> = sink => {
199
-
expect(signal).not.toBe(TalkbackKind.Close);
200
-
if (signal === TalkbackKind.Pull) {
205
-
sink(SignalKind.End);
212
-
const sink: Sink<any> = signal => {
213
-
if (signal === SignalKind.End) {
214
-
signals.push(signal);
216
-
} else if (signal.tag === SignalKind.Push) {
217
-
signals.push(signal);
218
-
talkback!(TalkbackKind.Pull);
220
-
talkback = signal[0];
224
-
operator(source)(sink);
226
-
// When pushing a value we expect an immediate Push then End signal
227
-
talkback!(TalkbackKind.Pull);
228
-
jest.runAllTimers();
229
-
expect(ending).toBe(1);
230
-
expect(pulls).toBe(3);
231
-
expect(signals).toEqual([push(result), push(result), SignalKind.End]);
235
-
/* This tests a noop operator for Start signals from the source.
236
-
When the operator's sink is started by the source it'll receive
237
-
a Start event. As a response it should never send more than one
238
-
Start signals to the sink. */
239
-
const passesSingleStart = (operator: Operator<any, any>) => {
240
-
it('sends a single Start event to the incoming sink (spec)', () => {
243
-
const source: Source<any> = sink => {
244
-
sink(start(() => {}));
247
-
const sink: Sink<any> = signal => {
248
-
if (signal !== SignalKind.End && signal.tag === SignalKind.Start) {
253
-
// When starting the operator we expect a single start event on the sink
254
-
operator(source)(sink);
255
-
expect(starts).toBe(1);
259
-
/* This tests a noop operator for silence after End signals from the source.
260
-
When the operator receives the End signal it shouldn't forward any other
261
-
signals to the sink anymore.
262
-
This isn't a strict requirement, but some operators should ensure that
263
-
all sources are well behaved. This is particularly true for operators
264
-
that either Close sources themselves or may operate on multiple sources. */
265
-
const passesStrictEnd = (operator: Operator<any, any>) => {
266
-
it('stops all signals after End has been received (spec: strict end)', () => {
268
-
const signals: Signal<any>[] = [];
270
-
const source: Source<any> = sink => {
273
-
if (signal === TalkbackKind.Pull) {
275
-
sink(SignalKind.End);
282
-
const sink: Sink<any> = signal => {
283
-
if (signal === SignalKind.End) {
284
-
signals.push(signal);
285
-
} else if (signal.tag === SignalKind.Push) {
286
-
signals.push(signal);
288
-
signal[0](TalkbackKind.Pull);
292
-
operator(source)(sink);
294
-
// The Push signal should've been dropped
295
-
jest.runAllTimers();
296
-
expect(signals).toEqual([SignalKind.End]);
297
-
expect(pulls).toBe(1);
300
-
it('stops all signals after Close has been received (spec: strict close)', () => {
301
-
const signals: Signal<any>[] = [];
303
-
const source: Source<any> = sink => {
306
-
if (signal === TalkbackKind.Close) {
313
-
const sink: Sink<any> = signal => {
314
-
if (signal === SignalKind.End) {
315
-
signals.push(signal);
316
-
} else if (signal.tag === SignalKind.Push) {
317
-
signals.push(signal);
319
-
signal[0](TalkbackKind.Close);
323
-
operator(source)(sink);
325
-
// The Push signal should've been dropped
326
-
jest.runAllTimers();
327
-
expect(signals).toEqual([]);
331
-
/* This tests an immediately closing operator for End signals to
332
-
the sink and Close signals to the source.
333
-
When an operator closes immediately we expect to see a Close
334
-
signal at the source and an End signal to the sink, since the
335
-
closing operator is expected to end the entire chain. */
336
-
const passesCloseAndEnd = (closingOperator: Operator<any, any>) => {
337
-
it('closes the source and ends the sink correctly (spec: ending operator)', () => {
341
-
const source: Source<any> = sink => {
344
-
// For some operator tests we do need to send a single value
345
-
if (signal === TalkbackKind.Pull) {
354
-
const sink: Sink<any> = signal => {
355
-
if (signal === SignalKind.End) {
357
-
} else if (signal.tag === SignalKind.Start) {
358
-
signal[0](TalkbackKind.Pull);
362
-
// We expect the operator to immediately end and close
363
-
closingOperator(source)(sink);
364
-
expect(closing).toBe(1);
365
-
expect(ending).toBe(1);
369
-
const passesAsyncSequence = (operator: Operator<any, any>, result: any = 0) => {
370
-
it('passes an async push with an async end (spec)', () => {
371
-
let hasPushed = false;
372
-
const signals: Signal<any>[] = [];
374
-
const source: Source<any> = sink => {
377
-
if (signal === TalkbackKind.Pull && !hasPushed) {
379
-
setTimeout(() => sink(push(0)), 10);
380
-
setTimeout(() => sink(SignalKind.End), 20);
386
-
const sink: Sink<any> = signal => {
387
-
if (signal === SignalKind.End) {
388
-
signals.push(signal);
389
-
} else if (signal.tag === SignalKind.Push) {
390
-
signals.push(signal);
393
-
signal[0](TalkbackKind.Pull);
398
-
// We initially expect to see the push signal
399
-
// Afterwards after all timers all other signals come in
400
-
operator(source)(sink);
401
-
expect(signals.length).toBe(0);
402
-
jest.advanceTimersByTime(5);
403
-
expect(hasPushed).toBeTruthy();
404
-
jest.runAllTimers();
406
-
expect(signals).toEqual([push(result), SignalKind.End]);