Skip to content

Commit

Permalink
refactor, added travis, coveralls
Browse files Browse the repository at this point in the history
  • Loading branch information
carlosrberto committed Apr 1, 2018
1 parent ebd0fb9 commit 35aed17
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 107 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
lib
node_modules
.coverage
12 changes: 12 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
language: node_js
node_js:
- "8"
install:
- yarn
script:
- yarn lint
- yarn test
- yarn cov

after_success:
- yarn cov:publish
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[![Build Status](https://travis-ci.org/carlosrberto/lazy-list.svg?branch=master)](https://travis-ci.org/carlosrberto/lazy-list)
[![Coverage Status](https://coveralls.io/repos/github/carlosrberto/lazy-list/badge.svg?branch=master)](https://coveralls.io/github/carlosrberto/lazy-list?branch=master)

# LazyList

LazyList implements `map`, `filter` and `reduce` in JavaScript Arrays with lazy evaluation.
Expand Down
6 changes: 6 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
coverageDirectory: '.coverage',
collectCoverageFrom: [
'src/**/*.{js,jsx}',
],
};
10 changes: 8 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"babel-core": "^6.26.0",
"babel-jest": "^22.4.3",
"babel-preset-env": "^1.6.1",
"coveralls": "^3.0.0",
"eslint": "^4.19.1",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.10.0",
Expand All @@ -15,13 +16,18 @@
"regenerator-runtime": "^0.11.1",
"rimraf": "^2.6.2",
"rollup": "^0.57.1",
"rollup-plugin-babel": "^3.0.3"
"rollup-plugin-babel": "^3.0.3",
"static-server": "^2.2.0"
},
"scripts": {
"start": "npm run test -- --watch",
"test": "jest",
"cov": "jest --coverage",
"cov:serve": "npm run cov && static-server .coverage/lcov-report --port 3000 --no-nocache",
"cov:publish": "cat ./.coverage/lcov.info | coveralls",
"lint": "eslint .",
"clean": "rimraf lib",
"build": "npm run clean && npm run lint && npm run test && rollup -c"
}
},
"dependencies": {}
}
111 changes: 60 additions & 51 deletions src/LazyList.js
Original file line number Diff line number Diff line change
@@ -1,84 +1,93 @@
const LIST_METHODS = {
export const LIST_OPERATIONS = {
MAP: 'map',
FILTER: 'filter',
FOR_EACH: 'forEach',
REDUCE: 'reduce',
};

export const VALID_OPERATIONS = Object
.keys(LIST_OPERATIONS)
.map(v => LIST_OPERATIONS[v]);

export default class LazyList {
constructor(list) {
constructor(list, operations = []) {
this.operations = operations;
this.list = list;
this.t = [];
}

map(fn) {
this.t.push([LIST_METHODS.MAP, fn]);
return this;
createOperation(type, fn, ...extraArgs) {
if (!(type in VALID_OPERATIONS)) {
throw Error('Invalid operation type argument');
}

if (typeof fn !== 'function') {
throw Error('Invalid callback argument');
}

return new LazyList(
this.list,
[
...this.operations,
...[{ type, fn, extraArgs }],
],
);
}

filter(fn) {
this.t.push([LIST_METHODS.FILTER, fn]);
return this;
map(fn) {
return this.createOperation(LIST_OPERATIONS.MAP, fn);
}

forEach(fn) {
this.t.push([LIST_METHODS.FOR_EACH, fn]);
return this;
filter(fn) {
return this.createOperation(LIST_OPERATIONS.FILTER, fn);
}

reduce(fn, acc) {
this.t.push([LIST_METHODS.REDUCE, fn, acc]);
return this.value();
reduce(fn, ...extraArgs) {
return this.createOperation(LIST_OPERATIONS.REDUCE, fn, ...extraArgs).value();
}

value() {
const { list, operations } = this;
const listLength = list.length;
const operationsLength = operations.length;
const result = [];
const hasReduce = this.t[this.t.length - 1][0] === LIST_METHODS.REDUCE;
let reducerAcc;
for (let i = 0; i < this.list.length; i += 1) {
let nextValue = this.list[i];
for (let k = 0; k < this.t.length; k += 1) {
if (nextValue) {
switch (this.t[k][0]) {
case LIST_METHODS.MAP:
nextValue = this.t[k][1](nextValue);
break;

case LIST_METHODS.FILTER:
if (!this.t[k][1](nextValue)) {
nextValue = false;
}
break;

case LIST_METHODS.FOR_EACH:
this.t[k][1](nextValue);
break;
default:
}
}

for (let i = 0; i < listLength; i += 1) {
const item = list[i];
const nextItem = { value: item, valid: true };

if (nextValue && hasReduce) {
[, , reducerAcc] = this.t[this.t.length - 1];
if (!reducerAcc && result.length === 1) {
[reducerAcc] = result;
} else if (!reducerAcc && result.length === 0) {
reducerAcc = nextValue;
} else if (reducerAcc && result.length === 0) {
reducerAcc = this.t[this.t.length - 1][1](reducerAcc, nextValue);
for (let j = 0; j < operationsLength; j += 1) {
const { type, fn, extraArgs } = operations[j];

if (nextItem.valid && type === LIST_OPERATIONS.MAP) {
nextItem.value = fn(item, i);
} else if (nextItem.valid && type === LIST_OPERATIONS.FILTER) {
if (fn(item, i)) {
nextItem.value = item;
} else {
nextItem.valid = false;
}
if (result.length >= 1) {
reducerAcc = this.t[this.t.length - 1][1](reducerAcc, nextValue);
}

if (nextItem.valid && type === LIST_OPERATIONS.REDUCE) {
const [reducerInitial] = extraArgs;
if (i === 0) {
if (reducerInitial !== undefined) {
reducerAcc = fn(reducerInitial, item);
} else {
reducerAcc = item;
}
} else {
reducerAcc = fn(reducerAcc, item);
}
}
}

if (nextValue) {
result.push(nextValue);
if (nextItem.valid) {
result.push(nextItem.value);
}
}

if (hasReduce) {
if (reducerAcc) {
return reducerAcc;
}

Expand Down
107 changes: 107 additions & 0 deletions src/LazyList.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import lazy from './index';
import { LIST_OPERATIONS } from './LazyList';

describe('LazyList.createOperation', () => {
it('should throw when invalid operation is provided', () => {
expect(() => {
lazy([1]).createOperation('invalid', v => v + 1);
}).toThrowError('Invalid operation type argument');
});

it('should throw when invalid callback is provided', () => {
expect(() => {
lazy([1]).createOperation(LIST_OPERATIONS.MAP, null);
}).toThrowError('Invalid callback argument');
});

it('should return new Instance of LazyList', () => {
const l1 = lazy([1]);
const l2 = l1.createOperation(LIST_OPERATIONS.MAP, v => v + 1);
expect(l2).not.toBe(l1);
});

it('should store operation', () => {
const fn = v => v + 1;
const l1 = lazy([1]).createOperation(LIST_OPERATIONS.MAP, fn);
expect(l1.operations).toHaveLength(1);
expect(l1.operations[0].type).toEqual(LIST_OPERATIONS.MAP);
expect(l1.operations[0].fn).toEqual(fn);
});

it('should store previous operation', () => {
const l1 = lazy([1]);
const l2 = l1.createOperation(LIST_OPERATIONS.MAP, v => v + 1);
const l3 = l2.createOperation(LIST_OPERATIONS.MAP, v => v + 1);

expect(l1.operations).toHaveLength(0);
expect(l2.operations).toHaveLength(1);
expect(l3.operations).toHaveLength(2);
});
});

describe('LazyList.map', () => {
it('should pass value and index as arguments', () => {
const fn = jest.fn();
lazy([1, 2]).map((v, i) => {
fn(v, i);
return v + 1;
}).value();
expect(fn).toBeCalledWith(1, 0);
expect(fn).toBeCalledWith(2, 1);
});

it('should map value correctly', () => {
expect(lazy([1]).map(v => v + 1).value()).toEqual([2]);
expect(lazy([0]).map(v => v * 2).value()).toEqual([0]);
expect(lazy([null]).map(v => v * 2).value()).toEqual([0]);
expect(lazy([NaN]).map(v => v * 2).value()).toEqual([NaN]);
expect(lazy([undefined]).map(v => v * 2).value()).toEqual([NaN]);
expect(lazy([{ name: 'Joe' }]).map(v => v.name).value()).toEqual(['Joe']);
});

it('should work with multiple map calls', () => {
expect(lazy([' Joe ', ' Dog '])
.map(v => v.toUpperCase())
.map(v => v.trim())
.value()).toEqual(['Joe', 'Dog']);
});
});

describe('LazyList.filter', () => {
it('should filter value correctly', () => {
expect(lazy([0, 1, 2]).filter(v => v > 0).value()).toEqual([1, 2]);
expect(lazy([
{ name: 'Joe', age: 3 },
{ name: 'Spike', age: 16 },
]).filter(v => v.age > 10).value()).toEqual([{ name: 'Spike', age: 16 }]);
});

it('should work with multiple filter calls', () => {
expect(lazy([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.filter(v => v > 2)
.filter(v => v < 8)
.value()).toEqual([3, 4, 5, 6, 7]);
});
});

describe('LazyList.reduce', () => {
it('should reduce to correctly value', () => {
expect(lazy([1, 2, 3]).reduce((a, b) => a + b)).toEqual(6);
});

it('should reduce to correctly value when provide initial value', () => {
expect(lazy([1, 2, 3]).reduce((a, b) => a + b, 1)).toEqual(7);

expect(lazy([
{ value: 1 },
{ value: 2 },
]).reduce((acc, b) => (
{ value: acc.value + b.value }
), { value: 0 })).toEqual({ value: 3 });

expect(lazy([
{ value: 1 },
{ value: 2 },
]).reduce((acc, b) => acc + b.value, 0)).toEqual(3);
});
});
51 changes: 0 additions & 51 deletions src/index.test.js

This file was deleted.

Loading

0 comments on commit 35aed17

Please sign in to comment.