Mirror: 🎩 A tiny but capable push & pull stream library for TypeScript and Flow
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Move compliance tests to separate utility file (#117)

authored by

Phil Pluckthun and committed by
GitHub
6881788c b8ec16f0

+418 -403
+1
package.json
··· 70 70 ] 71 71 }, 72 72 "jest": { 73 + "testRegex": "(src/.*(\\.|/)(test|spec))\\.ts$", 73 74 "transform": { 74 75 "^.+\\.tsx?$": "@sucrase/jest-plugin" 75 76 }
+404
src/__tests__/compliance.ts
··· 1 + import { Source, Sink, Operator, Signal, SignalKind, TalkbackKind, TalkbackFn } from '../types'; 2 + import { push, start } from '../helpers'; 3 + 4 + /* This tests a noop operator for passive Pull talkback signals. 5 + A Pull will be sent from the sink upwards and should pass through 6 + the operator until the source receives it, which then pushes a 7 + value down. */ 8 + export const passesPassivePull = (operator: Operator<any, any>, output: any = 0) => { 9 + it('responds to Pull talkback signals (spec)', () => { 10 + let talkback: TalkbackFn | null = null; 11 + let pushes = 0; 12 + const values: any[] = []; 13 + 14 + const source: Source<any> = sink => { 15 + sink( 16 + start(signal => { 17 + if (!pushes && signal === TalkbackKind.Pull) { 18 + pushes++; 19 + sink(push(0)); 20 + } 21 + }) 22 + ); 23 + }; 24 + 25 + const sink: Sink<any> = signal => { 26 + expect(signal).not.toBe(SignalKind.End); 27 + if (signal === SignalKind.End) { 28 + /*noop*/ 29 + } else if (signal.tag === SignalKind.Push) { 30 + values.push(signal[0]); 31 + } else { 32 + talkback = signal[0]; 33 + } 34 + }; 35 + 36 + operator(source)(sink); 37 + // The Start signal should always come in immediately 38 + expect(talkback).not.toBe(null); 39 + // No Push signals should be issued initially 40 + expect(values).toEqual([]); 41 + 42 + // When pulling a value we expect an immediate response 43 + talkback!(TalkbackKind.Pull); 44 + jest.runAllTimers(); 45 + expect(values).toEqual([output]); 46 + }); 47 + }; 48 + 49 + /* This tests a noop operator for regular, active Push signals. 50 + A Push will be sent downwards from the source, through the 51 + operator to the sink. Pull events should be let through from 52 + the sink after every Push event. */ 53 + export const passesActivePush = (operator: Operator<any, any>, result: any = 0) => { 54 + it('responds to eager Push signals (spec)', () => { 55 + const values: any[] = []; 56 + let talkback: TalkbackFn | null = null; 57 + let sink: Sink<any> | null = null; 58 + let pulls = 0; 59 + 60 + const source: Source<any> = _sink => { 61 + (sink = _sink)( 62 + start(signal => { 63 + if (signal === TalkbackKind.Pull) pulls++; 64 + }) 65 + ); 66 + }; 67 + 68 + operator(source)(signal => { 69 + expect(signal).not.toBe(SignalKind.End); 70 + if (signal === SignalKind.End) { 71 + /*noop*/ 72 + } else if (signal.tag === SignalKind.Start) { 73 + talkback = signal[0]; 74 + } else if (signal.tag === SignalKind.Push) { 75 + values.push(signal[0]); 76 + talkback!(TalkbackKind.Pull); 77 + } 78 + }); 79 + 80 + // No Pull signals should be issued initially 81 + expect(pulls).toBe(0); 82 + 83 + // When pushing a value we expect an immediate response 84 + sink!(push(0)); 85 + jest.runAllTimers(); 86 + expect(values).toEqual([result]); 87 + // Subsequently the Pull signal should have travelled upwards 88 + expect(pulls).toBe(1); 89 + }); 90 + }; 91 + 92 + /* This tests a noop operator for Close talkback signals from the sink. 93 + A Close signal will be sent, which should be forwarded to the source, 94 + which then ends the communication without sending an End signal. */ 95 + export const passesSinkClose = (operator: Operator<any, any>) => { 96 + it('responds to Close signals from sink (spec)', () => { 97 + let talkback: TalkbackFn | null = null; 98 + let closing = 0; 99 + 100 + const source: Source<any> = sink => { 101 + sink( 102 + start(signal => { 103 + if (signal === TalkbackKind.Pull && !closing) { 104 + sink(push(0)); 105 + } else if (signal === TalkbackKind.Close) { 106 + closing++; 107 + } 108 + }) 109 + ); 110 + }; 111 + 112 + const sink: Sink<any> = signal => { 113 + expect(signal).not.toBe(SignalKind.End); 114 + if (signal === SignalKind.End) { 115 + /*noop*/ 116 + } else if (signal.tag === SignalKind.Push) { 117 + talkback!(TalkbackKind.Close); 118 + } else { 119 + talkback = signal[0]; 120 + } 121 + }; 122 + 123 + operator(source)(sink); 124 + 125 + // When pushing a value we expect an immediate close signal 126 + talkback!(TalkbackKind.Pull); 127 + jest.runAllTimers(); 128 + expect(closing).toBe(1); 129 + }); 130 + }; 131 + 132 + /* This tests a noop operator for End signals from the source. 133 + A Push and End signal will be sent after the first Pull talkback 134 + signal from the sink, which shouldn't lead to any extra Close or Pull 135 + talkback signals. */ 136 + export const passesSourceEnd = (operator: Operator<any, any>, result: any = 0) => { 137 + it('passes on immediate Push then End signals from source (spec)', () => { 138 + const signals: Signal<any>[] = []; 139 + let talkback: TalkbackFn | null = null; 140 + let pulls = 0; 141 + let ending = 0; 142 + 143 + const source: Source<any> = sink => { 144 + sink( 145 + start(signal => { 146 + expect(signal).not.toBe(TalkbackKind.Close); 147 + if (signal === TalkbackKind.Pull) { 148 + pulls++; 149 + if (pulls === 1) { 150 + sink(push(0)); 151 + sink(SignalKind.End); 152 + } 153 + } 154 + }) 155 + ); 156 + }; 157 + 158 + const sink: Sink<any> = signal => { 159 + if (signal === SignalKind.End) { 160 + signals.push(signal); 161 + ending++; 162 + } else if (signal.tag === SignalKind.Push) { 163 + signals.push(signal); 164 + } else { 165 + talkback = signal[0]; 166 + } 167 + }; 168 + 169 + operator(source)(sink); 170 + 171 + // When pushing a value we expect an immediate Push then End signal 172 + talkback!(TalkbackKind.Pull); 173 + jest.runAllTimers(); 174 + expect(ending).toBe(1); 175 + expect(signals).toEqual([push(result), SignalKind.End]); 176 + // Also no additional pull event should be created by the operator 177 + expect(pulls).toBe(1); 178 + }); 179 + }; 180 + 181 + /* This tests a noop operator for End signals from the source 182 + after the first pull in response to another. 183 + This is similar to passesSourceEnd but more well behaved since 184 + mergeMap/switchMap/concatMap are eager operators. */ 185 + export const passesSourcePushThenEnd = (operator: Operator<any, any>, result: any = 0) => { 186 + it('passes on End signals from source (spec)', () => { 187 + const signals: Signal<any>[] = []; 188 + let talkback: TalkbackFn | null = null; 189 + let pulls = 0; 190 + let ending = 0; 191 + 192 + const source: Source<any> = sink => { 193 + sink( 194 + start(signal => { 195 + expect(signal).not.toBe(TalkbackKind.Close); 196 + if (signal === TalkbackKind.Pull) { 197 + pulls++; 198 + if (pulls <= 2) { 199 + sink(push(0)); 200 + } else { 201 + sink(SignalKind.End); 202 + } 203 + } 204 + }) 205 + ); 206 + }; 207 + 208 + const sink: Sink<any> = signal => { 209 + if (signal === SignalKind.End) { 210 + signals.push(signal); 211 + ending++; 212 + } else if (signal.tag === SignalKind.Push) { 213 + signals.push(signal); 214 + talkback!(TalkbackKind.Pull); 215 + } else { 216 + talkback = signal[0]; 217 + } 218 + }; 219 + 220 + operator(source)(sink); 221 + 222 + // When pushing a value we expect an immediate Push then End signal 223 + talkback!(TalkbackKind.Pull); 224 + jest.runAllTimers(); 225 + expect(ending).toBe(1); 226 + expect(pulls).toBe(3); 227 + expect(signals).toEqual([push(result), push(result), SignalKind.End]); 228 + }); 229 + }; 230 + 231 + /* This tests a noop operator for Start signals from the source. 232 + When the operator's sink is started by the source it'll receive 233 + a Start event. As a response it should never send more than one 234 + Start signals to the sink. */ 235 + export const passesSingleStart = (operator: Operator<any, any>) => { 236 + it('sends a single Start event to the incoming sink (spec)', () => { 237 + let starts = 0; 238 + 239 + const source: Source<any> = sink => { 240 + sink(start(() => {})); 241 + }; 242 + 243 + const sink: Sink<any> = signal => { 244 + if (signal !== SignalKind.End && signal.tag === SignalKind.Start) { 245 + starts++; 246 + } 247 + }; 248 + 249 + // When starting the operator we expect a single start event on the sink 250 + operator(source)(sink); 251 + expect(starts).toBe(1); 252 + }); 253 + }; 254 + 255 + /* This tests a noop operator for silence after End signals from the source. 256 + When the operator receives the End signal it shouldn't forward any other 257 + signals to the sink anymore. 258 + This isn't a strict requirement, but some operators should ensure that 259 + all sources are well behaved. This is particularly true for operators 260 + that either Close sources themselves or may operate on multiple sources. */ 261 + export const passesStrictEnd = (operator: Operator<any, any>) => { 262 + it('stops all signals after End has been received (spec: strict end)', () => { 263 + let pulls = 0; 264 + const signals: Signal<any>[] = []; 265 + 266 + const source: Source<any> = sink => { 267 + sink( 268 + start(signal => { 269 + if (signal === TalkbackKind.Pull) { 270 + pulls++; 271 + sink(SignalKind.End); 272 + sink(push(123)); 273 + } 274 + }) 275 + ); 276 + }; 277 + 278 + const sink: Sink<any> = signal => { 279 + if (signal === SignalKind.End) { 280 + signals.push(signal); 281 + } else if (signal.tag === SignalKind.Push) { 282 + signals.push(signal); 283 + } else { 284 + signal[0](TalkbackKind.Pull); 285 + } 286 + }; 287 + 288 + operator(source)(sink); 289 + 290 + // The Push signal should've been dropped 291 + jest.runAllTimers(); 292 + expect(signals).toEqual([SignalKind.End]); 293 + expect(pulls).toBe(1); 294 + }); 295 + 296 + it('stops all signals after Close has been received (spec: strict close)', () => { 297 + const signals: Signal<any>[] = []; 298 + 299 + const source: Source<any> = sink => { 300 + sink( 301 + start(signal => { 302 + if (signal === TalkbackKind.Close) { 303 + sink(push(123)); 304 + } 305 + }) 306 + ); 307 + }; 308 + 309 + const sink: Sink<any> = signal => { 310 + if (signal === SignalKind.End) { 311 + signals.push(signal); 312 + } else if (signal.tag === SignalKind.Push) { 313 + signals.push(signal); 314 + } else { 315 + signal[0](TalkbackKind.Close); 316 + } 317 + }; 318 + 319 + operator(source)(sink); 320 + 321 + // The Push signal should've been dropped 322 + jest.runAllTimers(); 323 + expect(signals).toEqual([]); 324 + }); 325 + }; 326 + 327 + /* This tests an immediately closing operator for End signals to 328 + the sink and Close signals to the source. 329 + When an operator closes immediately we expect to see a Close 330 + signal at the source and an End signal to the sink, since the 331 + closing operator is expected to end the entire chain. */ 332 + export const passesCloseAndEnd = (closingOperator: Operator<any, any>) => { 333 + it('closes the source and ends the sink correctly (spec: ending operator)', () => { 334 + let closing = 0; 335 + let ending = 0; 336 + 337 + const source: Source<any> = sink => { 338 + sink( 339 + start(signal => { 340 + // For some operator tests we do need to send a single value 341 + if (signal === TalkbackKind.Pull) { 342 + sink(push(null)); 343 + } else { 344 + closing++; 345 + } 346 + }) 347 + ); 348 + }; 349 + 350 + const sink: Sink<any> = signal => { 351 + if (signal === SignalKind.End) { 352 + ending++; 353 + } else if (signal.tag === SignalKind.Start) { 354 + signal[0](TalkbackKind.Pull); 355 + } 356 + }; 357 + 358 + // We expect the operator to immediately end and close 359 + closingOperator(source)(sink); 360 + expect(closing).toBe(1); 361 + expect(ending).toBe(1); 362 + }); 363 + }; 364 + 365 + export const passesAsyncSequence = (operator: Operator<any, any>, result: any = 0) => { 366 + it('passes an async push with an async end (spec)', () => { 367 + let hasPushed = false; 368 + const signals: Signal<any>[] = []; 369 + 370 + const source: Source<any> = sink => { 371 + sink( 372 + start(signal => { 373 + if (signal === TalkbackKind.Pull && !hasPushed) { 374 + hasPushed = true; 375 + setTimeout(() => sink(push(0)), 10); 376 + setTimeout(() => sink(SignalKind.End), 20); 377 + } 378 + }) 379 + ); 380 + }; 381 + 382 + const sink: Sink<any> = signal => { 383 + if (signal === SignalKind.End) { 384 + signals.push(signal); 385 + } else if (signal.tag === SignalKind.Push) { 386 + signals.push(signal); 387 + } else { 388 + setTimeout(() => { 389 + signal[0](TalkbackKind.Pull); 390 + }, 5); 391 + } 392 + }; 393 + 394 + // We initially expect to see the push signal 395 + // Afterwards after all timers all other signals come in 396 + operator(source)(sink); 397 + expect(signals.length).toBe(0); 398 + jest.advanceTimersByTime(5); 399 + expect(hasPushed).toBeTruthy(); 400 + jest.runAllTimers(); 401 + 402 + expect(signals).toEqual([push(result), SignalKind.End]); 403 + }); 404 + };
+13 -403
src/__tests__/operators.test.ts
··· 1 - import { Source, Sink, Operator, Signal, SignalKind, TalkbackKind, TalkbackFn } from '../types'; 1 + import { Source, Sink, Signal, SignalKind, TalkbackKind, TalkbackFn } from '../types'; 2 2 import { push, start } from '../helpers'; 3 3 4 + import { 5 + passesPassivePull, 6 + passesActivePush, 7 + passesSinkClose, 8 + passesSourceEnd, 9 + passesSingleStart, 10 + passesStrictEnd, 11 + passesSourcePushThenEnd, 12 + passesAsyncSequence, 13 + passesCloseAndEnd, 14 + } from './compliance'; 15 + 4 16 import * as sources from '../sources'; 5 17 import * as sinks from '../sinks'; 6 18 import * as operators from '../operators'; 7 - 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 11 - value down. */ 12 - const passesPassivePull = (operator: Operator<any, any>, output: any = 0) => { 13 - it('responds to Pull talkback signals (spec)', () => { 14 - let talkback: TalkbackFn | null = null; 15 - let pushes = 0; 16 - const values: any[] = []; 17 - 18 - const source: Source<any> = sink => { 19 - sink( 20 - start(signal => { 21 - if (!pushes && signal === TalkbackKind.Pull) { 22 - pushes++; 23 - sink(push(0)); 24 - } 25 - }) 26 - ); 27 - }; 28 - 29 - const sink: Sink<any> = signal => { 30 - expect(signal).not.toBe(SignalKind.End); 31 - if (signal === SignalKind.End) { 32 - /*noop*/ 33 - } else if (signal.tag === SignalKind.Push) { 34 - values.push(signal[0]); 35 - } else { 36 - talkback = signal[0]; 37 - } 38 - }; 39 - 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([]); 45 - 46 - // When pulling a value we expect an immediate response 47 - talkback!(TalkbackKind.Pull); 48 - jest.runAllTimers(); 49 - expect(values).toEqual([output]); 50 - }); 51 - }; 52 - 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; 62 - let pulls = 0; 63 - 64 - const source: Source<any> = _sink => { 65 - (sink = _sink)( 66 - start(signal => { 67 - if (signal === TalkbackKind.Pull) pulls++; 68 - }) 69 - ); 70 - }; 71 - 72 - operator(source)(signal => { 73 - expect(signal).not.toBe(SignalKind.End); 74 - if (signal === SignalKind.End) { 75 - /*noop*/ 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); 81 - } 82 - }); 83 - 84 - // No Pull signals should be issued initially 85 - expect(pulls).toBe(0); 86 - 87 - // When pushing a value we expect an immediate response 88 - sink!(push(0)); 89 - jest.runAllTimers(); 90 - expect(values).toEqual([result]); 91 - // Subsequently the Pull signal should have travelled upwards 92 - expect(pulls).toBe(1); 93 - }); 94 - }; 95 - 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; 102 - let closing = 0; 103 - 104 - const source: Source<any> = sink => { 105 - sink( 106 - start(signal => { 107 - if (signal === TalkbackKind.Pull && !closing) { 108 - sink(push(0)); 109 - } else if (signal === TalkbackKind.Close) { 110 - closing++; 111 - } 112 - }) 113 - ); 114 - }; 115 - 116 - const sink: Sink<any> = signal => { 117 - expect(signal).not.toBe(SignalKind.End); 118 - if (signal === SignalKind.End) { 119 - /*noop*/ 120 - } else if (signal.tag === SignalKind.Push) { 121 - talkback!(TalkbackKind.Close); 122 - } else { 123 - talkback = signal[0]; 124 - } 125 - }; 126 - 127 - operator(source)(sink); 128 - 129 - // When pushing a value we expect an immediate close signal 130 - talkback!(TalkbackKind.Pull); 131 - jest.runAllTimers(); 132 - expect(closing).toBe(1); 133 - }); 134 - }; 135 - 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; 144 - let pulls = 0; 145 - let ending = 0; 146 - 147 - const source: Source<any> = sink => { 148 - sink( 149 - start(signal => { 150 - expect(signal).not.toBe(TalkbackKind.Close); 151 - if (signal === TalkbackKind.Pull) { 152 - pulls++; 153 - if (pulls === 1) { 154 - sink(push(0)); 155 - sink(SignalKind.End); 156 - } 157 - } 158 - }) 159 - ); 160 - }; 161 - 162 - const sink: Sink<any> = signal => { 163 - if (signal === SignalKind.End) { 164 - signals.push(signal); 165 - ending++; 166 - } else if (signal.tag === SignalKind.Push) { 167 - signals.push(signal); 168 - } else { 169 - talkback = signal[0]; 170 - } 171 - }; 172 - 173 - operator(source)(sink); 174 - 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); 182 - }); 183 - }; 184 - 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; 193 - let pulls = 0; 194 - let ending = 0; 195 - 196 - const source: Source<any> = sink => { 197 - sink( 198 - start(signal => { 199 - expect(signal).not.toBe(TalkbackKind.Close); 200 - if (signal === TalkbackKind.Pull) { 201 - pulls++; 202 - if (pulls <= 2) { 203 - sink(push(0)); 204 - } else { 205 - sink(SignalKind.End); 206 - } 207 - } 208 - }) 209 - ); 210 - }; 211 - 212 - const sink: Sink<any> = signal => { 213 - if (signal === SignalKind.End) { 214 - signals.push(signal); 215 - ending++; 216 - } else if (signal.tag === SignalKind.Push) { 217 - signals.push(signal); 218 - talkback!(TalkbackKind.Pull); 219 - } else { 220 - talkback = signal[0]; 221 - } 222 - }; 223 - 224 - operator(source)(sink); 225 - 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]); 232 - }); 233 - }; 234 - 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)', () => { 241 - let starts = 0; 242 - 243 - const source: Source<any> = sink => { 244 - sink(start(() => {})); 245 - }; 246 - 247 - const sink: Sink<any> = signal => { 248 - if (signal !== SignalKind.End && signal.tag === SignalKind.Start) { 249 - starts++; 250 - } 251 - }; 252 - 253 - // When starting the operator we expect a single start event on the sink 254 - operator(source)(sink); 255 - expect(starts).toBe(1); 256 - }); 257 - }; 258 - 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)', () => { 267 - let pulls = 0; 268 - const signals: Signal<any>[] = []; 269 - 270 - const source: Source<any> = sink => { 271 - sink( 272 - start(signal => { 273 - if (signal === TalkbackKind.Pull) { 274 - pulls++; 275 - sink(SignalKind.End); 276 - sink(push(123)); 277 - } 278 - }) 279 - ); 280 - }; 281 - 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); 287 - } else { 288 - signal[0](TalkbackKind.Pull); 289 - } 290 - }; 291 - 292 - operator(source)(sink); 293 - 294 - // The Push signal should've been dropped 295 - jest.runAllTimers(); 296 - expect(signals).toEqual([SignalKind.End]); 297 - expect(pulls).toBe(1); 298 - }); 299 - 300 - it('stops all signals after Close has been received (spec: strict close)', () => { 301 - const signals: Signal<any>[] = []; 302 - 303 - const source: Source<any> = sink => { 304 - sink( 305 - start(signal => { 306 - if (signal === TalkbackKind.Close) { 307 - sink(push(123)); 308 - } 309 - }) 310 - ); 311 - }; 312 - 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); 318 - } else { 319 - signal[0](TalkbackKind.Close); 320 - } 321 - }; 322 - 323 - operator(source)(sink); 324 - 325 - // The Push signal should've been dropped 326 - jest.runAllTimers(); 327 - expect(signals).toEqual([]); 328 - }); 329 - }; 330 - 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)', () => { 338 - let closing = 0; 339 - let ending = 0; 340 - 341 - const source: Source<any> = sink => { 342 - sink( 343 - start(signal => { 344 - // For some operator tests we do need to send a single value 345 - if (signal === TalkbackKind.Pull) { 346 - sink(push(null)); 347 - } else { 348 - closing++; 349 - } 350 - }) 351 - ); 352 - }; 353 - 354 - const sink: Sink<any> = signal => { 355 - if (signal === SignalKind.End) { 356 - ending++; 357 - } else if (signal.tag === SignalKind.Start) { 358 - signal[0](TalkbackKind.Pull); 359 - } 360 - }; 361 - 362 - // We expect the operator to immediately end and close 363 - closingOperator(source)(sink); 364 - expect(closing).toBe(1); 365 - expect(ending).toBe(1); 366 - }); 367 - }; 368 - 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>[] = []; 373 - 374 - const source: Source<any> = sink => { 375 - sink( 376 - start(signal => { 377 - if (signal === TalkbackKind.Pull && !hasPushed) { 378 - hasPushed = true; 379 - setTimeout(() => sink(push(0)), 10); 380 - setTimeout(() => sink(SignalKind.End), 20); 381 - } 382 - }) 383 - ); 384 - }; 385 - 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); 391 - } else { 392 - setTimeout(() => { 393 - signal[0](TalkbackKind.Pull); 394 - }, 5); 395 - } 396 - }; 397 - 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(); 405 - 406 - expect(signals).toEqual([push(result), SignalKind.End]); 407 - }); 408 - }; 409 19 410 20 beforeEach(() => { 411 21 jest.useFakeTimers();