Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Store Middleware #391

Merged
merged 3 commits into from
Jun 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions src/core/middleware/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { destroy, invalidator, create } from '../vdom';
import injector from '../middleware/injector';
import Store, { StatePaths, Path } from '../../stores/Store';
import { Process } from '../../stores/process';

const factory = create({ destroy, invalidator, injector });

export const createStoreMiddleware = <S = any>(initial?: (store: Store<S>) => void) => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepts an optional initial (perhaps should be called init or initialize) function that receives the factorie'd store and can initialize state etc.

let store = new Store<S>();
let initialized = false;
initial && initial(store);
const storeMiddleware = factory(({ middleware: { destroy, invalidator, injector } }) => {
const handles: any[] = [];
destroy(() => {
let handle: any;
while ((handle = handles.pop())) {
handle();
}
});
if (!initialized) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does a one time check for a store in the application registry, if it exists the middleware will use that instead

const injectedStore = injector.get<Store<S>>('state');
if (injectedStore) {
store = injectedStore;
}
initialized = true;
}
const registeredPaths: string[] = [];
const path: StatePaths<S> = (path: any, ...segments: any) => {
return (store as any).path(path, ...segments);
};
return {
get<U = any>(path: Path<S, U>): U {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automatically registers paths to invalidate on for a widget

if (registeredPaths.indexOf(path.path) === -1) {
const handle = store.onChange(path, () => {
invalidator();
});
handles.push(() => handle.remove());
registeredPaths.push(path.path);
}
return store.get(path);
},
path,
at<U = any>(path: Path<S, U[]>, index: number) {
return store.at(path, index);
},
executor<T extends Process<any, any>>(process: T): ReturnType<T> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

function that returns an executor from a process, const exec = executor(process) is equivalent to const exec = process(store);

return process(store) as any;
}
};
});
return storeMiddleware;
};

export default createStoreMiddleware;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exports a factory that creates the middleware, this is so that it can be typed with the store state interface, also factories a Store that is shared across all instances of the middleware.

1 change: 1 addition & 0 deletions tests/core/unit/middleware/all.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ import './icache';
import './injector';
import './intersection';
import './resize';
import './store';
import './theme';
158 changes: 158 additions & 0 deletions tests/core/unit/middleware/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
const { it, describe, afterEach, beforeEach } = intern.getInterface('bdd');
const { assert } = intern.getPlugin('chai');
import { sandbox } from 'sinon';

import { createProcess } from '../../../../src/stores/process';
import { replace } from '../../../../src/stores/state/operations';
import Store from '../../../../src/stores/Store';
import createStoreMiddleware from '../../../../src/core/middleware/store';

const sb = sandbox.create();
const destroyStub = sb.stub();
const invalidatorStub = sb.stub();
const injectorStub = {
get: sb.stub(),
subscribe: sb.stub()
};
let storeMiddleware = createStoreMiddleware();

describe('store middleware', () => {
beforeEach(() => {
storeMiddleware = createStoreMiddleware();
});

afterEach(() => {
sb.resetHistory();
});

it('Should return data from store and subscribe to data changes for the path', () => {
const { callback } = storeMiddleware();
const store = callback({
id: 'test',
middleware: {
destroy: destroyStub,
invalidator: invalidatorStub,
injector: injectorStub
},
properties: {}
});
let result = store.get(store.path('my-state'));
assert.isUndefined(result);
const testProcess = createProcess('test', [
({ path }) => {
return [replace(path('my-state'), 'test-data')];
}
]);
store.executor(testProcess)({});
assert.isTrue(invalidatorStub.calledOnce);
result = store.get(store.path('my-state'));
assert.strictEqual(result, 'test-data');
});

it('Should be able to work with arrays in the store', () => {
const { callback } = storeMiddleware();
const store = callback({
id: 'test',
middleware: {
destroy: destroyStub,
invalidator: invalidatorStub,
injector: injectorStub
},
properties: {}
});
let result = store.get(store.path('my-state'));
assert.isUndefined(result);
let testProcess = createProcess('test', [
({ path }) => {
return [replace(path('my-state'), [1])];
}
]);
store.executor(testProcess)({});
assert.isTrue(invalidatorStub.calledOnce);
result = store.get(store.at(store.path('my-state'), 0));
assert.deepEqual(result, 1);
testProcess = createProcess('test', [
({ path, at }) => {
return [replace(at(path('my-state'), 1), 2)];
}
]);
store.executor(testProcess)({});
assert.isTrue(invalidatorStub.calledTwice);
result = store.get(store.at(store.path('my-state'), 1));
assert.deepEqual(result, 2);
});

it('Should remove subscription handles when destroyed', () => {
const { callback } = storeMiddleware();
const store = callback({
id: 'test',
middleware: {
destroy: destroyStub,
invalidator: invalidatorStub,
injector: injectorStub
},
properties: {}
});
let result = store.get(store.path('my-state'));
assert.isUndefined(result);
let testProcess = createProcess('test', [
({ path }) => {
return [replace(path('my-state'), 'test-data')];
}
]);
store.executor(testProcess)({});
assert.isTrue(invalidatorStub.calledOnce);
result = store.get(store.path('my-state'));
assert.strictEqual(result, 'test-data');
destroyStub.getCall(0).callArg(0);
testProcess = createProcess('test', [
({ path }) => {
return [replace(path('my-state'), 'test')];
}
]);
store.executor(testProcess)({});
assert.isTrue(invalidatorStub.calledOnce);
result = store.get(store.path('my-state'));
assert.strictEqual(result, 'test');
});

it('Should use an injected store if available', () => {
const { callback } = storeMiddleware();
const injectedStore = new Store();
let testProcess = createProcess('test', [
({ path }) => {
return [replace(path('my', 'nested', 'state'), 'existing-data')];
}
]);
testProcess(injectedStore)({});
injectorStub.get.returns(injectedStore);
const store = callback({
id: 'test',
middleware: {
destroy: destroyStub,
invalidator: invalidatorStub,
injector: injectorStub
},
properties: {}
});
const result = store.get(store.path('my', 'nested', 'state'));
assert.strictEqual(result, 'existing-data');
const otherStore = callback({
id: 'test',
middleware: {
destroy: destroyStub,
invalidator: invalidatorStub,
injector: injectorStub
},
properties: {}
});
const resultTwo = otherStore.get(store.path('my', 'nested', 'state'));
assert.strictEqual(resultTwo, 'existing-data');
});

it('Should run initialize function on creation', () => {
const init = sb.stub();
storeMiddleware = createStoreMiddleware(init);
assert.isTrue(init.calledOnce);
});
});