Commit 4b7c566d authored by Robert Knight's avatar Robert Knight

Update export style and documentation for sidebar store

Convert the `store/index.js` and `store/create-store.js` modules to use
named rather than default exports per our current conventions, and
revise the documentation.

The goal of the revised documentation is to more clearly describe what the
store is and how it is used within the sidebar/notebook app, assuming
that the reader is likely have at least some familiarity with Redux (or
can read the "Introduction" section of the linked website if not). In
particular I have tried to convey:

 - The separation between the "base" `createStore` function which is not
   application-specific and the `createSidebarStore` function and the
   modules it uses which are
 - How the store in the sidebar app differs from a standard/base Redux store
 - Best practices around using the store from other parts of the app
   (use `useStoreProxy` in UI components, use selector and action
   methods rather than `getState` and `dispatch`)
parent 40eaf5c9
...@@ -131,7 +131,7 @@ import { ThreadsService } from './services/threads'; ...@@ -131,7 +131,7 @@ import { ThreadsService } from './services/threads';
import { ToastMessengerService } from './services/toast-messenger'; import { ToastMessengerService } from './services/toast-messenger';
// Redux store. // Redux store.
import store from './store'; import { createSidebarStore } from './store';
// Utilities. // Utilities.
import { Injector } from '../shared/injector'; import { Injector } from '../shared/injector';
...@@ -167,7 +167,7 @@ function startApp(config, appEl) { ...@@ -167,7 +167,7 @@ function startApp(config, appEl) {
.register('tags', TagsService) .register('tags', TagsService)
.register('threadsService', ThreadsService) .register('threadsService', ThreadsService)
.register('toastMessenger', ToastMessengerService) .register('toastMessenger', ToastMessengerService)
.register('store', store); .register('store', { factory: createSidebarStore });
// Register utility values/classes. // Register utility values/classes.
// //
......
...@@ -75,7 +75,8 @@ import { createReducer, bindSelectors } from './util'; ...@@ -75,7 +75,8 @@ import { createReducer, bindSelectors } from './util';
*/ */
/** /**
* Redux store augmented with methods to dispatch actions and select state. * Redux store augmented with selector methods to query specific state and
* action methods that dispatch specific actions.
* *
* @template {object} Actions * @template {object} Actions
* @template {object} Selectors * @template {object} Selectors
...@@ -97,16 +98,25 @@ import { createReducer, bindSelectors } from './util'; ...@@ -97,16 +98,25 @@ import { createReducer, bindSelectors } from './util';
* - The _selectors_ for reading that state or computing things * - The _selectors_ for reading that state or computing things
* from that state. * from that state.
* *
* On top of the standard Redux store methods, the returned store also exposes * In addition to the standard Redux store interface, the returned store also exposes
* each action and selector from the input modules as a method which operates on * each action and selector from the input modules as a method. For example, if
* the store. * a store is created from a module that has a `getWidget(<id>)` selector and
* an `addWidget(<object>)` action, a consumer would use `store.getWidget(<id>)`
* to fetch an item and `store.addWidget(<object>)` to dispatch an action that
* adds an item. External consumers of the store should in most cases use these
* selector and action methods rather than `getState` or `dispatch`. This
* makes it easier to refactor the internal state structure.
*
* Preact UI components access stores via the `useStoreProxy` hook defined in
* `use-store.js`. This returns a proxy which enables UI components to observe
* what store state a component depends upon and re-render when it changes.
* *
* @param {Module<any,any,any,any>[]} modules * @param {Module<any,any,any,any>[]} modules
* @param {any[]} [initArgs] - Arguments to pass to each state module's `init` function * @param {any[]} [initArgs] - Arguments to pass to each state module's `init` function
* @param {any[]} [middleware] - List of additional Redux middlewares to use * @param {any[]} [middleware] - List of additional Redux middlewares to use
* @return Store<any,any,any> * @return Store<any,any,any>
*/ */
export default function createStore(modules, initArgs = [], middleware = []) { export function createStore(modules, initArgs = [], middleware = []) {
// Create the initial state and state update function. // Create the initial state and state update function.
// Namespaced objects for initial states. // Namespaced objects for initial states.
......
/** import { createStore } from './create-store';
* Central store of state for the sidebar application, managed using
* [Redux](http://redux.js.org/).
*
* State management in Redux apps work as follows:
*
* 1. All important application state is stored in a single, immutable object.
* 2. The user interface is a presentation of this state. Interaction with the
* UI triggers updates by creating `actions`.
* 3. Actions are plain JS objects which describe some event that happened in
* the application. Updates happen by passing actions to a `reducer`
* function which takes the current application state, the action and
* returns the new application state.
*
* The process of updating the app state using an action is known as
* 'dispatching' the action.
* 4. Other parts of the app can subscribe to changes in the app state.
* This is used to to update the UI etc.
*
* "middleware" functions can wrap the dispatch process in order to implement
* logging, trigger side effects etc.
*
* Tests for a given action consist of:
*
* 1. Checking that the UI (or other event source) dispatches the correct
* action when something happens.
* 2. Checking that given an initial state, and an action, a reducer returns
* the correct resulting state.
* 3. Checking that the UI correctly presents a given state.
*/
import createStore from './create-store';
import debugMiddleware from './debug-middleware'; import debugMiddleware from './debug-middleware';
import activity from './modules/activity'; import activity from './modules/activity';
import annotations from './modules/annotations'; import annotations from './modules/annotations';
...@@ -74,18 +43,19 @@ import viewer from './modules/viewer'; ...@@ -74,18 +43,19 @@ import viewer from './modules/viewer';
*/ */
/** /**
* Factory which creates the sidebar app's state store. * Create the central state store for the sidebar application.
*
* This is a Redux [1] store composed of several modules, augmented with
* _selector_ methods for querying it and _action_ methods for applying updates.
* See the `createStore` documentation for API and usage details.
* *
* Returns a Redux store augmented with methods for each action and selector in * [1] https://redux.js.org
* the individual state modules. ie. `store.actionName(args)` dispatches an
* action through the store and `store.selectorName(args)` invokes a selector
* passing the current state of the store.
* *
* @param {import('../../types/config').SidebarConfig} settings * @param {import('../../types/config').SidebarConfig} settings
* @return {SidebarStore} * @return {SidebarStore}
* @inject
*/ */
// @inject export function createSidebarStore(settings) {
export default function store(settings) {
const middleware = [debugMiddleware]; const middleware = [debugMiddleware];
const modules = [ const modules = [
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import activity from '../activity'; import activity from '../activity';
describe('sidebar/store/modules/activity', () => { describe('sidebar/store/modules/activity', () => {
......
import * as fixtures from '../../../test/annotation-fixtures'; import * as fixtures from '../../../test/annotation-fixtures';
import * as metadata from '../../../helpers/annotation-metadata'; import * as metadata from '../../../helpers/annotation-metadata';
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import annotations from '../annotations'; import annotations from '../annotations';
import route from '../route'; import route from '../route';
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import defaults from '../defaults'; import defaults from '../defaults';
describe('store/modules/defaults', function () { describe('store/modules/defaults', function () {
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import directLinked from '../direct-linked'; import directLinked from '../direct-linked';
describe('sidebar/store/modules/direct-linked', () => { describe('sidebar/store/modules/direct-linked', () => {
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import annotations from '../annotations'; import annotations from '../annotations';
import drafts from '../drafts'; import drafts from '../drafts';
import { Draft } from '../drafts'; import { Draft } from '../drafts';
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import filters from '../filters'; import filters from '../filters';
import selection from '../selection'; import selection from '../selection';
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import frames from '../frames'; import frames from '../frames';
describe('sidebar/store/modules/frames', function () { describe('sidebar/store/modules/frames', function () {
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import groups from '../groups'; import groups from '../groups';
import session from '../session'; import session from '../session';
import immutable from '../../../util/immutable'; import immutable from '../../../util/immutable';
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import links from '../links'; import links from '../links';
describe('sidebar/store/modules/links', () => { describe('sidebar/store/modules/links', () => {
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import annotations from '../annotations'; import annotations from '../annotations';
import groups from '../groups'; import groups from '../groups';
import realTimeUpdates from '../real-time-updates'; import realTimeUpdates from '../real-time-updates';
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import route from '../route'; import route from '../route';
describe('store/modules/route', () => { describe('store/modules/route', () => {
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import annotations from '../annotations'; import annotations from '../annotations';
import filters from '../filters'; import filters from '../filters';
import selection from '../selection'; import selection from '../selection';
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import session from '../session'; import session from '../session';
describe('sidebar/store/modules/session', () => { describe('sidebar/store/modules/session', () => {
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import sidebarPanels from '../sidebar-panels'; import sidebarPanels from '../sidebar-panels';
describe('sidebar/store/modules/sidebar-panels', () => { describe('sidebar/store/modules/sidebar-panels', () => {
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import toastMessages from '../toast-messages'; import toastMessages from '../toast-messages';
describe('store/modules/toast-messages', function () { describe('store/modules/toast-messages', function () {
......
import createStore from '../../create-store'; import { createStore } from '../../create-store';
import viewer from '../viewer'; import viewer from '../viewer';
describe('store/modules/viewer', function () { describe('store/modules/viewer', function () {
......
/* global process */ /* global process */
import createStore from '../create-store'; import { createStore } from '../create-store';
const A = 0; const A = 0;
...@@ -73,7 +73,7 @@ function counterStore(initArgs = [], middleware = []) { ...@@ -73,7 +73,7 @@ function counterStore(initArgs = [], middleware = []) {
return createStore(modules, initArgs, middleware); return createStore(modules, initArgs, middleware);
} }
describe('sidebar/store/create-store', () => { describe('createStore', () => {
it('returns a working Redux store', () => { it('returns a working Redux store', () => {
const store = counterStore(); const store = counterStore();
assert.equal(store.getState().a.count, 0); assert.equal(store.getState().a.count, 0);
......
import * as annotationFixtures from '../../test/annotation-fixtures'; import * as annotationFixtures from '../../test/annotation-fixtures';
import storeFactory from '../index'; import { createSidebarStore } from '../index';
import immutable from '../../util/immutable'; import immutable from '../../util/immutable';
const defaultAnnotation = annotationFixtures.defaultAnnotation; const defaultAnnotation = annotationFixtures.defaultAnnotation;
...@@ -16,7 +16,7 @@ const fixtures = immutable({ ...@@ -16,7 +16,7 @@ const fixtures = immutable({
], ],
}); });
describe('store', function () { describe('createSidebarStore', function () {
let store; let store;
function tagForID(id) { function tagForID(id) {
...@@ -28,7 +28,7 @@ describe('store', function () { ...@@ -28,7 +28,7 @@ describe('store', function () {
} }
beforeEach(function () { beforeEach(function () {
store = storeFactory({}); store = createSidebarStore({});
}); });
describe('initialization', function () { describe('initialization', function () {
...@@ -38,12 +38,12 @@ describe('store', function () { ...@@ -38,12 +38,12 @@ describe('store', function () {
}); });
it('sets the selection when settings.annotations is set', function () { it('sets the selection when settings.annotations is set', function () {
store = storeFactory({ annotations: 'testid' }); store = createSidebarStore({ annotations: 'testid' });
assert.deepEqual(store.selectedAnnotations(), ['testid']); assert.deepEqual(store.selectedAnnotations(), ['testid']);
}); });
it('expands the selected annotations when settings.annotations is set', function () { it('expands the selected annotations when settings.annotations is set', function () {
store = storeFactory({ annotations: 'testid' }); store = createSidebarStore({ annotations: 'testid' });
assert.deepEqual(store.expandedMap(), { assert.deepEqual(store.expandedMap(), {
testid: true, testid: true,
}); });
......
import { mount } from 'enzyme'; import { mount } from 'enzyme';
import { act } from 'preact/test-utils'; import { act } from 'preact/test-utils';
import createStore from '../create-store'; import { createStore } from '../create-store';
import { useStoreProxy, $imports } from '../use-store'; import { useStoreProxy, $imports } from '../use-store';
// Store module for use with `createStore` in tests. // Store module for use with `createStore` in tests.
......
...@@ -3,7 +3,7 @@ import { useReducer } from 'preact/hooks'; ...@@ -3,7 +3,7 @@ import { useReducer } from 'preact/hooks';
import { act } from 'preact/test-utils'; import { act } from 'preact/test-utils';
import { Injector } from '../../../shared/injector'; import { Injector } from '../../../shared/injector';
import storeFactory from '../../store'; import { createSidebarStore } from '../../store';
import { ServiceContext } from '../../service-context'; import { ServiceContext } from '../../service-context';
import useRootThread from '../../components/hooks/use-root-thread'; import useRootThread from '../../components/hooks/use-root-thread';
...@@ -46,7 +46,7 @@ describe('integration: annotation threading', () => { ...@@ -46,7 +46,7 @@ describe('integration: annotation threading', () => {
beforeEach(function () { beforeEach(function () {
const container = new Injector() const container = new Injector()
.register('store', storeFactory) .register('store', { factory: createSidebarStore })
.register('annotationsService', () => {}) .register('annotationsService', () => {})
.register('settings', { value: {} }); .register('settings', { value: {} });
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment