Skip to content

Commit

Permalink
Merge 3a8a329 into 136f7a6
Browse files Browse the repository at this point in the history
  • Loading branch information
taras committed Jul 2, 2018
2 parents 136f7a6 + 3a8a329 commit 45ef33f
Show file tree
Hide file tree
Showing 13 changed files with 927 additions and 384 deletions.
267 changes: 196 additions & 71 deletions src/tree.js

Large diffs are not rendered by default.

28 changes: 27 additions & 1 deletion src/types/array.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import transform from '../transform';
import Tree from '../tree';
import Any from './any';
import { parameterized } from './parameters0';
import invariant from 'invariant';

class ArrayType {
initialize(value = []) {
return value;
Expand Down Expand Up @@ -87,7 +89,7 @@ class ArrayType {
}
})
}, child);
}, children.filter(tree => fn(tree.state)));
}, children.filter(tree => fn(tree.microstate)));
}, this);
}

Expand Down Expand Up @@ -120,6 +122,30 @@ class ArrayType {
clear() {
return this.set([]);
}

/**
* The reduce() transition applies a function against an accumulator
* and each element in the array (from left to right) to reduce it to
* a single value.
*
* The reducer function will receive a microstate as an accumulator and
* a microstate for each value. The microstate returned from the reducer
* function will be passed to next invocation of the reducer function.
*
* @param {*} fn
* @param {*|Microstate}
* @returns {Microstate}
*/
reduce(fn, initial) {
invariant(typeof fn === 'function', `reduce transition expects a reduce function as first argument, got ${fn}`);
invariant(typeof initial !== 'undefined', `reduce transition requires initial value as second arguement, got ${initial}`);

let children = Tree.from(this).children || [];

return children.reduce((memo, tree, currentIndex) => {
return Tree.from(fn(memo, tree.prune().microstate, currentIndex)).microstate;
}, Tree.from(initial).microstate);
}
}

export default parameterized(ArrayType, {T: Any});
2 changes: 1 addition & 1 deletion tests/computed-properties.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class State {
lastName = String;

get fullName() {
return `${this.firstName} ${this.lastName}`;
return `${this.firstName.state} ${this.lastName.state}`;
}

get birthDate() {
Expand Down
52 changes: 0 additions & 52 deletions tests/constants.test.js

This file was deleted.

21 changes: 17 additions & 4 deletions tests/examples/authentication.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { create, reveal } from 'microstates';
import logTree from '../../src/utils/log-tree';

class Session {
content = null;

get content() {
return null;
}

initialize(session) {
if (session) {
return create(AuthenticatedSession, session);
Expand All @@ -13,7 +17,11 @@ class Session {
}

class AuthenticatedSession extends Session {
isAuthenticated = true;

get isAuthenticated() {
return true;
}

content = Object;

logout() {
Expand All @@ -22,8 +30,13 @@ class AuthenticatedSession extends Session {
}

class AnonymousSession extends Session {
content = null;
isAuthenticated = false;
get content() {
return null;
}

get isAuthenticated() {
return false;
}

authenticate(user) {
return create(AuthenticatedSession, { content: user });
Expand Down
13 changes: 7 additions & 6 deletions tests/examples/cart.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@ describe('cart example', () => {
class Cart {
products = Array;
get price() {
return this.products.reduce((acc, product) => acc + product.quantity * product.price, 0);
return this
.products.reduce((acc, product) => acc.increment(product.quantity.state * product.price.state), 0)
.products;
}
get count() {
return this.products.reduce((acc, product) => acc + product.quantity, 0);
return this
.products.reduce((acc, product) => acc.increment(product.quantity.state), 0)
.products;
}
}
describe('adding products without initial value', () => {
Expand All @@ -19,11 +23,8 @@ describe('cart example', () => {
.products.push({ quantity: 2, price: 20 });
});
it('adds items to the cart', () => {
expect(ms.state.price).toEqual(50);
expect(ms.state.count).toEqual(3);
expect(ms.state).toMatchObject({
products: [{ quantity: 1, price: 10 }, { quantity: 2, price: 20 }],
});
// expect(ms.price.state).toEqual(50);
});
it('provides valueOf', () => {
expect(ms.valueOf()).toEqual({
Expand Down
49 changes: 48 additions & 1 deletion tests/microstate.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'jest';
import Microstate, { create, map } from 'microstates';
import Microstate, { create, map, use, Tree } from 'microstates';

it('exports create', function() {
expect(create).toBeInstanceOf(Function);
Expand Down Expand Up @@ -102,4 +102,51 @@ describe('transitions', () => {
it('has name of the function', () => {
expect(increment.name).toBe('increment');
});
});

describe('middleware', () => {
describe('invocation', () => {
class A {
counter = Number;
outsideTransition() {
return this.counter.increment();
}
}
let a, callback, a1;
beforeEach(() => {
callback = jest.fn();
let middleware = next => (microstate, transition, args) => {
callback(transition.name);
return next(microstate, transition, args);
}
a = use(middleware, create(A, { counter: 42 }));
a1 = a.outsideTransition();
});
it('called callback once', () => {
expect(callback).toHaveBeenCalledTimes(1);
});
it('got called only for outsideTransition', () => {
expect(callback.mock.calls[0][0]).toBe('outsideTransition');
});
it('was not called second time', () => {
expect(callback.mock.calls[1]).toBeUndefined();
});
});
describe('installation in initialize', () => {
let middleware;
let corvete;
class Car {
initialize() {
return use(middleware, this);
}
}

beforeEach(() => {
middleware = jest.fn(next => (...args) => next());
corvete = create(Car);
});
it('has middleware', () => {
expect(Tree.from(corvete).meta.middleware.includes(middleware)).toBe(true);
});
});
});
40 changes: 28 additions & 12 deletions tests/observable-test.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
import "jest";
import Microstate, { create } from "microstates";
import Microstate, { create, Tree } from "microstates";
import SymbolObservable from 'symbol-observable';
import { from } from 'rxjs';

describe('rxjs interop', function() {
let ms, observable, observer, last;
let observer, last;
beforeEach(() => {
ms = create(Number, 42);
observer = jest.fn(next => last = next);
observable = from(ms);
let subscription = observable.subscribe(observer);
last.increment();
last.increment();
last.increment();
from(create(Number, 42)).subscribe(observer);
});
it('sent 4 states to obsever', function() {
expect(observer.mock.calls).toHaveLength(4);
it('has the middleware in the root', () => {
expect(Tree.from(last).root.meta.middleware).toHaveLength(1);
expect(Tree.from(last).root.meta.middleware[0].name).toBe('notifyObserver');
});
it('incremented 3 times', function() {
expect(last.state).toBe(45);
it('called observer once', () => {
expect(observer).toHaveBeenCalledTimes(1);
});
describe('first call', () => {
beforeEach(() => last.increment());
it('has the middleware in the root', () => {
expect(Tree.from(last).root.meta.middleware).toHaveLength(1);
expect(Tree.from(last).root.meta.middleware[0].name).toBe('notifyObserver');
});
it('called observer twice', () => {
expect(observer).toHaveBeenCalledTimes(2);
});
describe('second call', () => {
beforeEach(() => last.increment());
it('has the middleware in the root', () => {
expect(Tree.from(last).root.meta.middleware).toHaveLength(1);
expect(Tree.from(last).root.meta.middleware[0].name).toBe('notifyObserver');
});
it('called observer three times', () => {
expect(observer).toHaveBeenCalledTimes(3);
});
});
});
});

Expand Down

0 comments on commit 45ef33f

Please sign in to comment.