Skip to content

Commit

Permalink
Add requester apis (#7)
Browse files Browse the repository at this point in the history
* Make reduce function the default
- Fix tests
- Fix examples
- Fix timetravel

* Fix and add tests.

* Keep the api of legacy staat.

* Fix react tests

* Add Hooks api (#6)

* Create hooks api

* Use sideEffect for hook subscription.

* Update hooks code.

* Performance improvements to the hook

* Hooks tests

* Add requester functions

* Add requester.
  • Loading branch information
ericmackrodt committed Jul 2, 2019
1 parent b14e229 commit c197fd8
Show file tree
Hide file tree
Showing 32 changed files with 1,015 additions and 346 deletions.
42 changes: 42 additions & 0 deletions packages/core/src/__tests__/requester.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import staat from '../staat';
import { Staat } from '../types';

type TestState = {
count: number;
};

const state: TestState = {
count: 37,
};

describe('requester', () => {
let sut: Staat<TestState>;
beforeEach(() => {
sut = staat(state);
});

it('selects from state', async () => {
let value: number = 10;
await sut.request(async ({ select }) => {
value = select(s => s.count);
});

expect(value).toBe(37);
});

it('reduces state', async () => {
await sut.request(async ({ reduce }) => {
reduce(s => ({ ...s, count: 99 }));
});

expect(sut.currentState.count).toBe(99);
});

it('accepts arguments', async () => {
await sut.request(async ({ reduce }, value: number) => {
reduce(s => ({ ...s, count: value }));
}, 489);

expect(sut.currentState.count).toBe(489);
});
});
62 changes: 61 additions & 1 deletion packages/core/src/__tests__/scope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ describe('scope', () => {
expect(sut2.path).toEqual(['another1', 'another2']);
});

describe('transfomer', () => {
describe('transformer', () => {
it('is called with scope', () => {
const sut = scope<State, 'level1'>('level1');
const stub = jest.fn();
Expand Down Expand Up @@ -160,4 +160,64 @@ describe('scope', () => {
},
});
});

describe('reducer', () => {
it('is called with scope', () => {
const sut = scope<State, 'level1'>('level1');
const stub = jest.fn();
const reducer = sut.reducer(stub);
reducer(initialState);
expect(stub).toHaveBeenCalledWith(initialState.level1);
});

it('is called with deeper scope', () => {
const sut = scope<State, 'level1', 'level2'>('level1', 'level2');
const stub = jest.fn();
const reducer = sut.reducer(stub);
reducer(initialState);
expect(stub).toHaveBeenCalledWith(initialState.level1.level2);
});

it('updates scope', () => {
const sut = scope<State, 'level1'>('level1');
const reducer = sut.reducer((currentScope, value: string) => {
return { ...currentScope, value1: value };
});
expect(reducer(initialState, 'new_value')).toEqual({
level1: {
value1: 'new_value',
level2: {
value2: '',
},
},
another1: {
count1: 0,
another2: {
count2: 0,
},
},
});
});

it('updates deeper scope', () => {
const sut = scope<State, 'level1', 'level2'>('level1', 'level2');
const reducer = sut.reducer((currentScope, value2: string) => {
return { ...currentScope, value2 };
});
expect(reducer(initialState, 'new_value')).toEqual({
level1: {
value1: '',
level2: {
value2: 'new_value',
},
},
another1: {
count1: 0,
another2: {
count2: 0,
},
},
});
});
});
});
172 changes: 113 additions & 59 deletions packages/core/src/__tests__/staat.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import staat from '../staat';
import { Staat } from '../types';
import { Staat, LegacyStaat } from '../types';

type TestState = {
count: number;
Expand All @@ -19,72 +19,126 @@ const transformers = {
};

describe('staat', () => {
let sut: Staat<TestState, typeof transformers>;
beforeEach(() => {
sut = staat(transformers, state);
});
describe('legacy', () => {
let sut: LegacyStaat<typeof transformers, TestState>;
beforeEach(() => {
sut = staat(transformers, state);
});

it('sets up all members', () => {
expect(sut).toHaveProperty('currentState');
expect(sut.currentState).toBe(state);
expect(typeof sut.subscribe).toBe('function');
expect(typeof sut.unsubscribe).toBe('function');
expect(typeof sut.add).toBe('function');
expect(typeof sut.subtract).toBe('function');
});
it('sets up all members', () => {
expect(sut).toHaveProperty('currentState');
expect(sut.currentState).toBe(state);
expect(typeof sut.subscribe).toBe('function');
expect(typeof sut.unsubscribe).toBe('function');
expect(typeof sut.add).toBe('function');
expect(typeof sut.subtract).toBe('function');
});

it('sets up deep transformers', () => {
const sut2 = staat({ deep: transformers }, state);
expect(sut2).toHaveProperty('deep');
expect(typeof sut2.deep.add).toBe('function');
expect(typeof sut2.deep.subtract).toBe('function');
});
it('sets up deep transformers', () => {
const sut2 = staat({ deep: transformers }, state);
expect(sut2).toHaveProperty('deep');
expect(typeof sut2.deep.add).toBe('function');
expect(typeof sut2.deep.subtract).toBe('function');
});

it('creates external trasformers signature and calls the declaration', async () => {
let newState = await sut.add(100);
expect(sut.currentState).toEqual({ count: 100 });
expect(newState).toEqual({ count: 100 });
expect(sut.currentState).not.toBe(state);
newState = await sut.subtract(50);
expect(sut.currentState).toEqual({ count: 50 });
expect(newState).toEqual({ count: 50 });
expect(sut.currentState).not.toBe(state);
});
it('creates external trasformers signature and calls the declaration', async () => {
let newState = await sut.add(100);
expect(sut.currentState).toEqual({ count: 100 });
expect(newState).toEqual({ count: 100 });
expect(sut.currentState).not.toBe(state);
newState = await sut.subtract(50);
expect(sut.currentState).toEqual({ count: 50 });
expect(newState).toEqual({ count: 50 });
expect(sut.currentState).not.toBe(state);
});

it('fires subscription when calling transformer', async () => {
const subscription = jest.fn();
sut.subscribe(subscription);
await sut.add(1);
expect(subscription).toHaveBeenCalled();
});
it('fires subscription when calling transformer', async () => {
const subscription = jest.fn();
sut.subscribe(subscription);
await sut.add(1);
expect(subscription).toHaveBeenCalled();
});

it('unsubscribes from state', async () => {
const subscription = jest.fn();
sut.subscribe(subscription);
await sut.add(1);
sut.unsubscribe(subscription);
await sut.add(1);
expect(subscription).toHaveBeenCalledTimes(1);
});
it('unsubscribes from state', async () => {
const subscription = jest.fn();
sut.subscribe(subscription);
await sut.add(1);
sut.unsubscribe(subscription);
await sut.add(1);
expect(subscription).toHaveBeenCalledTimes(1);
});

it('calls asynchronous transformers', async () => {
const sut2 = staat(
{
multiply(currentState: TestState, value: number) {
return Promise.resolve({
...currentState,
count: currentState.count * value,
});
it('calls asynchronous transformers', async () => {
const sut2 = staat(
{
multiply(currentState: TestState, value: number) {
return Promise.resolve({
...currentState,
count: currentState.count * value,
});
},
},
{
count: 2,
},
},
{
count: 2,
},
);
);

const newState = await sut2.multiply(10);

expect(sut2.currentState).toEqual({ count: 20 });
expect(newState).toEqual({ count: 20 });
});
});

describe('current', () => {
let sut: Staat<TestState>;
beforeEach(() => {
sut = staat(state);
});

it('sets up all members', () => {
expect(sut).toHaveProperty('currentState');
expect(sut.currentState).toBe(state);
expect(typeof sut.subscribe).toBe('function');
expect(typeof sut.unsubscribe).toBe('function');
expect(typeof sut.reduce).toBe('function');
expect(typeof sut.request).toBe('function');
});

it('reduces the value', () => {
const newState = sut.reduce(s => ({ ...s, count: 100 }));
expect(sut.currentState).toEqual({ count: 100 });
expect(newState).toEqual({ count: 100 });
expect(sut.currentState).not.toBe(state);
});

it('reduces the value using external function with parameters', () => {
function reducer(s: TestState, value: number) {
return { ...s, count: value };
}
const newState = sut.reduce(reducer, 50);
expect(sut.currentState).toEqual({ count: 50 });
expect(newState).toEqual({ count: 50 });
expect(sut.currentState).not.toBe(state);
});

const newState = await sut2.multiply(10);
it('fires subscription when reducing', async () => {
const subscription = jest.fn();
sut.subscribe(subscription);
sut.reduce(s => ({ ...s, count: 1 }));
await new Promise(resolve => setTimeout(resolve, 1));
expect(subscription).toHaveBeenCalled();
});

expect(sut2.currentState).toEqual({ count: 20 });
expect(newState).toEqual({ count: 20 });
it('unsubscribes from state', async () => {
const subscription = jest.fn();
sut.subscribe(subscription);
sut.reduce(s => ({ ...s, count: 1 }));
await new Promise(resolve => setTimeout(resolve, 1));
sut.unsubscribe(subscription);
sut.reduce(s => ({ ...s, count: 2 }));
await new Promise(resolve => setTimeout(resolve, 1));
expect(subscription).toHaveBeenCalledTimes(1);
});
});
});
2 changes: 2 additions & 0 deletions packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ export {
Transformers,
Subscription,
Staat,
LegacyStaat,
StateContainerType,
IScope,
TransformersTree,
} from './types';
export * from './scope';
export const internals = {
Expand Down
13 changes: 13 additions & 0 deletions packages/core/src/scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,22 @@ class Scope<TState> implements IScope<TState, unknown> {

constructor(private properties: string[]) {}

public reducer<TArgs extends any[]>(
definition: (currentScope: unknown, ...args: TArgs) => unknown,
): (currentState: TState, ...args: TArgs) => TState {
return (currentState: any, ...args: any) => {
const s = getScope<TState, any>(currentState, this.properties);
const result = definition(s, ...args);
return setScope({ ...currentState }, result, this.properties);
};
}

public transformer<TArgs extends any[]>(
definition: (currentScope: unknown, ...args: TArgs) => unknown,
): (currentState: TState, ...args: TArgs) => TState | Promise<TState> {
console.warn(
'[Staat] Scope transformer has been discontinued, please use reducer instead',
);
return (currentState: any, ...args: any) => {
const s = getScope<TState, any>(currentState, this.properties);
const result = definition(s, ...args);
Expand Down
Loading

0 comments on commit c197fd8

Please sign in to comment.