Skip to content

Commit e738a14

Browse files
authored
Merge pull request #72 from Sykander/async-reduce-specs
Async Reduce Spec
2 parents 72e0069 + 8fc043d commit e738a14

13 files changed

+318
-37
lines changed

src/async-array.js

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const { asyncMap, asyncMapOverIterable } = require('./async-map'),
66
asyncFindIndex,
77
asyncFindIndexOnIterable
88
} = require('./async-find-index'),
9+
{ asyncReduce, asyncReduceIterable } = require('./async-reduce'),
910
{ asyncSort, asyncSortIterable } = require('./async-sort');
1011

1112
/**
@@ -16,20 +17,26 @@ const { asyncMap, asyncMapOverIterable } = require('./async-map'),
1617
*/
1718
class AsyncArray extends Array {}
1819

19-
// Bind static async methods
20-
(AsyncArray.asyncFind = asyncFindInIterable),
21-
(AsyncArray.asyncFindIndex = asyncFindIndexOnIterable),
22-
(AsyncArray.asyncFilter = asyncFilterIterable),
23-
(AsyncArray.asyncForEach = asyncForEachOfIterable),
24-
(AsyncArray.asyncMap = asyncMapOverIterable),
25-
(AsyncArray.asyncSort = asyncSortIterable);
20+
// Add static methods
21+
Object.assign(AsyncArray, {
22+
asyncFind: asyncFindInIterable,
23+
asyncFindIndex: asyncFindIndexOnIterable,
24+
asyncFilter: asyncFilterIterable,
25+
asyncForEach: asyncForEachOfIterable,
26+
asyncMap: asyncMapOverIterable,
27+
asyncReduce: asyncReduceIterable,
28+
asyncSort: asyncSortIterable
29+
});
2630

27-
// Bind prototypical async methods
28-
(AsyncArray.prototype.asyncFind = asyncFind),
29-
(AsyncArray.prototype.asyncFindIndex = asyncFindIndex),
30-
(AsyncArray.prototype.asyncFilter = asyncFilter),
31-
(AsyncArray.prototype.asyncForEach = asyncForEach),
32-
(AsyncArray.prototype.asyncMap = asyncMap),
33-
(AsyncArray.prototype.asyncSort = asyncSort);
31+
// Create prototype methods
32+
Object.assign(AsyncArray.prototype, {
33+
asyncFind,
34+
asyncFindIndex,
35+
asyncFilter,
36+
asyncForEach,
37+
asyncMap,
38+
asyncReduce,
39+
asyncSort
40+
});
3441

3542
module.exports = AsyncArray;

src/async-reduce.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/**
2+
* Async Reduce
3+
* ============
4+
* Reduce asynchronously and resolve when
5+
* all items have been transduced.
6+
* @async
7+
* @param {Function} callback - callback(currentValue, index, array)
8+
* @param {any} [accumulator]
9+
* @return {any}
10+
* @throws {TypeError}
11+
*/
12+
const asyncReduce = (module.exports.asyncReduce = async function asyncReduce() {});
13+
14+
/**
15+
* Async Reduce
16+
* ============
17+
* Reduce an iterable object asynchronously and
18+
* resolve when all items have been transduced.
19+
* @async
20+
* @param {Object} iterable
21+
* @param {Function} callback - callback(currentValue, index, array)
22+
* @param {any} [accumulator]
23+
* @return {any}
24+
* @throws {TypeError}
25+
*/
26+
module.exports.asyncReduceIterable = async function asyncReduceIterable(
27+
iterable,
28+
callback,
29+
accumulator
30+
) {
31+
return asyncReduce.call(iterable, callback, accumulator);
32+
};

src/async-sort.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ const { validateIsFunction, validateIsIterable } = require('./validation'),
55
/**
66
* Async Sort
77
* ==========
8-
* Asynchronously sorts and an iterable object and resolves when fully sorted
8+
* Asynchronously sorts and resolves when fully sorted
99
* note that the object is sorted in place and no copy is made
1010
* @async
1111
* @param {Function} [compare=compareByUnicode] - default is sort by item's unicode value
@@ -25,7 +25,7 @@ const asyncSort = (module.exports.asyncSort = async function asyncSort(
2525
/**
2626
* Async Sort Iterable
2727
* ===================
28-
* Asynchronously sorts and an iterable object and resolves when fully sorted
28+
* Asynchronously sorts an iterable object and resolves when fully sorted
2929
* note that the object is sorted in place and no copy is made
3030
* @async
3131
* @param {Object} iterable

test/async-array.spec.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ context('Async Array', () => {
2020
it('Should have access to the asyncMap method', () =>
2121
expect(AsyncArray.prototype.asyncMap).to.be.a('function'));
2222

23+
it('Should have access to the asyncReduce method', () =>
24+
expect(AsyncArray.prototype.asyncReduce).to.be.a('function'));
25+
2326
it('Should have access to the asyncSort method', () =>
2427
expect(AsyncArray.prototype.asyncSort).to.be.a('function'));
2528
});
@@ -37,6 +40,9 @@ context('Async Array', () => {
3740
it('Should have access to the static asyncMap method', () =>
3841
expect(AsyncArray.asyncMap).to.be.a('function'));
3942

43+
it('Should have access to the static asyncReduce method', () =>
44+
expect(AsyncArray.asyncReduce).to.be.a('function'));
45+
4046
it('Should have access to the static asyncSort method', () =>
4147
expect(AsyncArray.asyncSort).to.be.a('function'));
4248
});

test/async-filter.spec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,11 @@ context('Async Filter', () => {
109109
});
110110

111111
it('Should have access to currentValue, index and array on the callback', () =>
112-
hasAccessToCorrectArgumentsOnCallback(array, result));
112+
hasAccessToCorrectArgumentsOnCallback(array, result, [
113+
'currentValue',
114+
'index',
115+
'array'
116+
]));
113117
});
114118

115119
describe('Given the optional thisArg parameter', () => {

test/async-find-index.spec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,11 @@ context('Async Find Index', () => {
112112
});
113113

114114
it('Should have access to currentValue, index and array on the callback', () =>
115-
hasAccessToCorrectArgumentsOnCallback(array, result));
115+
hasAccessToCorrectArgumentsOnCallback(array, result, [
116+
'currentValue',
117+
'index',
118+
'array'
119+
]));
116120
});
117121

118122
describe('Given the optional thisArg parameter', () => {

test/async-find.spec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,11 @@ context('Async Find', () => {
101101
});
102102

103103
it('Should have access to currentValue, index and array on the callback', () =>
104-
hasAccessToCorrectArgumentsOnCallback(array, result));
104+
hasAccessToCorrectArgumentsOnCallback(array, result, [
105+
'currentValue',
106+
'index',
107+
'array'
108+
]));
105109
});
106110

107111
describe('Given the optional thisArg parameter', () => {

test/async-for-each.spec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,11 @@ context('Async For Each', () => {
8383
});
8484

8585
it('Should have access to currentValue, index and array on the callback', () =>
86-
hasAccessToCorrectArgumentsOnCallback(array, result));
86+
hasAccessToCorrectArgumentsOnCallback(array, result, [
87+
'currentValue',
88+
'index',
89+
'array'
90+
]));
8791
});
8892

8993
describe('Given the optional thisArg parameter', () => {

test/async-map.spec.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,11 @@ context('Async Map', () => {
103103
});
104104

105105
it('Should have access to currentValue, index and array on the callback', () =>
106-
hasAccessToCorrectArgumentsOnCallback(array, result));
106+
hasAccessToCorrectArgumentsOnCallback(array, result, [
107+
'currentValue',
108+
'index',
109+
'array'
110+
]));
107111
});
108112

109113
describe('Given the optional thisArg parameter', () => {

test/async-reduce.spec.js

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
const { expect } = require('./support/chai'),
2+
{ getArray } = require('./support/data-factory'),
3+
{ getCallback } = require('./support/helpers'),
4+
{
5+
rejectsWithError,
6+
ranCallbacksInOrder,
7+
hasAccessToCorrectArgumentsOnCallback
8+
} = require('./support/spec-helpers'),
9+
{ asyncReduce } = require('../src');
10+
11+
context('Async Reduce', () => {
12+
let array;
13+
14+
beforeEach(() => {
15+
array = getArray();
16+
});
17+
18+
describe('Given no arguments', () => {
19+
it('Should reject with "TypeError: undefined is not iterable"', () =>
20+
rejectsWithError(
21+
asyncReduce(),
22+
new TypeError('undefined is not iterable')
23+
));
24+
});
25+
26+
describe('Given no callback', () => {
27+
it('Should reject with "TypeError: undefined is not a function"', () =>
28+
rejectsWithError(
29+
asyncReduce(array),
30+
new TypeError('undefined is not a function')
31+
));
32+
});
33+
34+
describe('Given a synchronous callback', () => {
35+
let result, callback, reducedAccumulator;
36+
37+
beforeEach(async () => {
38+
({ result, callback } = getCallback({
39+
isReduce: true
40+
}));
41+
42+
reducedAccumulator = await asyncReduce(array, callback);
43+
});
44+
45+
it('Should run each callback in order', () => {
46+
ranCallbacksInOrder(result);
47+
48+
expect(result.length).to.be.greaterThan(
49+
0,
50+
'No callbacks were run.'
51+
);
52+
});
53+
54+
it('Should reduce each item in order', async () => {
55+
array.every((element, index) => {
56+
return expect(reducedAccumulator[index]).to.equal(element);
57+
});
58+
});
59+
60+
it('Should resolve to the completed value accumulator', async () => {
61+
result.every(({ item }, index) => {
62+
return expect(reducedAccumulator[index]).to.equal(item);
63+
});
64+
65+
expect(result.length).to.be.greaterThan(
66+
0,
67+
'No callbacks were run.'
68+
);
69+
});
70+
});
71+
72+
describe('Given an asynchronous callback', () => {
73+
let result, callback, reducedAccumulator;
74+
75+
beforeEach(async () => {
76+
({ result, callback } = getCallback({
77+
isAsync: true,
78+
isReduce: true
79+
}));
80+
81+
reducedAccumulator = await asyncReduce(array, callback);
82+
});
83+
84+
it('Should run each callback in order', () => {
85+
ranCallbacksInOrder(result);
86+
87+
expect(result.length).to.be.greaterThan(
88+
0,
89+
'No callbacks were run.'
90+
);
91+
});
92+
93+
it('Should reduce each item in order', async () => {
94+
array.every((element, index) => {
95+
return expect(reducedAccumulator[index]).to.equal(element);
96+
});
97+
});
98+
99+
it('Should resolve to the completed value accumulator', async () => {
100+
result.every(({ item }, index) => {
101+
return expect(reducedAccumulator[index]).to.equal(item);
102+
});
103+
104+
expect(result.length).to.be.greaterThan(
105+
0,
106+
'No callbacks were run.'
107+
);
108+
});
109+
});
110+
111+
describe('Given a callback that throws an error', () => {
112+
let callback, error;
113+
114+
beforeEach(
115+
() =>
116+
({
117+
callback,
118+
meta: { error }
119+
} = getCallback({ isReduce: true, isError: true }))
120+
);
121+
122+
it('Should reject with that error', async () =>
123+
rejectsWithError(asyncReduce(array, callback), error));
124+
});
125+
126+
describe('Given a callback that uses all arguments', () => {
127+
let result, callback;
128+
129+
beforeEach(async () => {
130+
({ result, callback } = getCallback({
131+
isReduce: true
132+
}));
133+
134+
await asyncReduce(array, callback);
135+
});
136+
137+
it('Should have access to accumulator, currentValue, index and array on the callback', () => {
138+
hasAccessToCorrectArgumentsOnCallback(array, result, [
139+
'accumulator',
140+
'currentValue',
141+
'index',
142+
'array'
143+
]);
144+
145+
expect(result.length).to.be.greaterThan(
146+
0,
147+
'No callbacks were run.'
148+
);
149+
});
150+
});
151+
152+
describe('Given the optional accumulator parameter', () => {
153+
let result, callback, accumulator;
154+
155+
beforeEach(async () => {
156+
({ result, callback } = getCallback()), (accumulator = {});
157+
158+
await asyncReduce(array, callback, accumulator);
159+
});
160+
161+
it('Should have accesss to accumulator on all callback iterations', () => {
162+
expect(
163+
result.every(
164+
({ accumulator: resultAccumulator }) =>
165+
resultAccumulator === accumulator
166+
)
167+
).to.be.true;
168+
169+
expect(result.length).to.be.greaterThan(
170+
0,
171+
'No callbacks were run.'
172+
);
173+
});
174+
});
175+
});

0 commit comments

Comments
 (0)