Skip to content

Commit c5e1ec0

Browse files
committed
test: refactor unit tests to be more clear
1 parent a82228b commit c5e1ec0

File tree

1 file changed

+133
-114
lines changed

1 file changed

+133
-114
lines changed

test/unit/utils.test.js

Lines changed: 133 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -41,133 +41,152 @@ describe('utils', function () {
4141
});
4242
});
4343

44-
context('makeInterruptibleAsyncInterval', function () {
45-
before(function () {
46-
this.clock = sinon.useFakeTimers();
47-
});
44+
describe('#makeInterruptibleAsyncInterval', function () {
45+
let clock;
4846

49-
after(function () {
50-
this.clock.restore();
47+
beforeEach(function () {
48+
clock = sinon.useFakeTimers();
5149
});
5250

53-
it('should execute a method in an repeating interval', function (done) {
54-
let lastTime = now();
55-
const marks = [];
56-
const executor = makeInterruptibleAsyncInterval(
57-
callback => {
58-
marks.push(now() - lastTime);
59-
lastTime = now();
60-
callback();
61-
},
62-
{ interval: 10 }
63-
);
64-
65-
setTimeout(() => {
66-
expect(marks).to.eql([10, 10, 10, 10, 10]);
67-
expect(marks.every(mark => marks[0] === mark)).to.be.true;
68-
executor.stop();
69-
done();
70-
}, 51);
71-
72-
this.clock.tick(51);
51+
afterEach(function () {
52+
clock.restore();
7353
});
7454

75-
it('should schedule execution sooner if requested within min interval threshold', function (done) {
76-
let lastTime = now();
77-
const marks = [];
78-
const executor = makeInterruptibleAsyncInterval(
79-
callback => {
80-
marks.push(now() - lastTime);
81-
lastTime = now();
82-
callback();
83-
},
84-
{ interval: 50, minInterval: 10 }
85-
);
86-
87-
// immediately schedule execution
88-
executor.wake();
89-
90-
setTimeout(() => {
91-
expect(marks).to.eql([10, 50]);
92-
executor.stop();
93-
done();
94-
}, 100);
95-
96-
this.clock.tick(100);
55+
context('when the immediate option is provided', function () {
56+
const fn = callback => {
57+
callback();
58+
};
59+
const fnSpy = sinon.spy(fn);
60+
61+
it('executes the function immediately', function (done) {
62+
const executor = makeInterruptibleAsyncInterval(fnSpy, { immediate: true, interval: 20 });
63+
setTimeout(() => {
64+
// The provided function should be called exactly once, since we wait 10ms
65+
// to perform the assertion and the interval is 20ms, so the executor is
66+
// stopped before the scheduled next call.
67+
expect(fnSpy.calledOnce).to.be.true;
68+
executor.stop();
69+
done();
70+
}, 10);
71+
clock.tick(10);
72+
});
9773
});
9874

99-
it('should debounce multiple requests to wake the interval sooner', function (done) {
100-
let lastTime = now();
101-
const marks = [];
102-
const executor = makeInterruptibleAsyncInterval(
103-
callback => {
104-
marks.push(now() - lastTime);
105-
lastTime = now();
106-
callback();
107-
},
108-
{ interval: 50, minInterval: 10 }
109-
);
110-
111-
for (let i = 0; i < 100; ++i) {
112-
executor.wake();
113-
}
114-
115-
setTimeout(() => {
116-
expect(marks).to.eql([10, 50, 50, 50, 50]);
117-
executor.stop();
118-
done();
119-
}, 250);
120-
121-
this.clock.tick(250);
75+
context('when the immediate option is not provided', function () {
76+
const fn = callback => {
77+
callback();
78+
};
79+
const fnSpy = sinon.spy(fn);
80+
81+
it('executes the function on the provided interval', function (done) {
82+
const executor = makeInterruptibleAsyncInterval(fnSpy, { interval: 10 });
83+
setTimeout(() => {
84+
// The provided function should be called exactly twice, since we wait 21ms
85+
// to perform the assertion and the interval is 10ms, so the executor is
86+
// stopped before the third call.
87+
expect(fnSpy.calledTwice).to.be.true;
88+
executor.stop();
89+
done();
90+
}, 21);
91+
clock.tick(21);
92+
});
12293
});
12394

124-
it('should immediately schedule if the clock is unreliable', function (done) {
125-
let clockCalled = 0;
126-
let lastTime = now();
127-
const marks = [];
128-
const executor = makeInterruptibleAsyncInterval(
129-
callback => {
130-
marks.push(now() - lastTime);
131-
lastTime = now();
95+
describe('#wake', function () {
96+
context('when the time until next call is negative', function () {
97+
const fn = callback => {
13298
callback();
133-
},
134-
{
135-
interval: 50,
136-
minInterval: 10,
137-
immediate: true,
138-
clock() {
139-
clockCalled += 1;
140-
141-
// needs to happen on the third call because `wake` checks
142-
// the `currentTime` at the beginning of the function
143-
// The value of now() is not actually negative in the case of
144-
// the unreliable check so we force to a negative value now
145-
// for this test.
146-
if (clockCalled === 3) {
147-
return -1;
99+
};
100+
const fnSpy = sinon.spy(fn);
101+
102+
it('calls the function immediately', function (done) {
103+
const executor = makeInterruptibleAsyncInterval(fnSpy, {
104+
interval: 10,
105+
clock: () => {
106+
// We have our fake clock return a value that will force
107+
// the time until the next call to be a negative value,
108+
// which will in turn force an immediate execution upon
109+
// wake.
110+
return 11;
148111
}
112+
});
113+
114+
// This will reset the last call time to 0 and ensure the function has
115+
// not been called yet.
116+
executor.stop();
117+
// Now we call our method under test with the expectation it will force
118+
// an immediate execution.
119+
executor.wake();
120+
121+
setTimeout(() => {
122+
// The provided function should be called exactly once in this section.
123+
// This is because we immediately stopped the executor, then force woke
124+
// it to get an immediate call with time until the next call being a
125+
// negative value.
126+
expect(fnSpy.calledOnce).to.be.true;
127+
executor.stop();
128+
done();
129+
}, 10);
130+
clock.tick(11);
131+
});
132+
});
149133

150-
return now();
151-
}
152-
}
153-
);
134+
context('when time since last wake is less than the minimum interval', function () {
135+
const fn = callback => {
136+
callback();
137+
};
138+
const fnSpy = sinon.spy(fn);
139+
140+
it('does not call the function', function (done) {
141+
const executor = makeInterruptibleAsyncInterval(fnSpy, { interval: 10 });
142+
143+
// This will reset the last wake time to 0 and ensure the function has
144+
// not been called yet.
145+
executor.stop();
146+
// Now we call our method under test with the expectation it will not be
147+
// called immediately since our current time is still under the interval
148+
// time.
149+
executor.wake();
150+
151+
setTimeout(() => {
152+
// The provided function should never be called in this case.
153+
// This is because we immediately stopped the executor, then force woke
154+
// it but the current time is still under the interval time.
155+
expect(fnSpy.callCount).to.equal(0);
156+
executor.stop();
157+
done();
158+
}, 9);
159+
clock.tick(9);
160+
});
161+
});
154162

155-
// force mark at 20ms, and then the unreliable system clock
156-
// will report a very stale `lastCallTime` on this mark.
157-
setTimeout(() => executor.wake(), 10);
158-
159-
// try to wake again in another `minInterval + immediate`, now
160-
// using a very old `lastCallTime`. This should result in an
161-
// immediate scheduling: 0ms (immediate), 20ms (wake with minIterval)
162-
// and then 10ms for another immediate.
163-
setTimeout(() => executor.wake(), 30);
164-
165-
setTimeout(() => {
166-
executor.stop();
167-
expect(marks).to.eql([0, 20, 10, 50, 50, 50, 50]);
168-
done();
169-
}, 250);
170-
this.clock.tick(250);
163+
context('when time since last call is greater than the minimum interval', function () {
164+
const fn = callback => {
165+
callback();
166+
};
167+
const fnSpy = sinon.spy(fn);
168+
169+
it('reschedules the function call for the minimum interval', function (done) {
170+
const executor = makeInterruptibleAsyncInterval(fnSpy, {
171+
interval: 50,
172+
minInterval: 10
173+
});
174+
175+
// Calling wake here will force the reschedule to happen at the minimum interval
176+
// provided, which is 10ms.
177+
executor.wake();
178+
179+
setTimeout(() => {
180+
// We expect function calls to happen after 10ms, which is the minimum interval,
181+
// and then in 50ms intervals after that. The second call would happen at 60ms
182+
// time from the original call so we've stopped the executor before a third.
183+
expect(fnSpy.calledTwice).to.be.true;
184+
executor.stop();
185+
done();
186+
}, 61);
187+
clock.tick(61);
188+
});
189+
});
171190
});
172191
});
173192

0 commit comments

Comments
 (0)