Skip to content

Commit beb1ba1

Browse files
committed
Restructure tests
1 parent 4244ec6 commit beb1ba1

File tree

6 files changed

+335
-207
lines changed

6 files changed

+335
-207
lines changed

index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ export function map (input, callback, options) {
183183
'return': () => markAsEnded(),
184184
// TODO: Add "throw", see reference in https://tc39.es/ecma262/ ? And https://twitter.com/matteocollina/status/1392056117128306691
185185
'throw': async (err) => {
186+
// FIXME: Should remember the throw? And return a rejected promise always?
186187
markAsEnded();
187188
throw err;
188189
},

test/return.spec.js

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import chai from 'chai';
2+
import chaiAsPromised from 'chai-as-promised';
3+
import chaiQuantifiers from 'chai-quantifiers';
4+
import sinon from 'sinon';
5+
import sinonChai from 'sinon-chai';
6+
7+
import {
8+
map as bufferAsyncIterable,
9+
} from '../index.js';
10+
11+
import {
12+
yieldValuesOverTime,
13+
} from './utils.js';
14+
15+
chai.use(chaiAsPromised);
16+
chai.use(chaiQuantifiers);
17+
chai.use(sinonChai);
18+
19+
chai.should();
20+
21+
describe('bufferAsyncIterable() AsyncInterface return()', () => {
22+
const count = 6;
23+
24+
/** @type {import('sinon').SinonFakeTimers} */
25+
let clock;
26+
/** @type {AsyncIterable<number>} */
27+
let baseAsyncIterable;
28+
/** @type {number[]} */
29+
let expectedResult;
30+
31+
beforeEach(() => {
32+
clock = sinon.useFakeTimers();
33+
baseAsyncIterable = yieldValuesOverTime(count, (i) => i % 2 === 1 ? 2000 : 100);
34+
35+
expectedResult = [];
36+
for (let i = 0; i < count; i++) {
37+
expectedResult.push(i);
38+
}
39+
});
40+
41+
afterEach(() => {
42+
sinon.restore();
43+
});
44+
45+
it('should end the iterator when called', async () => {
46+
const iterator = bufferAsyncIterable(baseAsyncIterable, async (item) => item);
47+
48+
await iterator.next().should.eventually.deep.equal({ value: 0 });
49+
50+
const returnValue = iterator.return();
51+
const nextAfterReturn = iterator.next();
52+
53+
await clock.runAllAsync();
54+
55+
await returnValue.should.eventually.deep.equal({ done: true, value: undefined });
56+
await nextAfterReturn.should.eventually.deep.equal({ done: true, value: undefined });
57+
});
58+
59+
it('should be called when a loop breaks', async () => {
60+
const iterator = bufferAsyncIterable(baseAsyncIterable, async (item) => item, { queueSize: 3 });
61+
const returnSpy = sinon.spy(iterator, 'return');
62+
const throwSpy = sinon.spy(iterator, 'throw');
63+
64+
const promisedResult = (async () => {
65+
// eslint-disable-next-line no-unreachable-loop
66+
for await (const value of iterator) {
67+
value.should.equal(0);
68+
break;
69+
}
70+
71+
return Date.now();
72+
})();
73+
74+
await clock.runAllAsync();
75+
76+
const duration = await promisedResult;
77+
78+
// TODO: Do we need an await here?
79+
await iterator.next().should.eventually.deep.equal({ done: true, value: undefined });
80+
duration.should.equal(2200);
81+
82+
returnSpy.should.have.been.calledOnceWithExactly();
83+
throwSpy.should.not.have.been.called;
84+
});
85+
86+
it('should be called when a loop throws', async () => {
87+
const iterator = bufferAsyncIterable(baseAsyncIterable, async (item) => item);
88+
const returnSpy = sinon.spy(iterator, 'return');
89+
const throwSpy = sinon.spy(iterator, 'throw');
90+
const errorToThrow = new Error('Yet another error');
91+
92+
const promisedResult = (async () => {
93+
// eslint-disable-next-line no-unreachable-loop
94+
for await (const value of iterator) {
95+
value.should.equal(0);
96+
throw errorToThrow;
97+
}
98+
99+
return Date.now();
100+
})();
101+
102+
await clock.runAllAsync();
103+
104+
returnSpy.should.have.been.calledOnceWithExactly();
105+
throwSpy.should.not.have.been.called;
106+
107+
await promisedResult.should.eventually.be.rejectedWith(errorToThrow);
108+
await iterator.next().should.eventually.deep.equal({ done: true, value: undefined });
109+
});
110+
});

test/throw.spec.js

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import chai from 'chai';
2+
import chaiAsPromised from 'chai-as-promised';
3+
import chaiQuantifiers from 'chai-quantifiers';
4+
import sinon from 'sinon';
5+
import sinonChai from 'sinon-chai';
6+
7+
import {
8+
map as bufferAsyncIterable,
9+
} from '../index.js';
10+
11+
import {
12+
yieldValuesOverTime,
13+
} from './utils.js';
14+
15+
chai.use(chaiAsPromised);
16+
chai.use(chaiQuantifiers);
17+
chai.use(sinonChai);
18+
19+
chai.should();
20+
21+
describe('bufferAsyncIterable() AsyncInterface throw()', () => {
22+
const count = 6;
23+
24+
/** @type {AsyncIterable<number>} */
25+
let baseAsyncIterable;
26+
/** @type {number[]} */
27+
let expectedResult;
28+
29+
beforeEach(() => {
30+
baseAsyncIterable = yieldValuesOverTime(count, (i) => i % 2 === 1 ? 2000 : 100);
31+
32+
expectedResult = [];
33+
for (let i = 0; i < count; i++) {
34+
expectedResult.push(i);
35+
}
36+
});
37+
38+
it('should end the iterator when called', async () => {
39+
const errorToThrow = new Error('Yet another error');
40+
41+
const iterator = bufferAsyncIterable(baseAsyncIterable, async (item) => item);
42+
43+
await iterator.next().should.eventually.deep.equal({ value: 0 });
44+
await iterator.throw(errorToThrow).should.eventually.be.rejectedWith(errorToThrow);
45+
await iterator.next().should.eventually.deep.equal({ done: true, value: undefined });
46+
});
47+
48+
it('should be called when a loop throws', async () => {
49+
const iterator = bufferAsyncIterable(baseAsyncIterable, async (item) => item);
50+
const returnSpy = sinon.spy(iterator, 'return');
51+
const throwSpy = sinon.spy(iterator, 'throw');
52+
const errorToThrow = new Error('Yet another error');
53+
54+
let caught;
55+
56+
// Inspired by https://github.com/WebKit/WebKit/blob/1a09d8d95ba6085df4ef44306c4bfc9fc86fdbc7/JSTests/test262/test/language/expressions/yield/star-rhs-iter-thrw-thrw-get-err.js
57+
async function * g () {
58+
try {
59+
yield * iterator;
60+
} catch (err) {
61+
caught = err;
62+
throw err;
63+
}
64+
}
65+
66+
const wrappedIterator = g();
67+
68+
await wrappedIterator.next().should.eventually.deep.equal({ done: false, value: 0 });
69+
await wrappedIterator.throw(errorToThrow).should.eventually.be.rejectedWith(errorToThrow);
70+
await wrappedIterator.next().should.eventually.deep.equal({ done: true, value: undefined });
71+
await iterator.next().should.eventually.deep.equal({ done: true, value: undefined });
72+
73+
(caught || {}).should.equal(errorToThrow);
74+
throwSpy.should.have.been.calledOnceWithExactly(errorToThrow);
75+
returnSpy.should.not.have.been.called;
76+
});
77+
});

test/utils.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* @param {unknown} item
3+
* @returns {boolean}
4+
*/
5+
export function isAsyncGenerator (item) {
6+
return item && typeof item === 'object'
7+
? Symbol.toStringTag in item && item[Symbol.toStringTag] === 'AsyncGenerator'
8+
: false;
9+
}
10+
11+
/**
12+
* @param {number} delay
13+
* @returns {Promise<void>}
14+
*/
15+
export function promisableTimeout (delay) {
16+
return new Promise(resolve => setTimeout(resolve, delay));
17+
}
18+
19+
/**
20+
* @param {number} count
21+
* @param {number|((i: number) => number)} wait
22+
* @returns {AsyncIterable<number>}
23+
*/
24+
export async function * yieldValuesOverTime (count, wait) {
25+
const waitCallback = typeof wait === 'number' ? () => wait : wait;
26+
for (let i = 0; i < count; i++) {
27+
yield i;
28+
await promisableTimeout(waitCallback(i));
29+
}
30+
}
31+
32+
/**
33+
* @param {number} count
34+
* @param {number|((i: number) => number)} wait
35+
* @param {string} prefix
36+
* @returns {AsyncIterable<string>}
37+
*/
38+
export async function * yieldValuesOverTimeWithPrefix (count, wait, prefix) {
39+
const waitCallback = typeof wait === 'number' ? () => wait : wait;
40+
for (let i = 0; i < count; i++) {
41+
yield prefix + i;
42+
await promisableTimeout(waitCallback(i));
43+
}
44+
}

test/utils.spec.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import chai from 'chai';
2+
import chaiAsPromised from 'chai-as-promised';
3+
import chaiQuantifiers from 'chai-quantifiers';
4+
import sinon from 'sinon';
5+
import sinonChai from 'sinon-chai';
6+
7+
import {
8+
yieldValuesOverTime,
9+
} from './utils.js';
10+
11+
chai.use(chaiAsPromised);
12+
chai.use(chaiQuantifiers);
13+
chai.use(sinonChai);
14+
15+
chai.should();
16+
17+
describe('yieldValuesOverTime()', () => {
18+
const count = 6;
19+
20+
/** @type {import('sinon').SinonFakeTimers} */
21+
let clock;
22+
/** @type {AsyncIterable<number>} */
23+
let baseAsyncIterable;
24+
/** @type {number[]} */
25+
let expectedResult;
26+
27+
beforeEach(() => {
28+
clock = sinon.useFakeTimers();
29+
baseAsyncIterable = yieldValuesOverTime(count, (i) => i % 2 === 1 ? 2000 : 100);
30+
31+
expectedResult = [];
32+
for (let i = 0; i < count; i++) {
33+
expectedResult.push(i);
34+
}
35+
});
36+
37+
afterEach(() => {
38+
sinon.restore();
39+
});
40+
41+
it('should return all values when looped over ', async () => {
42+
// Create the promise first, then have it be fully executed using clock.runAllAsync()
43+
const promisedResult = (async () => {
44+
/** @type {number[]} */
45+
const rawResult = [];
46+
47+
for await (const value of baseAsyncIterable) {
48+
rawResult.push(value);
49+
}
50+
51+
/** @type {[number[], number]} */
52+
const result = [rawResult, Date.now()];
53+
54+
return result;
55+
})();
56+
57+
await clock.runAllAsync();
58+
59+
const [result, duration] = await promisedResult;
60+
61+
result.should.deep.equal(expectedResult);
62+
duration.should.equal(6300);
63+
});
64+
65+
it('should return all values when accessed directly ', async () => {
66+
// Create the promise first, then have it be fully executed using clock.runAllAsync()
67+
const promisedResult = (async () => {
68+
/** @type {AsyncIterator<number, void>} */
69+
const asyncIterator = baseAsyncIterable[Symbol.asyncIterator]();
70+
71+
/** @type {Promise<IteratorResult<number, void>>[]} */
72+
const iterations = [];
73+
74+
for (let i = 0; i < count; i++) {
75+
iterations.push(asyncIterator.next());
76+
}
77+
78+
const rawResult = await Promise.all(iterations);
79+
80+
/** @type {[(number|void)[], number]} */
81+
const result = [
82+
rawResult.map(item => item.value),
83+
Date.now(),
84+
];
85+
86+
return result;
87+
})();
88+
89+
await clock.runAllAsync();
90+
91+
const [result, duration] = await promisedResult;
92+
93+
result.should.deep.equal(expectedResult);
94+
duration.should.equal(4300);
95+
});
96+
});

0 commit comments

Comments
 (0)