1 |
|
2 | 'use strict';
|
3 |
|
4 | let assert = require('assert');
|
5 | let Cycle = require('../lib/index').default;
|
6 | let Rx = require('rx');
|
7 | let sinon = require('sinon');
|
8 |
|
9 | describe('Cycle', function () {
|
10 | it('should have `run`', function () {
|
11 | assert.strictEqual(typeof Cycle.run, 'function');
|
12 | });
|
13 |
|
14 | it('should throw if first argument is not a function', function () {
|
15 | assert.throws(() => {
|
16 | Cycle('not a function');
|
17 | }, /First argument given to Cycle must be the 'main' function/i);
|
18 | });
|
19 |
|
20 | it('should throw if second argument is not an object', function () {
|
21 | assert.throws(() => {
|
22 | Cycle(() => {}, 'not an object');
|
23 | }, /Second argument given to Cycle must be an object with driver functions/i);
|
24 | });
|
25 |
|
26 | it('should throw if second argument is an empty object', function () {
|
27 | assert.throws(() => {
|
28 | Cycle(() => {}, {});
|
29 | }, /Second argument given to Cycle must be an object with at least one/i);
|
30 | });
|
31 |
|
32 | it('should return sinks object and sources object', function () {
|
33 | function app(ext) {
|
34 | return {
|
35 | other: ext.other.take(1).startWith('a')
|
36 | };
|
37 | }
|
38 | function driver() {
|
39 | return Rx.Observable.just('b');
|
40 | }
|
41 | let {sinks, sources} = Cycle(app, {other: driver});
|
42 | assert.strictEqual(typeof sinks, 'object');
|
43 | assert.strictEqual(typeof sinks.other.subscribe, 'function');
|
44 | assert.strictEqual(typeof sources, 'object');
|
45 | assert.notStrictEqual(typeof sources.other, 'undefined');
|
46 | assert.notStrictEqual(sources.other, null);
|
47 | assert.strictEqual(typeof sources.other.subscribe, 'function');
|
48 | });
|
49 |
|
50 | it('should return a run() which in turn returns a dispose()', function (done) {
|
51 | function app(sources) {
|
52 | return {
|
53 | other: sources.other.take(6).map(x => String(x)).startWith('a')
|
54 | };
|
55 | }
|
56 | function driver(sink) {
|
57 | return sink.map(x => x.charCodeAt(0)).delay(1);
|
58 | }
|
59 | let {sinks, sources, run} = Cycle(app, {other: driver});
|
60 | let dispose;
|
61 | sources.other.subscribe(x => {
|
62 | assert.strictEqual(x, 97);
|
63 | dispose();
|
64 | done();
|
65 | });
|
66 | dispose = run();
|
67 | });
|
68 |
|
69 | it('should not work after has been disposed', function (done) {
|
70 | let number$ = Rx.Observable.range(1, 3)
|
71 | .concatMap(x => Rx.Observable.just(x).delay(50));
|
72 | function app() {
|
73 | return {other: number$};
|
74 | }
|
75 | let {sinks, sources, run} = Cycle(app, {
|
76 | other: number$ => number$.map(number => 'x' + number)
|
77 | });
|
78 | let dispose;
|
79 | sources.other.subscribe(function (x) {
|
80 | assert.notStrictEqual(x, 'x3');
|
81 | if (x === 'x2') {
|
82 | dispose();
|
83 | setTimeout(() => {
|
84 | done();
|
85 | }, 100);
|
86 | }
|
87 | });
|
88 | dispose = run();
|
89 | });
|
90 |
|
91 | describe('run()', function () {
|
92 | it('should throw if first argument is not a function', function () {
|
93 | assert.throws(() => {
|
94 | Cycle.run('not a function');
|
95 | }, /First argument given to Cycle must be the 'main' function/i);
|
96 | });
|
97 |
|
98 | it('should throw if second argument is not an object', function () {
|
99 | assert.throws(() => {
|
100 | Cycle.run(() => {}, 'not an object');
|
101 | }, /Second argument given to Cycle must be an object with driver functions/i);
|
102 | });
|
103 |
|
104 | it('should throw if second argument is an empty object', function () {
|
105 | assert.throws(() => {
|
106 | Cycle.run(() => {}, {});
|
107 | }, /Second argument given to Cycle must be an object with at least one/i);
|
108 | });
|
109 |
|
110 | it('should return a dispose function', function () {
|
111 | let sandbox = sinon.sandbox.create();
|
112 | const spy = sandbox.spy();
|
113 | function app(ext) {
|
114 | return {
|
115 | other: ext.other.take(1).startWith('a')
|
116 | };
|
117 | }
|
118 | function driver() {
|
119 | return Rx.Observable.just('b').doOnNext(spy);
|
120 | }
|
121 | let dispose = Cycle.run(app, {other: driver});
|
122 | assert.strictEqual(typeof dispose, 'function');
|
123 | sinon.assert.calledOnce(spy);
|
124 | dispose();
|
125 | });
|
126 |
|
127 | it('should happen synchronously', function (done) {
|
128 | let sandbox = sinon.sandbox.create();
|
129 | const spy = sandbox.spy();
|
130 | function app(sources) {
|
131 | sources.other.subscribe();
|
132 | return {
|
133 | other: Rx.Observable.of(10),
|
134 | };
|
135 | }
|
136 | let mutable = 'correct';
|
137 | function driver(sink) {
|
138 | return sink.map(x => 'a' + 10).doOnNext(x => {
|
139 | assert.strictEqual(x, 'a10');
|
140 | assert.strictEqual(mutable, 'correct');
|
141 | spy();
|
142 | });
|
143 | }
|
144 | Cycle.run(app, {other: driver});
|
145 | mutable = 'wrong';
|
146 | setTimeout(() => {
|
147 | sinon.assert.calledOnce(spy);
|
148 | done();
|
149 | }, 20);
|
150 | });
|
151 |
|
152 | it('should report errors from main() in the console', function (done) {
|
153 | let sandbox = sinon.sandbox.create();
|
154 | sandbox.stub(console, "error");
|
155 |
|
156 | function main(sources) {
|
157 | return {
|
158 | other: sources.other.take(1).startWith('a').map(() => {
|
159 | throw new Error('malfunction');
|
160 | })
|
161 | };
|
162 | }
|
163 | function driver() {
|
164 | return Rx.Observable.just('b');
|
165 | }
|
166 |
|
167 | Cycle.run(main, {other: driver});
|
168 | setTimeout(() => {
|
169 | sinon.assert.calledOnce(console.error);
|
170 | sinon.assert.calledWithExactly(console.error, sinon.match("malfunction"));
|
171 |
|
172 | sandbox.restore();
|
173 | done();
|
174 | }, 20);
|
175 | });
|
176 | });
|
177 | });
|