8 kBPlain TextView Raw
1import { BasicTests } from "../../test/helper/Basic.js";
2import { Loop } from "./Loop.js";
3import { Offline, whenBetween } from "../../test/helper/Offline.js";
4import { expect } from "chai";
5import { noOp } from "../core/util/Interface.js";
6import { Time } from "../core/type/Time.js";
7
8describe("Loop", () => {
9 BasicTests(Loop);
10
11 context("Constructor", () => {
12 it("takes a callback and an interval", () => {
13 return Offline(() => {
14 const callback = noOp;
15 const loop = new Loop(callback, "8n");
16 expect(loop.callback).to.equal(callback);
17 expect(loop.interval).to.equal(Time("8n").valueOf());
18 loop.dispose();
19 });
20 });
21
22 it("can be constructed with no arguments", () => {
23 return Offline(() => {
24 const loop = new Loop();
25 expect(loop.iterations).to.equal(Infinity);
26 loop.dispose();
27 });
28 });
29
30 it("can pass in arguments in options object", () => {
31 return Offline(() => {
32 const callback = noOp;
33 const loop = new Loop({
34 callback: callback,
35 iterations: 4,
36 probability: 0.3,
37 interval: "8t",
38 });
39 expect(loop.callback).to.equal(callback);
40 expect(loop.interval.valueOf()).to.equal(Time("8t").valueOf());
41 expect(loop.iterations).to.equal(4);
42 expect(loop.probability).to.equal(0.3);
43 loop.dispose();
44 });
45 });
46 });
47
48 context("Get/Set", () => {
49 it("can set values with object", () => {
50 return Offline(() => {
51 const callback = noOp;
52 const loop = new Loop();
53 loop.set({
54 callback: callback,
55 iterations: 8,
56 });
57 expect(loop.callback).to.equal(callback);
58 expect(loop.iterations).to.equal(8);
59 loop.dispose();
60 });
61 });
62
63 it("can get/set the humanize and interval values", () => {
64 return Offline(() => {
65 const loop = new Loop();
66 loop.humanize = true;
67 loop.interval = 0.4;
68 expect(loop.humanize).to.be.true;
69 expect(loop.interval).to.be.closeTo(0.4, 0.002);
70 loop.dispose();
71 });
72 });
73
74 it("can set get a the values as an object", () => {
75 return Offline(() => {
76 const callback = noOp;
77 const loop = new Loop({
78 callback: callback,
79 iterations: 4,
80 probability: 0.3,
81 });
82 const values = loop.get();
83 expect(values.iterations).to.equal(4);
84 expect(values.probability).to.equal(0.3);
85 loop.dispose();
86 });
87 });
88 });
89
90 context("Callback", () => {
91 it("does not invoke get invoked until started", () => {
92 return Offline(({ transport }) => {
93 new Loop(() => {
94 throw new Error("shouldn't call this callback");
95 }, "8n");
96 transport.start();
97 }, 0.3);
98 });
99
100 it("is invoked after it's started", () => {
101 let invoked = false;
102 return Offline(({ transport }) => {
103 const loop = new Loop(() => {
104 invoked = true;
105 loop.dispose();
106 }, 0.05).start(0);
107 transport.start();
108 }).then(() => {
109 expect(invoked).to.be.true;
110 });
111 });
112
113 it("passes in the scheduled time to the callback", () => {
114 let invoked = false;
115 return Offline(({ transport }) => {
116 const now = transport.now() + 0.1;
117 const loop = new Loop((time) => {
118 expect(time).to.be.a("number");
119 expect(time - now).to.be.closeTo(0.3, 0.01);
120 loop.dispose();
121 invoked = true;
122 });
123 transport.start(now);
124 loop.start(0.3);
125 }, 0.5).then(() => {
126 expect(invoked).to.be.true;
127 });
128 });
129
130 it("can mute the callback", () => {
131 return Offline(({ transport }) => {
132 const loop = new Loop(() => {
133 throw new Error("shouldn't call this callback");
134 }, "4n").start();
135 loop.mute = true;
136 expect(loop.mute).to.be.true;
137 transport.start();
138 }, 0.4);
139 });
140
141 it("can trigger with some probability", () => {
142 return Offline(({ transport }) => {
143 const loop = new Loop(() => {
144 throw new Error("shouldn't call this callback");
145 }, "4n").start();
146 loop.probability = 0;
147 expect(loop.probability).to.equal(0);
148 transport.start();
149 }, 0.4);
150 });
151 });
152
153 context("Scheduling", () => {
154 it("can be started and stopped multiple times", () => {
155 return Offline(({ transport }) => {
156 const loop = new Loop().start().stop(0.2).start(0.4);
157 transport.start(0);
158 return (time) => {
159 whenBetween(time, 0, 0.19, () => {
160 expect(loop.state).to.equal("started");
161 });
162 whenBetween(time, 0.2, 0.39, () => {
163 expect(loop.state).to.equal("stopped");
164 });
165 whenBetween(time, 0.4, Infinity, () => {
166 expect(loop.state).to.equal("started");
167 });
168 };
169 }, 0.6);
170 });
171
172 it("restarts when transport is restarted", () => {
173 return Offline(({ transport }) => {
174 const note = new Loop().start(0).stop(0.4);
175 transport.start(0).stop(0.5).start(0.55);
176 return (time) => {
177 whenBetween(time, 0, 0.39, () => {
178 expect(note.state).to.equal("started");
179 });
180 whenBetween(time, 0.4, 0.5, () => {
181 expect(note.state).to.equal("stopped");
182 });
183 whenBetween(time, 0.55, 0.8, () => {
184 expect(note.state).to.equal("started");
185 });
186 };
187 }, 1);
188 });
189
190 it("can be cancelled", () => {
191 return Offline(({ transport }) => {
192 const note = new Loop().start(0);
193 expect(note.state).to.equal("started");
194 transport.start();
195
196 let firstStop = false;
197 let restarted = false;
198 const tested = false;
199 return (time) => {
200 // stop the transport
201 if (time > 0.2 && !firstStop) {
202 firstStop = true;
203 transport.stop();
204 note.cancel();
205 }
206 if (time > 0.3 && !restarted) {
207 restarted = true;
208 transport.start();
209 }
210 if (time > 0.4 && !tested) {
211 restarted = true;
212 transport.start();
213 expect(note.state).to.equal("stopped");
214 }
215 };
216 }, 0.5);
217 });
218 });
219
220 context("Looping", () => {
221 it("loops", () => {
222 let callCount = 0;
223 return Offline(({ transport }) => {
224 new Loop({
225 interval: 0.1,
226 callback: () => {
227 callCount++;
228 },
229 }).start(0);
230 transport.start();
231 }, 0.81).then(() => {
232 expect(callCount).to.equal(9);
233 });
234 });
235
236 it("loops for the specified interval", () => {
237 let invoked = false;
238 return Offline(({ transport }) => {
239 let lastCall;
240 new Loop({
241 interval: "8n",
242 callback: (time) => {
243 if (lastCall) {
244 invoked = true;
245 expect(time - lastCall).to.be.closeTo(0.25, 0.01);
246 }
247 lastCall = time;
248 },
249 }).start(0);
250 transport.start();
251 }, 1).then(() => {
252 expect(invoked).to.be.true;
253 });
254 });
255
256 it("can loop a specific number of iterations", () => {
257 let callCount = 0;
258 return Offline(({ transport }) => {
259 new Loop({
260 interval: 0.1,
261 iterations: 2,
262 callback: () => {
263 callCount++;
264 },
265 }).start(0);
266 transport.start();
267 }, 0.4).then(() => {
268 expect(callCount).to.equal(2);
269 });
270 });
271
272 it("reports the progress of the loop", () => {
273 return Offline(({ transport }) => {
274 const loop = new Loop({
275 interval: 1,
276 }).start(0);
277 transport.start();
278 return (time) => {
279 expect(loop.progress).to.be.closeTo(time, 0.05);
280 };
281 }, 0.8);
282 });
283 });
284
285 context("playbackRate", () => {
286 it("can adjust the playbackRate", () => {
287 let invoked = false;
288 return Offline(({ transport }) => {
289 let lastCall;
290 const loop = new Loop({
291 playbackRate: 2,
292 interval: 0.5,
293 callback: (time) => {
294 if (lastCall) {
295 invoked = true;
296 expect(time - lastCall).to.be.closeTo(0.25, 0.01);
297 }
298 lastCall = time;
299 },
300 }).start(0);
301 expect(loop.playbackRate).to.equal(2);
302 transport.start();
303 }, 0.7).then(() => {
304 expect(invoked).to.be.true;
305 });
306 });
307
308 it("can playback at a faster rate", () => {
309 let callCount = 0;
310 return Offline(({ transport }) => {
311 const loop = new Loop({
312 interval: 0.1,
313 callback: () => {
314 callCount++;
315 },
316 }).start(0);
317 loop.playbackRate = 1.5;
318 expect(loop.playbackRate).to.equal(1.5);
319 transport.start();
320 }, 0.81).then(() => {
321 expect(callCount).to.equal(13);
322 });
323 });
324 });
325});