1 |
|
2 | import 'mocha';
|
3 | import * as assert from 'assert';
|
4 | import * as sinon from 'sinon';
|
5 | import {setupReusable} from '../src/index';
|
6 | import xs, {Stream} from 'xstream';
|
7 |
|
8 | describe('setupReusable', function() {
|
9 | it('should be a function', function() {
|
10 | assert.strictEqual(typeof setupReusable, 'function');
|
11 | });
|
12 |
|
13 | it('should throw if argument is not object', function() {
|
14 | assert.throws(() => {
|
15 | (setupReusable as any)('not a function');
|
16 | }, /Argument given to setupReusable must be an object with driver/i);
|
17 | });
|
18 |
|
19 | it('should throw if argument is an empty object', function() {
|
20 | assert.throws(() => {
|
21 | (setupReusable as any)({});
|
22 | }, /Argument given to setupReusable must be an object with at least one/i);
|
23 | });
|
24 |
|
25 | it('should return engine with sources and run', function() {
|
26 | function app(ext: any): any {
|
27 | return {
|
28 | other: ext.other.take(1).startWith('a'),
|
29 | };
|
30 | }
|
31 | function driver() {
|
32 | return xs.of('b');
|
33 | }
|
34 | const {sources, run} = setupReusable({other: driver});
|
35 | assert.strictEqual(typeof sources, 'object');
|
36 | assert.notStrictEqual(typeof sources.other, 'undefined');
|
37 | assert.notStrictEqual(sources.other, null);
|
38 | assert.strictEqual(typeof sources.other.addListener, 'function');
|
39 | assert.strictEqual(typeof run, 'function');
|
40 | });
|
41 |
|
42 | it('should return an engine, which we can run and dispose', function() {
|
43 | const sandbox = sinon.createSandbox();
|
44 | const spy = sandbox.spy();
|
45 |
|
46 | type NiceSources = {
|
47 | other: Stream<string>;
|
48 | };
|
49 | type NiceSinks = {
|
50 | other: Stream<string>;
|
51 | };
|
52 |
|
53 | function app(sources: NiceSources): NiceSinks {
|
54 | return {
|
55 | other: sources.other.take(1).startWith('a'),
|
56 | };
|
57 | }
|
58 |
|
59 | function driver(sink: Stream<string>) {
|
60 | return xs.of('b').debug(spy);
|
61 | }
|
62 |
|
63 | const engine = setupReusable({other: driver});
|
64 | const sinks = app(engine.sources);
|
65 | const dispose = engine.run(sinks);
|
66 | assert.strictEqual(typeof dispose, 'function');
|
67 | sinon.assert.calledOnce(spy);
|
68 | dispose();
|
69 | });
|
70 |
|
71 | it('should allow reusing drivers for many apps', function(done) {
|
72 | const sandbox = sinon.createSandbox();
|
73 | const spy1 = sandbox.spy();
|
74 | const spy2 = sandbox.spy();
|
75 |
|
76 | type NiceSources = {
|
77 | other: Stream<string>;
|
78 | };
|
79 | type NiceSinks = {
|
80 | other: Stream<string>;
|
81 | };
|
82 |
|
83 | function app1(sources: NiceSources): NiceSinks {
|
84 | return {
|
85 | other: sources.other.mapTo('a').debug(spy1),
|
86 | };
|
87 | }
|
88 |
|
89 | function app2(sources: NiceSources): NiceSinks {
|
90 | return {
|
91 | other: sources.other.mapTo('x').debug(spy2),
|
92 | };
|
93 | }
|
94 |
|
95 | let sinkCompleted = 0;
|
96 | function driver(sink: Stream<string>) {
|
97 | sink.addListener({
|
98 | complete: () => {
|
99 | sinkCompleted++;
|
100 | done(
|
101 | new Error('complete should not be called before engine is before')
|
102 | );
|
103 | },
|
104 | });
|
105 | return xs.of('b');
|
106 | }
|
107 |
|
108 | const engine = setupReusable({other: driver});
|
109 |
|
110 | const dispose1 = engine.run(app1(engine.sources));
|
111 | sinon.assert.calledOnce(spy1);
|
112 | sinon.assert.calledWithExactly(spy1, 'a');
|
113 | sandbox.restore();
|
114 | dispose1();
|
115 |
|
116 | const dispose2 = engine.run(app2(engine.sources));
|
117 | sinon.assert.calledOnce(spy2);
|
118 | sinon.assert.calledWithExactly(spy2, 'x');
|
119 | dispose2();
|
120 | assert.strictEqual(sinkCompleted, 0);
|
121 | done();
|
122 | });
|
123 |
|
124 | it('should allow disposing the engine, stopping reusability', function(done) {
|
125 | const sandbox = sinon.createSandbox();
|
126 | const spy = sandbox.spy();
|
127 |
|
128 | type NiceSources = {
|
129 | other: Stream<string>;
|
130 | };
|
131 | type NiceSinks = {
|
132 | other: Stream<string>;
|
133 | };
|
134 |
|
135 | function app(sources: NiceSources): NiceSinks {
|
136 | return {
|
137 | other: sources.other.mapTo('a').debug(spy),
|
138 | };
|
139 | }
|
140 |
|
141 | let sinkCompleted = 0;
|
142 | function driver(sink: Stream<string>) {
|
143 | sink.addListener({
|
144 | complete: () => {
|
145 | sinkCompleted++;
|
146 | },
|
147 | });
|
148 | return xs.of('b');
|
149 | }
|
150 |
|
151 | const engine = setupReusable({other: driver});
|
152 |
|
153 | engine.run(app(engine.sources));
|
154 | sinon.assert.calledOnce(spy);
|
155 | sinon.assert.calledWithExactly(spy, 'a');
|
156 | sandbox.restore();
|
157 | engine.dispose();
|
158 | assert.strictEqual(sinkCompleted, 1);
|
159 | done();
|
160 | });
|
161 |
|
162 | it('should report errors from main() in the console', function(done) {
|
163 | const sandbox = sinon.createSandbox();
|
164 | sandbox.stub(console, 'error');
|
165 |
|
166 | function main(sources: any): any {
|
167 | return {
|
168 | other: sources.other
|
169 | .take(1)
|
170 | .startWith('a')
|
171 | .map(() => {
|
172 | throw new Error('malfunction');
|
173 | }),
|
174 | };
|
175 | }
|
176 | function driver(sink: Stream<any>) {
|
177 | sink.addListener({
|
178 | next: () => {},
|
179 | error: (err: any) => {},
|
180 | });
|
181 | return xs.of('b');
|
182 | }
|
183 |
|
184 | let caught = false;
|
185 | const engine = setupReusable({other: driver});
|
186 | try {
|
187 | const sinks = main(engine.sources);
|
188 | engine.run(sinks);
|
189 | } catch (e) {
|
190 | caught = true;
|
191 | }
|
192 | setTimeout(() => {
|
193 | sinon.assert.calledOnce(console.error as any);
|
194 | sinon.assert.calledWithExactly(
|
195 | console.error as any,
|
196 | sinon.match((err: any) => err.message === 'malfunction')
|
197 | );
|
198 |
|
199 |
|
200 |
|
201 | assert.strictEqual(caught, false);
|
202 |
|
203 | sandbox.restore();
|
204 | done();
|
205 | }, 80);
|
206 | });
|
207 | });
|