From b1595567db7445331533a069d249a6fc9f3a8c3c Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Sun, 13 Nov 2016 08:21:48 +0530 Subject: [PATCH 01/14] Integrate podda into the UI module. --- package.json | 1 + src/context.js | 3 ++- src/index.js | 13 ++++++++++++- src/modules/ui/actions/ui.js | 9 ++++++--- src/modules/ui/index.js | 3 +++ yarn.lock | 9 ++++++++- 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 30f7908..b3f235c 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "keycode": "^2.1.1", "lodash.pick": "^4.2.1", "mantra-core": "^1.7.0", + "podda": "^1.2.1", "qs": "^6.2.0", "react-fuzzy": "^0.3.3", "react-inspector": "^1.1.0", diff --git a/src/context.js b/src/context.js index c3e8221..0736d91 100644 --- a/src/context.js +++ b/src/context.js @@ -1,6 +1,7 @@ -export default function (reduxStore, domNode, provider) { +export default function (reduxStore, clientStore, domNode, provider) { return { reduxStore, + clientStore, domNode, provider, }; diff --git a/src/index.js b/src/index.js index 7ee1e37..673d8a8 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,6 @@ import { createStore, combineReducers } from 'redux'; import { createApp } from 'mantra-core'; +import Podda from 'podda'; import buildContext from './context.js'; import shortcutsModule from './modules/shortcuts'; @@ -31,7 +32,17 @@ export default function (domNode, provider) { const devTools = window.devToolsExtension && window.devToolsExtension(); const reduxStore = createStore(reducer, devTools); - const context = buildContext(reduxStore, domNode, provider); + const defaultState = { + ...shortcutsModule.defaultState, + ...apiModule.defaultState, + ...uiModule.defaultState, + }; + const clientStore = new Podda(defaultState); + clientStore.registerAPI('toggle', (store, key) => { + return store.set(key, !store.get(key)); + }); + + const context = buildContext(reduxStore, clientStore, domNode, provider); const app = createApp(context); app.loadModule(shortcutsModule); diff --git a/src/modules/ui/actions/ui.js b/src/modules/ui/actions/ui.js index f1a6879..83e5bd9 100755 --- a/src/modules/ui/actions/ui.js +++ b/src/modules/ui/actions/ui.js @@ -1,23 +1,26 @@ import { types } from './'; export default { - setStoryFilter({ reduxStore }, filter) { + setStoryFilter({ reduxStore, clientStore }, filter) { reduxStore.dispatch({ type: types.SET_STORY_FILTER, filter, }); + clientStore.set('storyFilter', filter); }, - toggleShortcutsHelp({ reduxStore }) { + toggleShortcutsHelp({ reduxStore, clientStore }) { reduxStore.dispatch({ type: types.TOGGLE_SHORTCUTS_HELP, }); + clientStore.toggle('showShortcutsHelp'); }, - selectDownPanel({ reduxStore }, panelName) { + selectDownPanel({ reduxStore, clientStore }, panelName) { reduxStore.dispatch({ type: types.SELECT_BOTTOM_PANEL, panelName, }); + clientStore.set('selectedBottomPanel', panelName); }, }; diff --git a/src/modules/ui/index.js b/src/modules/ui/index.js index 1fe79a3..63a14eb 100755 --- a/src/modules/ui/index.js +++ b/src/modules/ui/index.js @@ -9,6 +9,9 @@ export default { routes, actions, reducers, + defaultState: { + showShortcutsHelp: false, + }, load(c, a) { initPanels(c, a); handleRouting(c, a); diff --git a/yarn.lock b/yarn.lock index 212e3ed..62884a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2147,7 +2147,7 @@ ignore@^3.1.2: version "3.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.2.0.tgz#8d88f03c3002a0ac52114db25d2c673b0bf1e435" -immutable@^3.7.6: +immutable@^3.7.6, immutable@^3.8.1: version "3.8.1" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" @@ -3055,6 +3055,13 @@ pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" +podda: + version "1.2.1" + resolved "https://registry.yarnpkg.com/podda/-/podda-1.2.1.tgz#ea8d9d30a95eae9bdb736a52b2dc56fad5d21bd8" + dependencies: + babel-runtime "^6.11.6" + immutable "^3.8.1" + prelude-ls@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" From 161bce717c87326c0bf3d737e6a328f8d6e5a08a Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Sun, 13 Nov 2016 08:36:32 +0530 Subject: [PATCH 02/14] Add podda support to the api module. --- src/modules/api/actions/api.js | 70 +++++++++++++++++++++++++++++++--- src/modules/api/index.js | 6 +++ 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/modules/api/actions/api.js b/src/modules/api/actions/api.js index 96ad71c..d9fddfd 100755 --- a/src/modules/api/actions/api.js +++ b/src/modules/api/actions/api.js @@ -3,39 +3,99 @@ import { types } from './'; import { defaultState } from '../configs/reducers/api'; export default { - setStories({ reduxStore }, stories) { + setStories({ reduxStore, clientStore }, stories) { reduxStore.dispatch({ type: types.SET_STORIES, stories, }); + + clientStore.set('stories', stories); }, - selectStory({ reduxStore }, kind, story) { + selectStory({ reduxStore, clientStore }, kind, story) { reduxStore.dispatch({ type: types.SELECT_STORY, kind, story, }); + + clientStore.set('selectedKind', kind); + clientStore.set('selectedStory', story); }, - jumpToStory({ reduxStore }, direction) { + jumpToStory({ reduxStore, clientStore }, direction) { reduxStore.dispatch({ type: types.JUMP_TO_STORY, direction, }); + + clientStore.update((state) => { + return jumpToStory(state.stories, state.selectedKind, state.selectedStory, direction); + }); }, - setOptions({ reduxStore }, options) { + setOptions({ reduxStore, clientStore }, options) { reduxStore.dispatch({ type: types.SET_OPTIONS, options: pick(options, Object.keys(defaultState.options)), }); + + clientStore.update((state) => { + const newOptions = pick(options, Object.keys(state.options)); + const updatedOptions = { + ...state.options, + newOptions, + }; + + return { options: updatedOptions }; + }); }, - setQueryParams({ reduxStore }, customQueryParams) { + setQueryParams({ reduxStore, clientStore }, customQueryParams) { reduxStore.dispatch({ type: types.SET_QUERY_PARAMS, customQueryParams, }); + + clientStore.update((state) => { + const updatedQueryParams = { + ...state.customQueryParams, + customQueryParams, + }; + + Object.keys(customQueryParams).forEach(key => { + if (updatedQueryParams[key] === null) { + delete updatedQueryParams[key]; + } + }); + + return { + customQueryParams: updatedQueryParams, + }; + }) }, }; + +export function jumpToStory(storyKinds, selectedKind, selectedStory, direction) { + const flatteredStories = []; + let currentIndex = -1; + + storyKinds.forEach(({ kind, stories }) => { + stories.forEach((story) => { + flatteredStories.push({ kind, story }); + if (kind === selectedKind && story === selectedStory) { + currentIndex = flatteredStories.length - 1; + } + }); + }); + + const jumpedStory = flatteredStories[currentIndex + direction]; + if (!jumpedStory) { + return { selectedKind, selectedStory }; + } + + return { + selectedKind: jumpedStory.kind, + selectedStory: jumpedStory.story, + }; +} diff --git a/src/modules/api/index.js b/src/modules/api/index.js index 480c1c7..0a305f3 100755 --- a/src/modules/api/index.js +++ b/src/modules/api/index.js @@ -5,6 +5,12 @@ import initApi from './configs/init_api'; export default { reducers, actions, + defaultState: { + options: { + name: 'REACT STORYBOOK', + url: 'https://github.com/kadirahq/react-storybook', + }, + }, load({ reduxStore, provider }, _actions) { initApi(provider, reduxStore, _actions); }, From 730477dd0004b0a0976581def1865993a0890b24 Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Sun, 13 Nov 2016 09:00:43 +0530 Subject: [PATCH 03/14] Add podda to the shortcuts module. --- src/modules/shortcuts/actions/shortcuts.js | 45 ++++++++++++++++++++-- src/modules/shortcuts/index.js | 9 +++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/modules/shortcuts/actions/shortcuts.js b/src/modules/shortcuts/actions/shortcuts.js index d6d8d75..3d9c18b 100755 --- a/src/modules/shortcuts/actions/shortcuts.js +++ b/src/modules/shortcuts/actions/shortcuts.js @@ -6,7 +6,7 @@ import { defaultState } from '../configs/reducers/shortcuts'; export default { handleEvent(context, event) { - const { reduxStore } = context; + const { reduxStore, clientStore } = context; switch (event) { case features.NEXT_STORY: apiActions.api.jumpToStory(context, 1); @@ -19,6 +19,18 @@ export default { type: types.HANDLE_EVENT, event, }); + + clientStore.update((state) => { + const newOptions = keyEventToOptions(state.shortcutOptions); + const updatedOptions = { + ...state.shortcutOptions, + ...newOptions, + }; + + return { + shortcutOptions: updatedOptions + }; + }); } }, @@ -30,11 +42,38 @@ export default { }); }, - setOptions(context, options) { - const { reduxStore } = context; + setOptions({ reduxStore, clientStore }, options) { reduxStore.dispatch({ type: types.SET_LAYOUT, layout: pick(options, Object.keys(defaultState)), }); + + clientStore.update((state) => { + const updatedOptions = { + ...state.shortcutOptions, + ...pick(options, Object.keys(state.shortcutOptions)) + }; + + return { + shortcutOptions: updatedOptions, + }; + }); }, }; + +export function keyEventToOptions(currentOptions, event) { + switch (event) { + case features.FULLSCREEN: + return { goFullScreen: !currentOptions.goFullScreen }; + case features.DOWN_PANEL: + return { showDownPanel: !currentOptions.showDownPanel }; + case features.LEFT_PANEL: + return { showLeftPanel: !currentOptions.showLeftPanel }; + case features.SEARCH: + return { showSearchBox: !currentOptions.showSearchBox }; + case features.DOWN_PANEL_IN_RIGHT: + return { downPanelInRight: !currentOptions.downPanelInRight }; + default: + return {}; + } +} diff --git a/src/modules/shortcuts/index.js b/src/modules/shortcuts/index.js index 7c37bba..49be763 100755 --- a/src/modules/shortcuts/index.js +++ b/src/modules/shortcuts/index.js @@ -4,4 +4,13 @@ import reducers from './configs/reducers'; export default { reducers, actions, + defaultState: { + shortcutOptions: { + goFullScreen: false, + showLeftPanel: true, + showDownPanel: true, + showSearchBox: false, + downPanelInRight: false, + } + } }; From 72108b40cdd7557936ade9df28b086a858c642da Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Sun, 13 Nov 2016 09:34:55 +0530 Subject: [PATCH 04/14] Remove unwanted code. --- src/modules/shortcuts/actions/shortcuts.js | 8 -------- src/modules/ui/configs/handle_routing.js | 2 +- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/src/modules/shortcuts/actions/shortcuts.js b/src/modules/shortcuts/actions/shortcuts.js index 3d9c18b..9ee30f2 100755 --- a/src/modules/shortcuts/actions/shortcuts.js +++ b/src/modules/shortcuts/actions/shortcuts.js @@ -34,14 +34,6 @@ export default { } }, - setLayout(context, layout) { - const { reduxStore } = context; - reduxStore.dispatch({ - type: types.SET_LAYOUT, - layout, - }); - }, - setOptions({ reduxStore, clientStore }, options) { reduxStore.dispatch({ type: types.SET_LAYOUT, diff --git a/src/modules/ui/configs/handle_routing.js b/src/modules/ui/configs/handle_routing.js index f1edc5d..dc5b3dc 100755 --- a/src/modules/ui/configs/handle_routing.js +++ b/src/modules/ui/configs/handle_routing.js @@ -64,7 +64,7 @@ export function updateStore(queryParams, actions) { actions.api.selectStory(selectedKind, selectedStory); } - actions.shortcuts.setLayout({ + actions.shortcuts.setOptions({ goFullScreen: Boolean(Number(full)), showDownPanel: Boolean(Number(down)), showLeftPanel: Boolean(Number(left)), From ccb5a517136174b1c157ddfec1e645dad6ca975a Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Sun, 13 Nov 2016 09:57:39 +0530 Subject: [PATCH 05/14] Use podda inside containers. --- src/modules/api/actions/api.js | 41 +++++++++++++++++++-- src/modules/shortcuts/actions/shortcuts.js | 2 +- src/modules/ui/actions/ui.js | 2 +- src/modules/ui/containers/down_panel.js | 8 ++-- src/modules/ui/containers/layout.js | 8 ++-- src/modules/ui/containers/left_panel.js | 9 ++--- src/modules/ui/containers/search_box.js | 10 ++--- src/modules/ui/containers/shortcuts_help.js | 8 ++-- src/modules/ui/libs/gen_podda_loader.js | 18 +++++++++ src/modules/ui/libs/gen_redux_loader.js | 2 +- 10 files changed, 80 insertions(+), 28 deletions(-) create mode 100644 src/modules/ui/libs/gen_podda_loader.js diff --git a/src/modules/api/actions/api.js b/src/modules/api/actions/api.js index d9fddfd..cbda3c0 100755 --- a/src/modules/api/actions/api.js +++ b/src/modules/api/actions/api.js @@ -9,7 +9,16 @@ export default { stories, }); - clientStore.set('stories', stories); + clientStore.update((state) => { + const selectedKind = ensureKind(stories, state.selectedKind); + const selectedStory = ensureStory(stories, state.selectedKind, state.selectedStory); + + return { + stories, + selectedStory, + selectedKind, + }; + }); }, selectStory({ reduxStore, clientStore }, kind, story) { @@ -19,8 +28,12 @@ export default { story, }); - clientStore.set('selectedKind', kind); - clientStore.set('selectedStory', story); + clientStore.update((state) => { + const selectedKind = ensureKind(state.stories, kind); + const selectedStory = ensureStory(state.stories, selectedKind, story); + + return { selectedKind, selectedStory }; + }); }, jumpToStory({ reduxStore, clientStore }, direction) { @@ -99,3 +112,25 @@ export function jumpToStory(storyKinds, selectedKind, selectedStory, direction) selectedStory: jumpedStory.story, }; } + +export function ensureKind(storyKinds, selectedKind) { + if (!storyKinds) return selectedKind; + + const found = storyKinds.find(item => item.kind === selectedKind); + if (found) return found.kind; + // if the selected kind is non-existant, select the first kind + const kinds = storyKinds.map(item => item.kind); + return kinds[0]; +} + +export function ensureStory(storyKinds, selectedKind, selectedStory) { + if (!storyKinds) return selectedStory; + + const kindInfo = storyKinds.find(item => item.kind === selectedKind); + if (!kindInfo) return null; + + const found = kindInfo.stories.find(item => item === selectedStory); + if (found) return found; + + return kindInfo.stories[0]; +} diff --git a/src/modules/shortcuts/actions/shortcuts.js b/src/modules/shortcuts/actions/shortcuts.js index 9ee30f2..e7460cf 100755 --- a/src/modules/shortcuts/actions/shortcuts.js +++ b/src/modules/shortcuts/actions/shortcuts.js @@ -21,7 +21,7 @@ export default { }); clientStore.update((state) => { - const newOptions = keyEventToOptions(state.shortcutOptions); + const newOptions = keyEventToOptions(state.shortcutOptions, event); const updatedOptions = { ...state.shortcutOptions, ...newOptions, diff --git a/src/modules/ui/actions/ui.js b/src/modules/ui/actions/ui.js index 83e5bd9..3f2effd 100755 --- a/src/modules/ui/actions/ui.js +++ b/src/modules/ui/actions/ui.js @@ -21,6 +21,6 @@ export default { type: types.SELECT_BOTTOM_PANEL, panelName, }); - clientStore.set('selectedBottomPanel', panelName); + clientStore.set('selectedDownPanel', panelName); }, }; diff --git a/src/modules/ui/containers/down_panel.js b/src/modules/ui/containers/down_panel.js index 3e7f17c..011d6c2 100644 --- a/src/modules/ui/containers/down_panel.js +++ b/src/modules/ui/containers/down_panel.js @@ -1,11 +1,11 @@ import DownPanel from '../components/down_panel'; -import genReduxLoader from '../libs/gen_redux_loader'; +import genPoddaLoader from '../libs/gen_podda_loader'; import compose from '../../../compose'; -export function mapper({ ui }, props, { context, actions }) { +export function mapper(state, props, { context, actions }) { const panels = context().provider.getPanels(); const actionMap = actions(); - const selectedPanel = ui.selectedDownPanel; + const selectedPanel = state.selectedDownPanel; return { panels, @@ -14,4 +14,4 @@ export function mapper({ ui }, props, { context, actions }) { }; } -export default compose(genReduxLoader(mapper))(DownPanel); +export default compose(genPoddaLoader(mapper))(DownPanel); diff --git a/src/modules/ui/containers/layout.js b/src/modules/ui/containers/layout.js index 2625411..cc21cb0 100755 --- a/src/modules/ui/containers/layout.js +++ b/src/modules/ui/containers/layout.js @@ -1,11 +1,11 @@ import pick from 'lodash.pick'; import Layout from '../components/layout'; -import genReduxLoader from '../libs/gen_redux_loader'; +import genPoddaLoader from '../libs/gen_podda_loader'; import compose from '../../../compose'; -export const mapper = ({ shortcuts }) => { +export const mapper = ({ shortcutOptions }) => { return pick( - shortcuts, + shortcutOptions, 'showLeftPanel', 'showDownPanel', 'goFullScreen', @@ -13,4 +13,4 @@ export const mapper = ({ shortcuts }) => { ); }; -export default compose(genReduxLoader(mapper))(Layout); +export default compose(genPoddaLoader(mapper))(Layout); diff --git a/src/modules/ui/containers/left_panel.js b/src/modules/ui/containers/left_panel.js index 7874ddc..151b2e7 100755 --- a/src/modules/ui/containers/left_panel.js +++ b/src/modules/ui/containers/left_panel.js @@ -1,12 +1,11 @@ import LeftPanel from '../components/left_panel'; import * as filters from '../libs/filters'; -import genReduxLoader from '../libs/gen_redux_loader'; +import genPoddaLoader from '../libs/gen_podda_loader'; import compose from '../../../compose'; -export const mapper = ({ api, ui }, props, { actions }) => { +export const mapper = (state, props, { actions }) => { const actionMap = actions(); - const { stories, selectedKind, selectedStory, options } = api; - const { storyFilter } = ui; + const { stories, selectedKind, selectedStory, options, storyFilter } = state; const data = { stories: filters.storyFilter(stories, storyFilter, selectedKind, selectedStory), @@ -25,4 +24,4 @@ export const mapper = ({ api, ui }, props, { actions }) => { return data; }; -export default compose(genReduxLoader(mapper))(LeftPanel); +export default compose(genPoddaLoader(mapper))(LeftPanel); diff --git a/src/modules/ui/containers/search_box.js b/src/modules/ui/containers/search_box.js index 5042d41..7df3620 100644 --- a/src/modules/ui/containers/search_box.js +++ b/src/modules/ui/containers/search_box.js @@ -1,15 +1,15 @@ import SearchBox from '../components/search_box'; -import genReduxLoader from '../libs/gen_redux_loader'; +import genPoddaLoader from '../libs/gen_podda_loader'; import compose from '../../../compose'; -export const mapper = ({ api, shortcuts }, props, { actions }) => { +export const mapper = (state, props, { actions }) => { const actionMap = actions(); return { - showSearchBox: shortcuts.showSearchBox, - stories: api.stories, + showSearchBox: state.shortcutOptions.showSearchBox, + stories: state.stories, onSelectStory: actionMap.api.selectStory, handleEvent: actionMap.shortcuts.handleEvent, }; }; -export default compose(genReduxLoader(mapper))(SearchBox); +export default compose(genPoddaLoader(mapper))(SearchBox); diff --git a/src/modules/ui/containers/shortcuts_help.js b/src/modules/ui/containers/shortcuts_help.js index 867a0f3..10495eb 100755 --- a/src/modules/ui/containers/shortcuts_help.js +++ b/src/modules/ui/containers/shortcuts_help.js @@ -1,11 +1,11 @@ import ShortcutsHelp from '../components/shortcuts_help'; -import genReduxLoader from '../libs/gen_redux_loader'; +import genPoddaLoader from '../libs/gen_podda_loader'; import compose from '../../../compose'; -export const mapper = ({ ui }, props, { actions }) => { +export const mapper = (state, props, { actions }) => { const actionMap = actions(); const data = { - isOpen: ui.showShortcutsHelp, + isOpen: state.showShortcutsHelp, onClose: actionMap.ui.toggleShortcutsHelp, platform: window.navigator.platform.toLowerCase(), }; @@ -13,4 +13,4 @@ export const mapper = ({ ui }, props, { actions }) => { return data; }; -export default compose(genReduxLoader(mapper))(ShortcutsHelp); +export default compose(genPoddaLoader(mapper))(ShortcutsHelp); diff --git a/src/modules/ui/libs/gen_podda_loader.js b/src/modules/ui/libs/gen_podda_loader.js new file mode 100644 index 0000000..fd669a4 --- /dev/null +++ b/src/modules/ui/libs/gen_podda_loader.js @@ -0,0 +1,18 @@ +export default function genPoddaLoader(fn) { + return (props, onData, env) => { + const { clientStore } = env.context(); + + const processState = () => { + try { + const state = clientStore.getAll(); + const data = fn(state, props, env); + onData(null, data); + } catch (ex) { + onData(ex); + } + }; + + processState(); + return clientStore.subscribe(processState); + }; +} diff --git a/src/modules/ui/libs/gen_redux_loader.js b/src/modules/ui/libs/gen_redux_loader.js index 66b36b1..3d33c51 100644 --- a/src/modules/ui/libs/gen_redux_loader.js +++ b/src/modules/ui/libs/gen_redux_loader.js @@ -13,6 +13,6 @@ export default function getReduxLoader(fn) { }; processState(); - reduxStore.subscribe(processState); + return reduxStore.subscribe(processState); }; } From 7ca23a4e51b20b65845da9565bdca80f44b585ee Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Sun, 13 Nov 2016 10:15:40 +0530 Subject: [PATCH 06/14] Move everything related to redux from the codebase. --- src/context.js | 3 +- src/index.js | 11 +- src/modules/api/actions/api.js | 30 +- src/modules/api/configs/init_api.js | 22 +- .../api/configs/reducers/__tests__/api.js | 322 ------------------ src/modules/api/configs/reducers/api.js | 126 ------- src/modules/api/configs/reducers/index.js | 5 - src/modules/api/index.js | 6 +- src/modules/shortcuts/actions/index.js | 5 - src/modules/shortcuts/actions/shortcuts.js | 16 +- .../shortcuts/configs/reducers/index.js | 5 - .../shortcuts/configs/reducers/shortcuts.js | 48 --- src/modules/shortcuts/index.js | 2 - src/modules/ui/actions/ui.js | 17 +- src/modules/ui/configs/handle_routing.js | 20 +- .../ui/configs/reducers/__tests__/ui.js | 36 -- src/modules/ui/configs/reducers/index.js | 5 - src/modules/ui/configs/reducers/ui.js | 33 -- src/modules/ui/index.js | 2 - src/modules/ui/routes.js | 6 +- 20 files changed, 34 insertions(+), 686 deletions(-) delete mode 100755 src/modules/api/configs/reducers/__tests__/api.js delete mode 100755 src/modules/api/configs/reducers/api.js delete mode 100755 src/modules/api/configs/reducers/index.js delete mode 100755 src/modules/shortcuts/configs/reducers/index.js delete mode 100755 src/modules/shortcuts/configs/reducers/shortcuts.js delete mode 100755 src/modules/ui/configs/reducers/__tests__/ui.js delete mode 100755 src/modules/ui/configs/reducers/index.js delete mode 100755 src/modules/ui/configs/reducers/ui.js diff --git a/src/context.js b/src/context.js index 0736d91..38c5e44 100644 --- a/src/context.js +++ b/src/context.js @@ -1,6 +1,5 @@ -export default function (reduxStore, clientStore, domNode, provider) { +export default function (clientStore, domNode, provider) { return { - reduxStore, clientStore, domNode, provider, diff --git a/src/index.js b/src/index.js index 673d8a8..a9debf0 100644 --- a/src/index.js +++ b/src/index.js @@ -23,15 +23,6 @@ export default function (domNode, provider) { throw new Error('provider is not extended from the base Provider'); } - const reducer = combineReducers({ - ...shortcutsModule.reducers, - ...apiModule.reducers, - ...uiModule.reducers, - }); - - const devTools = window.devToolsExtension && window.devToolsExtension(); - const reduxStore = createStore(reducer, devTools); - const defaultState = { ...shortcutsModule.defaultState, ...apiModule.defaultState, @@ -42,7 +33,7 @@ export default function (domNode, provider) { return store.set(key, !store.get(key)); }); - const context = buildContext(reduxStore, clientStore, domNode, provider); + const context = buildContext(clientStore, domNode, provider); const app = createApp(context); app.loadModule(shortcutsModule); diff --git a/src/modules/api/actions/api.js b/src/modules/api/actions/api.js index cbda3c0..1d99ea1 100755 --- a/src/modules/api/actions/api.js +++ b/src/modules/api/actions/api.js @@ -1,17 +1,10 @@ import pick from 'lodash.pick'; -import { types } from './'; -import { defaultState } from '../configs/reducers/api'; export default { setStories({ reduxStore, clientStore }, stories) { - reduxStore.dispatch({ - type: types.SET_STORIES, - stories, - }); - clientStore.update((state) => { const selectedKind = ensureKind(stories, state.selectedKind); - const selectedStory = ensureStory(stories, state.selectedKind, state.selectedStory); + const selectedStory = ensureStory(stories, selectedKind, state.selectedStory); return { stories, @@ -22,12 +15,6 @@ export default { }, selectStory({ reduxStore, clientStore }, kind, story) { - reduxStore.dispatch({ - type: types.SELECT_STORY, - kind, - story, - }); - clientStore.update((state) => { const selectedKind = ensureKind(state.stories, kind); const selectedStory = ensureStory(state.stories, selectedKind, story); @@ -37,22 +24,12 @@ export default { }, jumpToStory({ reduxStore, clientStore }, direction) { - reduxStore.dispatch({ - type: types.JUMP_TO_STORY, - direction, - }); - clientStore.update((state) => { return jumpToStory(state.stories, state.selectedKind, state.selectedStory, direction); }); }, setOptions({ reduxStore, clientStore }, options) { - reduxStore.dispatch({ - type: types.SET_OPTIONS, - options: pick(options, Object.keys(defaultState.options)), - }); - clientStore.update((state) => { const newOptions = pick(options, Object.keys(state.options)); const updatedOptions = { @@ -65,11 +42,6 @@ export default { }, setQueryParams({ reduxStore, clientStore }, customQueryParams) { - reduxStore.dispatch({ - type: types.SET_QUERY_PARAMS, - customQueryParams, - }); - clientStore.update((state) => { const updatedQueryParams = { ...state.customQueryParams, diff --git a/src/modules/api/configs/init_api.js b/src/modules/api/configs/init_api.js index 5bc82bc..29d11a7 100644 --- a/src/modules/api/configs/init_api.js +++ b/src/modules/api/configs/init_api.js @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; -export default function (provider, reduxStore, actions) { +export default function (provider, clientStore, actions) { const callbacks = new EventEmitter(); let currentKind; let currentStory; @@ -30,9 +30,9 @@ export default function (provider, reduxStore, actions) { }, getQueryParam(key) { - const { api } = reduxStore.getState(); - if (api.customQueryParams) { - return api.customQueryParams[key]; + const state = clientStore.getAll(); + if (state.customQueryParams) { + return state.customQueryParams[key]; } return undefined; }, @@ -41,18 +41,18 @@ export default function (provider, reduxStore, actions) { provider.handleAPI(providerApi); // subscribe to redux store and trigger onStory's callback - reduxStore.subscribe(function () { - const { api } = reduxStore.getState(); - if (!api) return; + clientStore.subscribe(function () { + const state = clientStore.getAll(); + if (!state.selectedKind) return; - if (api.selectedKind === currentKind && api.selectedStory === currentStory) { + if (state.selectedKind === currentKind && state.selectedStory === currentStory) { // No change in the selected story so avoid emitting 'story' return; } - currentKind = api.selectedKind; - currentStory = api.selectedStory; - callbacks.emit('story', api.selectedKind, api.selectedStory); + currentKind = state.selectedKind; + currentStory = state.selectedStory; + callbacks.emit('story', state.selectedKind, state.selectedStory); // providerApi._onStoryCallback(api.selectedKind, api.selectedStory); }); } diff --git a/src/modules/api/configs/reducers/__tests__/api.js b/src/modules/api/configs/reducers/__tests__/api.js deleted file mode 100755 index 72742eb..0000000 --- a/src/modules/api/configs/reducers/__tests__/api.js +++ /dev/null @@ -1,322 +0,0 @@ -import reducer from '../api'; -import { expect } from 'chai'; -import { types } from '../../../actions'; -const { describe, it } = global; - -describe('manager.preview.config.reducers.preview', () => { - describe('SELECT_STORY', () => { - it('should set kind and story', () => { - const stories = [ - { - kind: 'kk', - stories: ['ss'], - }, - ]; - const action = { - type: types.SELECT_STORY, - kind: 'kk', - story: 'ss', - }; - - const newState = reducer({ stories }, action); - expect(newState.selectedKind).to.be.equal(action.kind); - expect(newState.selectedStory).to.be.equal(action.story); - }); - - it('should set the first kind, if the kind is non-exisitance', () => { - const stories = [ - { - kind: 'bb', - stories: ['ss'], - }, - ]; - const action = { - type: types.SELECT_STORY, - kind: 'kk', - story: 'ss', - }; - - const newState = reducer({ stories }, action); - expect(newState.selectedKind).to.be.equal(stories[0].kind); - expect(newState.selectedStory).to.be.equal(action.story); - }); - - it('should set the first story, if the story is non-exisitance', () => { - const stories = [ - { - kind: 'kk', - stories: ['ss', 'll'], - }, - ]; - const action = { - type: types.SELECT_STORY, - kind: 'kk', - story: 'dd', - }; - - const newState = reducer({ stories }, action); - expect(newState.selectedKind).to.be.equal(action.kind); - expect(newState.selectedStory).to.be.equal('ss'); - }); - - it('should keep selectedKind and selectedStory as is when there are no stories', () => { - const action = { - type: types.SELECT_STORY, - kind: 'kk', - story: 'ss', - }; - - const newState = reducer({}, action); - expect(newState.selectedKind).to.be.equal(action.kind); - expect(newState.selectedStory).to.be.equal(action.story); - }); - }); - - describe('SET_STORIES', () => { - it('should replace stories', () => { - const stories = { aa: 10 }; - const selectedKind = 'kk'; - const selectedStory = 'ss'; - const newStories = [ - { - kind: 'kk', - stories: ['ss'], - }, - ]; - - const action = { - type: types.SET_STORIES, - stories: newStories, - }; - - const newState = reducer({ stories, selectedKind, selectedStory }, action); - expect(newState.stories).to.deep.equal(newStories); - expect(newState.selectedKind).to.be.equal(selectedKind); - expect(newState.selectedStory).to.be.equal(selectedStory); - }); - - it('should set selectedKind again if not exists', () => { - const stories = { aa: 10 }; - const selectedKind = 'kk'; - const selectedStory = 'ss'; - const newStories = [ - { - kind: 'dd', - stories: ['ss'], - }, - ]; - - const action = { - type: types.SET_STORIES, - stories: newStories, - }; - - const newState = reducer({ stories, selectedKind, selectedStory }, action); - expect(newState.stories).to.deep.equal(newStories); - expect(newState.selectedKind).to.be.equal('dd'); - expect(newState.selectedStory).to.be.equal(selectedStory); - }); - - it('should set selectedStory again if not exists', () => { - const stories = { aa: 10 }; - const selectedKind = 'kk'; - const selectedStory = 'ss'; - const newStories = [ - { - kind: 'kk', - stories: ['pk'], - }, - ]; - - const action = { - type: types.SET_STORIES, - stories: newStories, - }; - - const newState = reducer({ stories, selectedKind, selectedStory }, action); - expect(newState.stories).to.deep.equal(newStories); - expect(newState.selectedKind).to.be.equal(selectedKind); - expect(newState.selectedStory).to.be.equal('pk'); - }); - - it('should set default selectedKind and selectedStory', () => { - const stories = { aa: 10 }; - const newStories = [ - { - kind: 'kk', - stories: ['pk'], - }, - ]; - - const action = { - type: types.SET_STORIES, - stories: newStories, - }; - - const newState = reducer({ stories }, action); - expect(newState.stories).to.deep.equal(newStories); - expect(newState.selectedKind).to.be.equal('kk'); - expect(newState.selectedStory).to.be.equal('pk'); - }); - - it('should respect existing selectedKind and selectStory', () => { - const selectedKind = 'bb'; - const selectedStory = 'dd'; - - const newStories = [ - { - kind: 'kk', - stories: ['pk'], - }, - { - kind: 'bb', - stories: ['pk', 'dd'], - }, - ]; - - const action = { - type: types.SET_STORIES, - stories: newStories, - }; - - const newState = reducer({ selectedKind, selectedStory }, action); - expect(newState.stories).to.deep.equal(newStories); - expect(newState.selectedKind).to.be.equal(selectedKind); - expect(newState.selectedStory).to.be.equal(selectedStory); - }); - }); - - describe('JUMP_TO_STORY', () => { - it('should jump to the next story', () => { - const selectedKind = 'kk'; - const selectedStory = 'ss'; - const stories = [ - { kind: 'kk', stories: ['ss'] }, - { kind: 'bb', stories: ['aa', 'cc'] }, - ]; - - const action = { - type: types.JUMP_TO_STORY, - direction: 1, - }; - - const newState = reducer({ - stories, selectedKind, selectedStory, - }, action); - - expect(newState.selectedKind).to.be.equal('bb'); - expect(newState.selectedStory).to.be.equal('aa'); - }); - - it('should jump to the prev story', () => { - const selectedKind = 'bb'; - const selectedStory = 'cc'; - const stories = [ - { kind: 'kk', stories: ['ss'] }, - { kind: 'bb', stories: ['aa', 'cc'] }, - ]; - - const action = { - type: types.JUMP_TO_STORY, - direction: -1, - }; - - const newState = reducer({ - stories, selectedKind, selectedStory, - }, action); - - expect(newState.selectedKind).to.be.equal('bb'); - expect(newState.selectedStory).to.be.equal('aa'); - }); - - it('should jump nowhere it there is no story', () => { - const selectedKind = 'kk'; - const selectedStory = 'ss'; - const stories = [ - { kind: 'kk', stories: ['ss'] }, - { kind: 'bb', stories: ['aa', 'cc'] }, - ]; - - const action = { - type: types.JUMP_TO_STORY, - direction: -10, - }; - - const newState = reducer({ - stories, selectedKind, selectedStory, - }, action); - - expect(newState.selectedKind).to.be.equal('kk'); - expect(newState.selectedStory).to.be.equal('ss'); - }); - }); -}); - -describe('SET OPTIONS', () => { - it('should set options merging with the default ones', () => { - const options = { - name: 'foo', - url: 'bar', - }; - - const action = { - type: types.SET_OPTIONS, - options: { - name: 'hello world', - }, - }; - - const expected = { - name: 'hello world', - url: 'bar', - }; - - const newState = reducer({ options }, action); - expect(newState.options).to.eql(expected); - }); -}); - -describe('SET_QUERY_PARAMS', () => { - it('should set custom query params merging with the existing ones', () => { - const customQueryParams = { - fooParams: 'foo', - barParams: 'bar', - }; - - const action = { - type: types.SET_QUERY_PARAMS, - customQueryParams: { - fooParams: 'baz', - }, - }; - - const expected = { - fooParams: 'baz', - barParams: 'bar', - }; - - const newState = reducer({ customQueryParams }, action); - expect(newState.customQueryParams).to.eql(expected); - }); - - it('should unset custom query params when the value is null', () => { - const customQueryParams = { - fooParams: 'foo', - barParams: 'bar', - }; - - const action = { - type: types.SET_QUERY_PARAMS, - customQueryParams: { - fooParams: null, - }, - }; - - const expected = { - barParams: 'bar', - }; - - const newState = reducer({ customQueryParams }, action); - expect(newState.customQueryParams).to.eql(expected); - }); -}); diff --git a/src/modules/api/configs/reducers/api.js b/src/modules/api/configs/reducers/api.js deleted file mode 100755 index 9c08cd9..0000000 --- a/src/modules/api/configs/reducers/api.js +++ /dev/null @@ -1,126 +0,0 @@ -import { types } from '../../actions'; - -export function ensureKind(storyKinds, selectedKind) { - if (!storyKinds) return selectedKind; - - const found = storyKinds.find(item => item.kind === selectedKind); - if (found) return found.kind; - // if the selected kind is non-existant, select the first kind - const kinds = storyKinds.map(item => item.kind); - return kinds[0]; -} - -export function ensureStory(storyKinds, selectedKind, selectedStory) { - if (!storyKinds) return selectedStory; - - const kindInfo = storyKinds.find(item => item.kind === selectedKind); - if (!kindInfo) return null; - - const found = kindInfo.stories.find(item => item === selectedStory); - if (found) return found; - - return kindInfo.stories[0]; -} - -export function jumpToStory(storyKinds, selectedKind, selectedStory, direction) { - const flatteredStories = []; - let currentIndex = -1; - - storyKinds.forEach(({ kind, stories }) => { - stories.forEach((story) => { - flatteredStories.push({ kind, story }); - if (kind === selectedKind && story === selectedStory) { - currentIndex = flatteredStories.length - 1; - } - }); - }); - - const jumpedStory = flatteredStories[currentIndex + direction]; - if (!jumpedStory) { - return { selectedKind, selectedStory }; - } - - return { - selectedKind: jumpedStory.kind, - selectedStory: jumpedStory.story, - }; -} - -export const defaultState = { - actions: [], - options: { - name: 'REACT STORYBOOK', - url: 'https://github.com/kadirahq/react-storybook', - }, -}; - -export default function (state = defaultState, action) { - switch (action.type) { - case types.SELECT_STORY: { - const selectedKind = ensureKind(state.stories, action.kind); - const selectedStory = ensureStory(state.stories, selectedKind, action.story); - return { - ...state, - selectedKind, - selectedStory, - }; - } - - case types.JUMP_TO_STORY: { - const { selectedKind, selectedStory } = - jumpToStory(state.stories, state.selectedKind, state.selectedStory, action.direction); - return { - ...state, - selectedKind, - selectedStory, - }; - } - - case types.SET_STORIES: { - const newState = { - ...state, - stories: action.stories, - }; - - newState.selectedKind = ensureKind(newState.stories, state.selectedKind); - newState.selectedStory = ensureStory( - newState.stories, newState.selectedKind, state.selectedStory - ); - - return newState; - } - - case types.SET_OPTIONS: { - const newOptions = { - ...state.options, - ...action.options, - }; - - return { - ...state, - options: newOptions, - }; - } - - case types.SET_QUERY_PARAMS: { - const newQueryParams = { - ...state.customQueryParams, - ...action.customQueryParams, - }; - - Object.keys(action.customQueryParams).forEach(key => { - if (newQueryParams[key] === null) { - delete newQueryParams[key]; - } - }); - - return { - ...state, - customQueryParams: newQueryParams, - }; - } - - default: - return state; - } -} diff --git a/src/modules/api/configs/reducers/index.js b/src/modules/api/configs/reducers/index.js deleted file mode 100755 index 7cb6cc4..0000000 --- a/src/modules/api/configs/reducers/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import api from './api'; - -export default { - api, -}; diff --git a/src/modules/api/index.js b/src/modules/api/index.js index 0a305f3..8c2181a 100755 --- a/src/modules/api/index.js +++ b/src/modules/api/index.js @@ -1,9 +1,7 @@ import actions from './actions'; -import reducers from './configs/reducers'; import initApi from './configs/init_api'; export default { - reducers, actions, defaultState: { options: { @@ -11,7 +9,7 @@ export default { url: 'https://github.com/kadirahq/react-storybook', }, }, - load({ reduxStore, provider }, _actions) { - initApi(provider, reduxStore, _actions); + load({ clientStore, provider }, _actions) { + initApi(provider, clientStore, _actions); }, }; diff --git a/src/modules/shortcuts/actions/index.js b/src/modules/shortcuts/actions/index.js index 4d9de77..1f8965d 100755 --- a/src/modules/shortcuts/actions/index.js +++ b/src/modules/shortcuts/actions/index.js @@ -1,10 +1,5 @@ import shortcuts from './shortcuts'; -export const types = { - HANDLE_EVENT: 'SHORTCUTS_HANDLE_EVENT', - SET_LAYOUT: 'SHORTCUTS_SET_LAYOUT', -}; - export default { shortcuts, }; diff --git a/src/modules/shortcuts/actions/shortcuts.js b/src/modules/shortcuts/actions/shortcuts.js index e7460cf..61c0c00 100755 --- a/src/modules/shortcuts/actions/shortcuts.js +++ b/src/modules/shortcuts/actions/shortcuts.js @@ -1,12 +1,10 @@ import pick from 'lodash.pick'; -import { types } from './'; import { features } from '../../../libs/key_events'; import apiActions from '../../api/actions'; -import { defaultState } from '../configs/reducers/shortcuts'; export default { handleEvent(context, event) { - const { reduxStore, clientStore } = context; + const { clientStore } = context; switch (event) { case features.NEXT_STORY: apiActions.api.jumpToStory(context, 1); @@ -15,11 +13,6 @@ export default { apiActions.api.jumpToStory(context, -1); break; default: - reduxStore.dispatch({ - type: types.HANDLE_EVENT, - event, - }); - clientStore.update((state) => { const newOptions = keyEventToOptions(state.shortcutOptions, event); const updatedOptions = { @@ -34,12 +27,7 @@ export default { } }, - setOptions({ reduxStore, clientStore }, options) { - reduxStore.dispatch({ - type: types.SET_LAYOUT, - layout: pick(options, Object.keys(defaultState)), - }); - + setOptions({ clientStore }, options) { clientStore.update((state) => { const updatedOptions = { ...state.shortcutOptions, diff --git a/src/modules/shortcuts/configs/reducers/index.js b/src/modules/shortcuts/configs/reducers/index.js deleted file mode 100755 index 1f8965d..0000000 --- a/src/modules/shortcuts/configs/reducers/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import shortcuts from './shortcuts'; - -export default { - shortcuts, -}; diff --git a/src/modules/shortcuts/configs/reducers/shortcuts.js b/src/modules/shortcuts/configs/reducers/shortcuts.js deleted file mode 100755 index 2a1d773..0000000 --- a/src/modules/shortcuts/configs/reducers/shortcuts.js +++ /dev/null @@ -1,48 +0,0 @@ -import { types } from '../../actions'; -import { features } from '../../../../libs/key_events'; - -export const defaultState = { - goFullScreen: false, - showLeftPanel: true, - showDownPanel: true, - showSearchBox: false, - downPanelInRight: false, -}; - -export function keyEventToState(state, event) { - switch (event) { - case features.FULLSCREEN: - return { goFullScreen: !state.goFullScreen }; - case features.DOWN_PANEL: - return { showDownPanel: !state.showDownPanel }; - case features.LEFT_PANEL: - return { showLeftPanel: !state.showLeftPanel }; - case features.SEARCH: - return { showSearchBox: !state.showSearchBox }; - case features.DOWN_PANEL_IN_RIGHT: - return { downPanelInRight: !state.downPanelInRight }; - default: - return {}; - } -} - -export default function (state = defaultState, action) { - switch (action.type) { - case types.HANDLE_EVENT: { - return { - ...state, - ...keyEventToState(state, action.event), - }; - } - - case types.SET_LAYOUT: { - return { - ...state, - ...action.layout, - }; - } - - default: - return state; - } -} diff --git a/src/modules/shortcuts/index.js b/src/modules/shortcuts/index.js index 49be763..d7af9ad 100755 --- a/src/modules/shortcuts/index.js +++ b/src/modules/shortcuts/index.js @@ -1,8 +1,6 @@ import actions from './actions'; -import reducers from './configs/reducers'; export default { - reducers, actions, defaultState: { shortcutOptions: { diff --git a/src/modules/ui/actions/ui.js b/src/modules/ui/actions/ui.js index 3f2effd..7d46389 100755 --- a/src/modules/ui/actions/ui.js +++ b/src/modules/ui/actions/ui.js @@ -1,26 +1,15 @@ import { types } from './'; export default { - setStoryFilter({ reduxStore, clientStore }, filter) { - reduxStore.dispatch({ - type: types.SET_STORY_FILTER, - filter, - }); + setStoryFilter({ clientStore }, filter) { clientStore.set('storyFilter', filter); }, - toggleShortcutsHelp({ reduxStore, clientStore }) { - reduxStore.dispatch({ - type: types.TOGGLE_SHORTCUTS_HELP, - }); + toggleShortcutsHelp({ clientStore }) { clientStore.toggle('showShortcutsHelp'); }, - selectDownPanel({ reduxStore, clientStore }, panelName) { - reduxStore.dispatch({ - type: types.SELECT_BOTTOM_PANEL, - panelName, - }); + selectDownPanel({ clientStore }, panelName) { clientStore.set('selectedDownPanel', panelName); }, }; diff --git a/src/modules/ui/configs/handle_routing.js b/src/modules/ui/configs/handle_routing.js index dc5b3dc..3886d18 100755 --- a/src/modules/ui/configs/handle_routing.js +++ b/src/modules/ui/configs/handle_routing.js @@ -3,25 +3,25 @@ export const config = { insidePopState: false, }; -export function changeUrl(reduxStore) { +export function changeUrl(clientStore) { // Do not change the URL if we are inside a popState event. if (config.insidePopState) return; - const { api, shortcuts, ui } = reduxStore.getState(); - if (!api) return; + const data = clientStore.getAll(); + if (!data.selectedKind) return; - const { selectedKind, selectedStory, customQueryParams } = api; + const { selectedKind, selectedStory, customQueryParams } = data; const { goFullScreen: full, showDownPanel: down, showLeftPanel: left, downPanelInRight: panelRight, - } = shortcuts; + } = data.shortcutOptions; const { selectedDownPanel: downPanel, - } = ui; + } = data; const urlObj = { ...customQueryParams, @@ -85,13 +85,13 @@ export function handleInitialUrl(actions, location) { updateStore(parsedQs, actions); } -export default function ({ reduxStore }, actions) { +export default function ({ clientStore }, actions) { // handle initial URL handleInitialUrl(actions, window.location); - // subscribe to reduxStore and change the URL - reduxStore.subscribe(() => changeUrl(reduxStore)); - changeUrl(reduxStore); + // subscribe to clientStore and change the URL + clientStore.subscribe(() => changeUrl(clientStore)); + changeUrl(clientStore); // handle back button window.onpopstate = () => { diff --git a/src/modules/ui/configs/reducers/__tests__/ui.js b/src/modules/ui/configs/reducers/__tests__/ui.js deleted file mode 100755 index 5a8d54e..0000000 --- a/src/modules/ui/configs/reducers/__tests__/ui.js +++ /dev/null @@ -1,36 +0,0 @@ -import reducer from '../ui'; -import { expect } from 'chai'; -import { types } from '../../../actions'; -const { describe, it } = global; - -describe('manager.ui.config.reducers.ui', () => { - describe('SET_STORY_FILTER', () => { - it('should set the given filter', () => { - const filter = 'wow'; - const oldState = { - storyFilter: 'xxxxx', - }; - - const action = { - type: types.SET_STORY_FILTER, - filter, - }; - const newState = reducer(oldState, action); - expect(newState.storyFilter).to.be.equal(filter); - }); - }); - - describe('TOGGLE_SHORTCUTS_HELP', () => { - it('should toggle the showShortcutsHelp value', () => { - const oldState = { - showShortcutsHelp: false, - }; - - const action = { - type: types.TOGGLE_SHORTCUTS_HELP, - }; - const newState = reducer(oldState, action); - expect(newState.showShortcutsHelp).to.be.equal(!oldState.showShortcutsHelp); - }); - }); -}); diff --git a/src/modules/ui/configs/reducers/index.js b/src/modules/ui/configs/reducers/index.js deleted file mode 100755 index f3bb723..0000000 --- a/src/modules/ui/configs/reducers/index.js +++ /dev/null @@ -1,5 +0,0 @@ -import ui from './ui'; - -export default { - ui, -}; diff --git a/src/modules/ui/configs/reducers/ui.js b/src/modules/ui/configs/reducers/ui.js deleted file mode 100755 index b5a027d..0000000 --- a/src/modules/ui/configs/reducers/ui.js +++ /dev/null @@ -1,33 +0,0 @@ -import { types } from '../../actions'; - -const defaultState = { - showShortcutsHelp: false, -}; - -export default function (state = defaultState, action) { - switch (action.type) { - case types.SET_STORY_FILTER: { - return { - ...state, - storyFilter: action.filter, - }; - } - - case types.SELECT_BOTTOM_PANEL: { - return { - ...state, - selectedDownPanel: action.panelName, - }; - } - - case types.TOGGLE_SHORTCUTS_HELP: { - return { - ...state, - showShortcutsHelp: !state.showShortcutsHelp, - }; - } - - default: - return state; - } -} diff --git a/src/modules/ui/index.js b/src/modules/ui/index.js index 63a14eb..321a4c5 100755 --- a/src/modules/ui/index.js +++ b/src/modules/ui/index.js @@ -1,6 +1,5 @@ import routes from './routes'; import actions from './actions'; -import reducers from './configs/reducers'; import initPanels from './configs/init_panels'; import handleRouting from './configs/handle_routing'; import handleKeyEvents from './configs/handle_keyevents'; @@ -8,7 +7,6 @@ import handleKeyEvents from './configs/handle_keyevents'; export default { routes, actions, - reducers, defaultState: { showShortcutsHelp: false, }, diff --git a/src/modules/ui/routes.js b/src/modules/ui/routes.js index a4b4f45..471256a 100755 --- a/src/modules/ui/routes.js +++ b/src/modules/ui/routes.js @@ -6,12 +6,12 @@ import DownPanel from './containers/down_panel'; import ShortcutsHelp from './containers/shortcuts_help'; import SearchBox from './containers/search_box'; -export default function (injectDeps, { reduxStore, provider, domNode }) { +export default function (injectDeps, { clientStore, provider, domNode }) { // generate preview const Preview = () => { - const { api } = reduxStore.getState(); + const state = clientStore.getAll(); const preview = - provider.renderPreview(api.selectedKind, api.selectedStory); + provider.renderPreview(state.selectedKind, state.selectedStory); return preview; }; From 7965b0bf96679c3b1c118ae2109bbc7e25e94da8 Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Mon, 14 Nov 2016 06:02:04 +0530 Subject: [PATCH 07/14] Cleanup the code base a bit. --- src/modules/api/actions/api.js | 4 ++-- src/modules/api/actions/index.js | 9 --------- src/modules/api/index.js | 2 +- src/modules/ui/actions/index.js | 6 ------ src/modules/ui/containers/left_panel.js | 6 +++--- 5 files changed, 6 insertions(+), 21 deletions(-) diff --git a/src/modules/api/actions/api.js b/src/modules/api/actions/api.js index 1d99ea1..23589a0 100755 --- a/src/modules/api/actions/api.js +++ b/src/modules/api/actions/api.js @@ -31,9 +31,9 @@ export default { setOptions({ reduxStore, clientStore }, options) { clientStore.update((state) => { - const newOptions = pick(options, Object.keys(state.options)); + const newOptions = pick(options, Object.keys(state.uiOptions)); const updatedOptions = { - ...state.options, + ...state.uiOptions, newOptions, }; diff --git a/src/modules/api/actions/index.js b/src/modules/api/actions/index.js index 876a546..7cb6cc4 100755 --- a/src/modules/api/actions/index.js +++ b/src/modules/api/actions/index.js @@ -1,12 +1,3 @@ -// define redux actions -export const types = { - SET_STORIES: 'API_SET_STORIES', - SELECT_STORY: 'API_SELECT_STORY', - JUMP_TO_STORY: 'API_JUMP_TO_STORY', - SET_OPTIONS: 'API_SET_OPTIONS', - SET_QUERY_PARAMS: 'API_SET_QUERY_PARAMS', -}; - import api from './api'; export default { diff --git a/src/modules/api/index.js b/src/modules/api/index.js index 8c2181a..06b9aea 100755 --- a/src/modules/api/index.js +++ b/src/modules/api/index.js @@ -4,7 +4,7 @@ import initApi from './configs/init_api'; export default { actions, defaultState: { - options: { + uiOptions: { name: 'REACT STORYBOOK', url: 'https://github.com/kadirahq/react-storybook', }, diff --git a/src/modules/ui/actions/index.js b/src/modules/ui/actions/index.js index db163ea..f3bb723 100755 --- a/src/modules/ui/actions/index.js +++ b/src/modules/ui/actions/index.js @@ -1,11 +1,5 @@ import ui from './ui'; -export const types = { - SET_STORY_FILTER: 'UI_SET_STORY_FILTER', - TOGGLE_SHORTCUTS_HELP: 'UI_TOGGLE_SHORTCUTS_HELP', - SELECT_BOTTOM_PANEL: 'UI_SELECT_BOTTOM_PANEL', -}; - export default { ui, }; diff --git a/src/modules/ui/containers/left_panel.js b/src/modules/ui/containers/left_panel.js index 151b2e7..6ad0dc1 100755 --- a/src/modules/ui/containers/left_panel.js +++ b/src/modules/ui/containers/left_panel.js @@ -5,7 +5,7 @@ import compose from '../../../compose'; export const mapper = (state, props, { actions }) => { const actionMap = actions(); - const { stories, selectedKind, selectedStory, options, storyFilter } = state; + const { stories, selectedKind, selectedStory, uiOptions, storyFilter } = state; const data = { stories: filters.storyFilter(stories, storyFilter, selectedKind, selectedStory), @@ -17,8 +17,8 @@ export const mapper = (state, props, { actions }) => { onStoryFilter: actionMap.ui.setStoryFilter, openShortcutsHelp: actionMap.ui.toggleShortcutsHelp, - name: options.name, - url: options.url, + name: uiOptions.name, + url: uiOptions.url, }; return data; From 05c1df12967c4d5cf0464b34fb320f0bb1612441 Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Tue, 15 Nov 2016 22:30:47 +0530 Subject: [PATCH 08/14] Fix lint issues. --- src/index.js | 1 - src/modules/api/actions/api.js | 94 +++++++++++----------- src/modules/shortcuts/actions/shortcuts.js | 38 ++++----- src/modules/shortcuts/index.js | 4 +- src/modules/ui/actions/ui.js | 2 - 5 files changed, 68 insertions(+), 71 deletions(-) diff --git a/src/index.js b/src/index.js index a9debf0..88e448d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,3 @@ -import { createStore, combineReducers } from 'redux'; import { createApp } from 'mantra-core'; import Podda from 'podda'; diff --git a/src/modules/api/actions/api.js b/src/modules/api/actions/api.js index 23589a0..6328bc1 100755 --- a/src/modules/api/actions/api.js +++ b/src/modules/api/actions/api.js @@ -1,5 +1,51 @@ import pick from 'lodash.pick'; +export function jumpToStory(storyKinds, selectedKind, selectedStory, direction) { + const flatteredStories = []; + let currentIndex = -1; + + storyKinds.forEach(({ kind, stories }) => { + stories.forEach((story) => { + flatteredStories.push({ kind, story }); + if (kind === selectedKind && story === selectedStory) { + currentIndex = flatteredStories.length - 1; + } + }); + }); + + const jumpedStory = flatteredStories[currentIndex + direction]; + if (!jumpedStory) { + return { selectedKind, selectedStory }; + } + + return { + selectedKind: jumpedStory.kind, + selectedStory: jumpedStory.story, + }; +} + +export function ensureKind(storyKinds, selectedKind) { + if (!storyKinds) return selectedKind; + + const found = storyKinds.find(item => item.kind === selectedKind); + if (found) return found.kind; + // if the selected kind is non-existant, select the first kind + const kinds = storyKinds.map(item => item.kind); + return kinds[0]; +} + +export function ensureStory(storyKinds, selectedKind, selectedStory) { + if (!storyKinds) return selectedStory; + + const kindInfo = storyKinds.find(item => item.kind === selectedKind); + if (!kindInfo) return null; + + const found = kindInfo.stories.find(item => item === selectedStory); + if (found) return found; + + return kindInfo.stories[0]; +} + export default { setStories({ reduxStore, clientStore }, stories) { clientStore.update((state) => { @@ -57,52 +103,6 @@ export default { return { customQueryParams: updatedQueryParams, }; - }) + }); }, }; - -export function jumpToStory(storyKinds, selectedKind, selectedStory, direction) { - const flatteredStories = []; - let currentIndex = -1; - - storyKinds.forEach(({ kind, stories }) => { - stories.forEach((story) => { - flatteredStories.push({ kind, story }); - if (kind === selectedKind && story === selectedStory) { - currentIndex = flatteredStories.length - 1; - } - }); - }); - - const jumpedStory = flatteredStories[currentIndex + direction]; - if (!jumpedStory) { - return { selectedKind, selectedStory }; - } - - return { - selectedKind: jumpedStory.kind, - selectedStory: jumpedStory.story, - }; -} - -export function ensureKind(storyKinds, selectedKind) { - if (!storyKinds) return selectedKind; - - const found = storyKinds.find(item => item.kind === selectedKind); - if (found) return found.kind; - // if the selected kind is non-existant, select the first kind - const kinds = storyKinds.map(item => item.kind); - return kinds[0]; -} - -export function ensureStory(storyKinds, selectedKind, selectedStory) { - if (!storyKinds) return selectedStory; - - const kindInfo = storyKinds.find(item => item.kind === selectedKind); - if (!kindInfo) return null; - - const found = kindInfo.stories.find(item => item === selectedStory); - if (found) return found; - - return kindInfo.stories[0]; -} diff --git a/src/modules/shortcuts/actions/shortcuts.js b/src/modules/shortcuts/actions/shortcuts.js index 61c0c00..eceb2f6 100755 --- a/src/modules/shortcuts/actions/shortcuts.js +++ b/src/modules/shortcuts/actions/shortcuts.js @@ -2,6 +2,23 @@ import pick from 'lodash.pick'; import { features } from '../../../libs/key_events'; import apiActions from '../../api/actions'; +export function keyEventToOptions(currentOptions, event) { + switch (event) { + case features.FULLSCREEN: + return { goFullScreen: !currentOptions.goFullScreen }; + case features.DOWN_PANEL: + return { showDownPanel: !currentOptions.showDownPanel }; + case features.LEFT_PANEL: + return { showLeftPanel: !currentOptions.showLeftPanel }; + case features.SEARCH: + return { showSearchBox: !currentOptions.showSearchBox }; + case features.DOWN_PANEL_IN_RIGHT: + return { downPanelInRight: !currentOptions.downPanelInRight }; + default: + return {}; + } +} + export default { handleEvent(context, event) { const { clientStore } = context; @@ -21,7 +38,7 @@ export default { }; return { - shortcutOptions: updatedOptions + shortcutOptions: updatedOptions, }; }); } @@ -31,7 +48,7 @@ export default { clientStore.update((state) => { const updatedOptions = { ...state.shortcutOptions, - ...pick(options, Object.keys(state.shortcutOptions)) + ...pick(options, Object.keys(state.shortcutOptions)), }; return { @@ -40,20 +57,3 @@ export default { }); }, }; - -export function keyEventToOptions(currentOptions, event) { - switch (event) { - case features.FULLSCREEN: - return { goFullScreen: !currentOptions.goFullScreen }; - case features.DOWN_PANEL: - return { showDownPanel: !currentOptions.showDownPanel }; - case features.LEFT_PANEL: - return { showLeftPanel: !currentOptions.showLeftPanel }; - case features.SEARCH: - return { showSearchBox: !currentOptions.showSearchBox }; - case features.DOWN_PANEL_IN_RIGHT: - return { downPanelInRight: !currentOptions.downPanelInRight }; - default: - return {}; - } -} diff --git a/src/modules/shortcuts/index.js b/src/modules/shortcuts/index.js index d7af9ad..b86918e 100755 --- a/src/modules/shortcuts/index.js +++ b/src/modules/shortcuts/index.js @@ -9,6 +9,6 @@ export default { showDownPanel: true, showSearchBox: false, downPanelInRight: false, - } - } + }, + }, }; diff --git a/src/modules/ui/actions/ui.js b/src/modules/ui/actions/ui.js index 7d46389..97e572b 100755 --- a/src/modules/ui/actions/ui.js +++ b/src/modules/ui/actions/ui.js @@ -1,5 +1,3 @@ -import { types } from './'; - export default { setStoryFilter({ clientStore }, filter) { clientStore.set('storyFilter', filter); From aa2553b13fec41740833978c3d6c0c1e770a76be Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Wed, 16 Nov 2016 05:59:12 +0530 Subject: [PATCH 09/14] Changed some tests a bit. --- .../ui/libs/__tests__/redux_composer.js | 102 ++---------------- src/modules/ui/libs/gen_redux_loader.js | 18 ---- 2 files changed, 9 insertions(+), 111 deletions(-) delete mode 100644 src/modules/ui/libs/gen_redux_loader.js diff --git a/src/modules/ui/libs/__tests__/redux_composer.js b/src/modules/ui/libs/__tests__/redux_composer.js index f428050..41b8df4 100755 --- a/src/modules/ui/libs/__tests__/redux_composer.js +++ b/src/modules/ui/libs/__tests__/redux_composer.js @@ -1,95 +1,11 @@ const { describe, it } = global; -import { expect } from 'chai'; -import { baseComposer } from '../redux_composer'; -import sinon from 'sinon'; - -describe('manager.ui.libs.redux_composer', function () { - describe('running', () => { - it('should run composer functions initially', (done) => { - const reduxStore = { - subscribe() {}, - getState() {}, - }; - - const context = () => ({ reduxStore }); - baseComposer(done, { context }, () => {}); - }); - - it('should run the composer functions for reduxStore subscribe', () => { - let processState; - const reduxStore = { - subscribe(ps) { - processState = ps; - }, - getState() {}, - }; - - const context = () => ({ reduxStore }); - const composerFn = sinon.stub(); - baseComposer(composerFn, { context }, () => {}); - processState(); - - expect(composerFn.callCount).to.be.equal(2); - }); - }); - - describe('composer function', () => { - it('should call the composer function with reduxState and props', () => { - const reduxState = { aa: 10 }; - const reduxStore = { - subscribe() {}, - getState: () => reduxState, - }; - - const context = () => ({ reduxStore }); - const props = { context }; - const composerFn = sinon.mock(); - baseComposer(composerFn, props, () => {}); - - expect(composerFn.firstCall.args[0]).to.deep.equal(reduxState); - expect(composerFn.firstCall.args[1]).to.be.equal(props); - }); - - it('should accept the returned data data', () => { - const reduxStore = { - subscribe() {}, - getState() {}, - }; - - const context = () => ({ reduxStore }); - const props = { context }; - const data = { aa: 20 }; - - const composerFn = () => { - return data; - }; - - const onData = sinon.mock(); - baseComposer(composerFn, props, onData); - - expect(onData.firstCall.args[0]).to.be.equal(null); - expect(onData.firstCall.args[1]).to.deep.equal(data); - }); - - it('should handle errors', () => { - const reduxStore = { - subscribe() {}, - getState() {}, - }; - - const context = () => ({ reduxStore }); - const props = { context }; - const error = new Error('Hello Error'); - - const composerFn = () => { - throw error; - }; - - const onData = sinon.mock(); - baseComposer(composerFn, props, onData); - - expect(onData.firstCall.args[0]).to.be.equal(error); - expect(onData.firstCall.args[1]).to.be.equal(undefined); - }); - }); +// import { expect } from 'chai'; +// import sinon from 'sinon'; + +describe('manager.ui.libs.gen_podda_loader', function () { + it('should map data properly'); + it('should handles errors thrown in the mapper'); + it('should pass props to the mapper'); + it('should pass env to the mapper'); + it('should unsubscribe to poddaStore when stopped'); }); diff --git a/src/modules/ui/libs/gen_redux_loader.js b/src/modules/ui/libs/gen_redux_loader.js deleted file mode 100644 index 3d33c51..0000000 --- a/src/modules/ui/libs/gen_redux_loader.js +++ /dev/null @@ -1,18 +0,0 @@ -export default function getReduxLoader(fn) { - return (props, onData, env) => { - const { reduxStore } = env.context(); - - const processState = () => { - try { - const state = reduxStore.getState(); - const data = fn(state, props, env); - onData(null, data); - } catch (ex) { - onData(ex); - } - }; - - processState(); - return reduxStore.subscribe(processState); - }; -} From 79caf69aae8a6f6d6291add9fd0aa589b22b3ab6 Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Wed, 16 Nov 2016 06:27:03 +0530 Subject: [PATCH 10/14] Fix tests for the ui module. --- src/modules/ui/actions/__tests__/ui.js | 42 +++++++++++-------- .../ui/configs/__tests__/handle_routing.js | 30 ++++++------- .../ui/containers/__tests__/down_panel.js | 4 +- src/modules/ui/containers/__tests__/layout.js | 4 +- .../ui/containers/__tests__/left_panel.js | 32 ++++++-------- .../ui/containers/__tests__/shortcuts_help.js | 4 +- 6 files changed, 54 insertions(+), 62 deletions(-) diff --git a/src/modules/ui/actions/__tests__/ui.js b/src/modules/ui/actions/__tests__/ui.js index 6f790ef..7af3d99 100755 --- a/src/modules/ui/actions/__tests__/ui.js +++ b/src/modules/ui/actions/__tests__/ui.js @@ -6,32 +6,40 @@ const { describe, it } = global; describe('manager.ui.actions.ui', () => { describe('setStoryFilter', () => { - it('should dispatch related redux action', () => { - const reduxStore = { - dispatch: sinon.stub(), + it('should set the given filter', () => { + const clientStore = { + set: sinon.stub(), }; const filter = 'kkkind'; - actions.setStoryFilter({ reduxStore }, filter); - const action = reduxStore.dispatch.args[0][0]; - expect(action).to.deep.equal({ - type: types.SET_STORY_FILTER, - filter, - }); + actions.setStoryFilter({ clientStore }, filter); + const args = clientStore.set.args[0]; + expect(args).to.deep.equal(['storyFilter', filter]); }); }); describe('toggleShortcutsHelp', () => { - it('should dispatch related redux action', () => { - const reduxStore = { - dispatch: sinon.stub(), + it('should toggle the client sotre accordingly', () => { + const clientStore = { + toggle: sinon.stub(), }; - actions.toggleShortcutsHelp({ reduxStore }); - const action = reduxStore.dispatch.args[0][0]; - expect(action).to.deep.equal({ - type: types.TOGGLE_SHORTCUTS_HELP, - }); + actions.toggleShortcutsHelp({ clientStore }); + const args = clientStore.toggle.args[0]; + expect(args).to.deep.equal(['showShortcutsHelp']); + }); + }); + + describe('selectDownPanel', () => { + it('should set the given panel name', () => { + const clientStore = { + set: sinon.stub(), + }; + const panelName = 'kkkind'; + + actions.selectDownPanel({ clientStore }, panelName); + const args = clientStore.set.args[0]; + expect(args).to.deep.equal(['selectedDownPanel', panelName]); }); }); }); diff --git a/src/modules/ui/configs/__tests__/handle_routing.js b/src/modules/ui/configs/__tests__/handle_routing.js index 65ed2a9..990af6c 100755 --- a/src/modules/ui/configs/__tests__/handle_routing.js +++ b/src/modules/ui/configs/__tests__/handle_routing.js @@ -13,27 +13,23 @@ describe('manager.ui.config.handle_routing', () => { }); it('should put the correct URL and state to pushState', (done) => { - const reduxState = { - api: { - selectedKind: 'kk', - selectedStory: 'ss', - customQueryParams: { - customText: 'test', - }, + const state = { + selectedKind: 'kk', + selectedStory: 'ss', + customQueryParams: { + customText: 'test', }, - shortcuts: { + shortcutOptions: { goFullScreen: false, showDownPanel: true, showLeftPanel: true, downPanelInRight: true, }, - ui: { - selectedDownPanel: 'pp', - }, + selectedDownPanel: 'pp', }; - const reduxStore = { - getState: () => reduxState, + const clientStore = { + getAll: () => state, }; // eslint-disable-next-line max-len @@ -57,7 +53,7 @@ describe('manager.ui.config.handle_routing', () => { expect(u).to.be.equal(pushState.url); done(); }; - changeUrl(reduxStore); + changeUrl(clientStore); window.history.pushState = originalPushState; }); }); @@ -70,7 +66,7 @@ describe('manager.ui.config.handle_routing', () => { setQueryParams: sinon.mock(), }, shortcuts: { - setLayout: sinon.mock(), + setOptions: sinon.mock(), }, ui: { selectDownPanel: sinon.mock(), @@ -87,10 +83,10 @@ describe('manager.ui.config.handle_routing', () => { handleInitialUrl(actions, location); expect(actions.api.selectStory.callCount).to.be.equal(1); - expect(actions.shortcuts.setLayout.callCount).to.be.equal(1); + expect(actions.shortcuts.setOptions.callCount).to.be.equal(1); expect(actions.ui.selectDownPanel.callCount).to.be.equal(1); /* eslint-disable no-unused-expressions */ - expect(actions.shortcuts.setLayout.calledWith({ + expect(actions.shortcuts.setOptions.calledWith({ goFullScreen: true, showDownPanel: false, showLeftPanel: false, diff --git a/src/modules/ui/containers/__tests__/down_panel.js b/src/modules/ui/containers/__tests__/down_panel.js index d60cb6f..1a4748c 100644 --- a/src/modules/ui/containers/__tests__/down_panel.js +++ b/src/modules/ui/containers/__tests__/down_panel.js @@ -6,9 +6,7 @@ describe('manager.ui.containers.down_panel', () => { describe('mapper', () => { it('should give correct data', () => { const state = { - ui: { - selectedDownPanel: 'sdp', - }, + selectedDownPanel: 'sdp', }; const selectDownPanel = () => 'selectDownPanel'; diff --git a/src/modules/ui/containers/__tests__/layout.js b/src/modules/ui/containers/__tests__/layout.js index b08cf81..8de9dfe 100755 --- a/src/modules/ui/containers/__tests__/layout.js +++ b/src/modules/ui/containers/__tests__/layout.js @@ -6,7 +6,7 @@ describe('manager.ui.containers.layout', () => { describe('mapper', () => { it('should give correct data', () => { const state = { - shortcuts: { + shortcutOptions: { showLeftPanel: 'aa', showDownPanel: 'bb', goFullScreen: 'cc', @@ -14,7 +14,7 @@ describe('manager.ui.containers.layout', () => { }; const data = mapper(state); - expect(data).to.deep.equal(state.shortcuts); + expect(data).to.deep.equal(state.shortcutOptions); }); }); }); diff --git a/src/modules/ui/containers/__tests__/left_panel.js b/src/modules/ui/containers/__tests__/left_panel.js index e56ddfd..ae5a977 100755 --- a/src/modules/ui/containers/__tests__/left_panel.js +++ b/src/modules/ui/containers/__tests__/left_panel.js @@ -8,7 +8,7 @@ describe('manager.ui.containers.left_panel', () => { const stories = [{ kind: 'sk', stories: ['dd'] }]; const selectedKind = 'sk'; const selectedStory = 'dd'; - const options = { + const uiOptions = { name: 'foo', url: 'bar', }; @@ -31,15 +31,11 @@ describe('manager.ui.containers.left_panel', () => { }; const state = { - ui: { - storyFilter: null, - }, - api: { - stories, - selectedKind, - selectedStory, - options, - }, + storyFilter: null, + stories, + selectedKind, + selectedStory, + uiOptions, }; const data = mapper(state, props, env); @@ -61,7 +57,7 @@ describe('manager.ui.containers.left_panel', () => { ]; const selectedKind = 'pk'; const selectedStory = 'dd'; - const options = { + const uiOptions = { name: 'foo', url: 'bar', }; @@ -84,15 +80,11 @@ describe('manager.ui.containers.left_panel', () => { }; const state = { - ui: { - storyFilter: 'ss', - }, - api: { - stories, - selectedKind, - selectedStory, - options, - }, + storyFilter: 'ss', + stories, + selectedKind, + selectedStory, + uiOptions, }; const data = mapper(state, props, env); diff --git a/src/modules/ui/containers/__tests__/shortcuts_help.js b/src/modules/ui/containers/__tests__/shortcuts_help.js index 30dff41..72cacef 100755 --- a/src/modules/ui/containers/__tests__/shortcuts_help.js +++ b/src/modules/ui/containers/__tests__/shortcuts_help.js @@ -18,9 +18,7 @@ describe('manager.ui.containers.shortcuts_help', () => { }; const state = { - ui: { - showShortcutsHelp, - }, + showShortcutsHelp, }; const data = mapper(state, props, env); From bb79782e9a0101a3f877eb5fb5bd0c2121e09ec8 Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Wed, 16 Nov 2016 06:47:28 +0530 Subject: [PATCH 11/14] Update test cases for API. --- src/modules/api/actions/__tests__/api.js | 91 ++++++------------- src/modules/api/configs/__tests__/init_api.js | 48 +++++----- src/modules/ui/actions/__tests__/ui.js | 1 - 3 files changed, 51 insertions(+), 89 deletions(-) diff --git a/src/modules/api/actions/__tests__/api.js b/src/modules/api/actions/__tests__/api.js index 645c264..3f34fa2 100755 --- a/src/modules/api/actions/__tests__/api.js +++ b/src/modules/api/actions/__tests__/api.js @@ -6,86 +6,51 @@ const { describe, it } = global; describe('manager.api.actions.api', () => { describe('setStories', () => { - it('should dispatch related redux action', () => { - const reduxStore = { - dispatch: sinon.stub(), - }; - const stories = [{ kind: 'aa', stories: [] }]; + describe('no selected story', () => { + it('should set stories and select the first story'); + }); + + describe('has a selected story', () => { + it('should set stories and select the existing story'); + }); - actions.setStories({ reduxStore }, stories); - const action = reduxStore.dispatch.args[0][0]; - expect(action).to.deep.equal({ - type: types.SET_STORIES, - stories, - }); + describe('has a selected story, but it\'s story isn\'t in new stories', () => { + it('should set stories and select the first story of the selected kind'); + }); + + describe('has a selected story, but it\'s kind isn\'t in new stories', () => { + it('should set stories and select the first story'); }); }); describe('selectStory', () => { - it('should dispatch related redux action', () => { - const reduxStore = { - dispatch: sinon.stub(), - }; - const kind = 'kkkind'; - const story = 'ssstory'; + describe('with both kind and story', () => { + it('should select the correct story'); + }); - actions.selectStory({ reduxStore }, kind, story); - const action = reduxStore.dispatch.args[0][0]; - expect(action).to.deep.equal({ - type: types.SELECT_STORY, - kind, - story, - }); + describe('with just the kind', () => { + it('should select the first of the kind'); }); }); describe('jumpToStory', () => { - it('should dispatch related redux action', () => { - const reduxStore = { - dispatch: sinon.stub(), - }; - const direction = -1; + describe('has enough stories', () => { + it('should select the next story'); + it('should select the prev story'); + }); - actions.jumpToStory({ reduxStore }, direction); - const action = reduxStore.dispatch.args[0][0]; - expect(action).to.deep.equal({ - type: types.JUMP_TO_STORY, - direction, - }); + describe('has not enough stories', () => { + it('should select the current story'); }); }); describe('setOptions', () => { - it('should dispatch related redux action', () => { - const reduxStore = { - dispatch: sinon.stub(), - }; - const options = {}; - - actions.setOptions({ reduxStore }, options); - const a = reduxStore.dispatch.args[0][0]; - expect(a).to.deep.equal({ - type: types.SET_OPTIONS, - options, - }); - }); + it('should update options'); + it('should only update options for the key already defined'); }); describe('setQueryParams', () => { - it('should dispatch related redux action', () => { - const reduxStore = { - dispatch: sinon.stub(), - }; - const customQueryParams = { - foo: 'bar', - }; - - actions.setQueryParams({ reduxStore }, customQueryParams); - const a = reduxStore.dispatch.args[0][0]; - expect(a).to.deep.equal({ - type: types.SET_QUERY_PARAMS, - customQueryParams, - }); - }); + it('shodul update query params'); + it('should delete the param if it\'s null'); }); }); diff --git a/src/modules/api/configs/__tests__/init_api.js b/src/modules/api/configs/__tests__/init_api.js index 607fe07..6b77cf6 100644 --- a/src/modules/api/configs/__tests__/init_api.js +++ b/src/modules/api/configs/__tests__/init_api.js @@ -16,7 +16,7 @@ describe('manager.api.config.initApi', () => { }, }; - const reduxStore = { + const clientStore = { subscribe: sinon.stub(), }; @@ -31,7 +31,7 @@ describe('manager.api.config.initApi', () => { }, }; - initApi(provider, reduxStore, actions); + initApi(provider, clientStore, actions); }); it('should trigger the onStory callback', (done) => { @@ -39,10 +39,10 @@ describe('manager.api.config.initApi', () => { const selectedKind = 'XXdd'; const selectedStory = 'u8sd'; - const reduxStore = { + const clientStore = { subscribe: sinon.stub(), - getState: () => ({ - api: { selectedKind, selectedStory }, + getAll: () => ({ + selectedKind, selectedStory, }), }; @@ -56,9 +56,9 @@ describe('manager.api.config.initApi', () => { }, }; - initApi(provider, reduxStore, actions); + initApi(provider, clientStore, actions); // calling the subscription - reduxStore.subscribe.args[0][0](); + clientStore.subscribe.args[0][0](); }); it('should support to add multiple onStory callback', (done) => { @@ -66,10 +66,10 @@ describe('manager.api.config.initApi', () => { const selectedKind = 'XXdd'; const selectedStory = 'u8sd'; - const reduxStore = { + const clientStore = { subscribe: sinon.stub(), - getState: () => ({ - api: { selectedKind, selectedStory }, + getAll: () => ({ + selectedKind, selectedStory, }), }; @@ -88,9 +88,9 @@ describe('manager.api.config.initApi', () => { }, }; - initApi(provider, reduxStore, actions); + initApi(provider, clientStore, actions); // calling the subscription - reduxStore.subscribe.args[0][0](); + clientStore.subscribe.args[0][0](); }); it('should support a way to remove onStory callback', (done) => { @@ -98,10 +98,10 @@ describe('manager.api.config.initApi', () => { const selectedKind = 'XXdd'; const selectedStory = 'u8sd'; - const reduxStore = { + const clientStore = { subscribe: sinon.stub(), - getState: () => ({ - api: { selectedKind, selectedStory }, + getAll: () => ({ + selectedKind, selectedStory, }), }; @@ -121,23 +121,21 @@ describe('manager.api.config.initApi', () => { }, }; - initApi(provider, reduxStore, actions); + initApi(provider, clientStore, actions); // calling the subscription - reduxStore.subscribe.args[0][0](); + clientStore.subscribe.args[0][0](); }); describe('getQueryParam', () => { it('should return the correct query param value', (done) => { const actions = { api: {}, shortcuts: {} }; - const reduxStore = { + const clientStore = { subscribe: sinon.stub(), - getState: () => ({ - api: { - customQueryParams: { - foo: 'foo value', - bar: 'bar value', - }, + getAll: () => ({ + customQueryParams: { + foo: 'foo value', + bar: 'bar value', }, }), }; @@ -150,7 +148,7 @@ describe('manager.api.config.initApi', () => { }, }; - initApi(provider, reduxStore, actions); + initApi(provider, clientStore, actions); }); }); }); diff --git a/src/modules/ui/actions/__tests__/ui.js b/src/modules/ui/actions/__tests__/ui.js index 7af3d99..a215359 100755 --- a/src/modules/ui/actions/__tests__/ui.js +++ b/src/modules/ui/actions/__tests__/ui.js @@ -1,7 +1,6 @@ import actions from '../ui'; import { expect } from 'chai'; import sinon from 'sinon'; -import { types } from '../'; const { describe, it } = global; describe('manager.ui.actions.ui', () => { From 8b138f7b3d2db734c0e332f4242c27b615d8f973 Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Wed, 16 Nov 2016 08:02:01 +0530 Subject: [PATCH 12/14] Fix tests for the api module. --- src/modules/api/actions/__tests__/api.js | 212 +++++++++++++++++++++-- src/modules/api/actions/api.js | 20 ++- 2 files changed, 208 insertions(+), 24 deletions(-) diff --git a/src/modules/api/actions/__tests__/api.js b/src/modules/api/actions/__tests__/api.js index 3f34fa2..7dec7e3 100755 --- a/src/modules/api/actions/__tests__/api.js +++ b/src/modules/api/actions/__tests__/api.js @@ -1,56 +1,238 @@ import actions from '../api'; import { expect } from 'chai'; -import sinon from 'sinon'; -import { types } from '../'; const { describe, it } = global; +class MockClientStore { + update(cb) { + this.updateCallback = cb; + } +} + +const stories = [ + { kind: 'abc', stories: ['a', 'b', 'c'] }, + { kind: 'bbc', stories: ['x', 'y', 'z'] }, +]; + describe('manager.api.actions.api', () => { describe('setStories', () => { describe('no selected story', () => { - it('should set stories and select the first story'); + it('should set stories and select the first story', () => { + const clientStore = new MockClientStore(); + actions.setStories({ clientStore }, stories); + + const newState = clientStore.updateCallback({}); + expect(newState).to.deep.equal({ + stories, + selectedKind: 'abc', + selectedStory: 'a', + }); + }); }); describe('has a selected story', () => { - it('should set stories and select the existing story'); + it('should set stories and select the existing story', () => { + const clientStore = new MockClientStore(); + actions.setStories({ clientStore }, stories); + + const state = { + selectedKind: 'abc', + selectedStory: 'c', + }; + const newState = clientStore.updateCallback(state); + expect(newState).to.deep.equal({ + stories, + selectedKind: 'abc', + selectedStory: 'c', + }); + }); }); describe('has a selected story, but it\'s story isn\'t in new stories', () => { - it('should set stories and select the first story of the selected kind'); + it('should set stories and select the first story of the selected kind', () => { + const clientStore = new MockClientStore(); + actions.setStories({ clientStore }, stories); + + const state = { + selectedKind: 'bbc', + selectedStory: 'k', + }; + const newState = clientStore.updateCallback(state); + expect(newState).to.deep.equal({ + stories, + selectedKind: 'bbc', + selectedStory: 'x', + }); + }); }); describe('has a selected story, but it\'s kind isn\'t in new stories', () => { - it('should set stories and select the first story'); + it('should set stories and select the first story', () => { + const clientStore = new MockClientStore(); + actions.setStories({ clientStore }, stories); + + const state = { + selectedKind: 'kky', + selectedStory: 'c', + }; + const newState = clientStore.updateCallback(state); + expect(newState).to.deep.equal({ + stories, + selectedKind: 'abc', + selectedStory: 'a', + }); + }); }); }); describe('selectStory', () => { describe('with both kind and story', () => { - it('should select the correct story'); + it('should select the correct story', () => { + const clientStore = new MockClientStore(); + actions.selectStory({ clientStore }, 'bbc', 'y'); + + const state = { + stories, + selectedKind: 'abc', + selectedStory: 'c', + }; + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + selectedKind: 'bbc', + selectedStory: 'y', + }); + }); }); describe('with just the kind', () => { - it('should select the first of the kind'); + it('should select the first of the kind', () => { + const clientStore = new MockClientStore(); + actions.selectStory({ clientStore }, 'bbc'); + + const state = { + stories, + selectedKind: 'abc', + selectedStory: 'c', + }; + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + selectedKind: 'bbc', + selectedStory: 'x', + }); + }); }); }); describe('jumpToStory', () => { describe('has enough stories', () => { - it('should select the next story'); - it('should select the prev story'); + it('should select the next story', () => { + const clientStore = new MockClientStore(); + actions.jumpToStory({ clientStore }, 1); + + const state = { + stories, + selectedKind: 'abc', + selectedStory: 'c', + }; + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + selectedKind: 'bbc', + selectedStory: 'x', + }); + }); + + it('should select the prev story', () => { + const clientStore = new MockClientStore(); + actions.jumpToStory({ clientStore }, -1); + + const state = { + stories, + selectedKind: 'abc', + selectedStory: 'c', + }; + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + selectedKind: 'abc', + selectedStory: 'b', + }); + }); }); describe('has not enough stories', () => { - it('should select the current story'); + it('should select the current story', () => { + const clientStore = new MockClientStore(); + actions.jumpToStory({ clientStore }, 1); + + const state = { + stories, + selectedKind: 'bbc', + selectedStory: 'z', + }; + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + selectedKind: 'bbc', + selectedStory: 'z', + }); + }); }); }); describe('setOptions', () => { - it('should update options'); - it('should only update options for the key already defined'); + it('should update options', () => { + const clientStore = new MockClientStore(); + actions.setOptions({ clientStore }, { abc: 10 }); + + const state = { + uiOptions: { bbc: 50, abc: 40 }, + }; + + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + uiOptions: { bbc: 50, abc: 10 }, + }); + }); + + it('should only update options for the key already defined', () => { + const clientStore = new MockClientStore(); + actions.setOptions({ clientStore }, { abc: 10, notGoingToState: 20 }); + + const state = { + uiOptions: { bbc: 50, abc: 40 }, + }; + + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + uiOptions: { bbc: 50, abc: 10 }, + }); + }); }); describe('setQueryParams', () => { - it('shodul update query params'); - it('should delete the param if it\'s null'); + it('shodul update query params', () => { + const clientStore = new MockClientStore(); + actions.setQueryParams({ clientStore }, { abc: 'aaa', cnn: 'ccc' }); + + const state = { + customQueryParams: { bbc: 'bbb', abc: 'sshd' }, + }; + + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + customQueryParams: { bbc: 'bbb', abc: 'aaa', cnn: 'ccc' }, + }); + }); + + it('should delete the param if it\'s null', () => { + const clientStore = new MockClientStore(); + actions.setQueryParams({ clientStore }, { abc: null, bbc: 'ccc' }); + + const state = { + customQueryParams: { bbc: 'bbb', abc: 'sshd' }, + }; + + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + customQueryParams: { bbc: 'ccc' }, + }); + }); }); }); diff --git a/src/modules/api/actions/api.js b/src/modules/api/actions/api.js index 6328bc1..dc69002 100755 --- a/src/modules/api/actions/api.js +++ b/src/modules/api/actions/api.js @@ -47,10 +47,12 @@ export function ensureStory(storyKinds, selectedKind, selectedStory) { } export default { - setStories({ reduxStore, clientStore }, stories) { + setStories({ clientStore }, stories) { clientStore.update((state) => { const selectedKind = ensureKind(stories, state.selectedKind); - const selectedStory = ensureStory(stories, selectedKind, state.selectedStory); + const currentSelectedStory = + (selectedKind === state.selectedKind) ? state.selectedStory : null; + const selectedStory = ensureStory(stories, selectedKind, currentSelectedStory); return { stories, @@ -60,7 +62,7 @@ export default { }); }, - selectStory({ reduxStore, clientStore }, kind, story) { + selectStory({ clientStore }, kind, story) { clientStore.update((state) => { const selectedKind = ensureKind(state.stories, kind); const selectedStory = ensureStory(state.stories, selectedKind, story); @@ -69,29 +71,29 @@ export default { }); }, - jumpToStory({ reduxStore, clientStore }, direction) { + jumpToStory({ clientStore }, direction) { clientStore.update((state) => { return jumpToStory(state.stories, state.selectedKind, state.selectedStory, direction); }); }, - setOptions({ reduxStore, clientStore }, options) { + setOptions({ clientStore }, options) { clientStore.update((state) => { const newOptions = pick(options, Object.keys(state.uiOptions)); const updatedOptions = { ...state.uiOptions, - newOptions, + ...newOptions, }; - return { options: updatedOptions }; + return { uiOptions: updatedOptions }; }); }, - setQueryParams({ reduxStore, clientStore }, customQueryParams) { + setQueryParams({ clientStore }, customQueryParams) { clientStore.update((state) => { const updatedQueryParams = { ...state.customQueryParams, - customQueryParams, + ...customQueryParams, }; Object.keys(customQueryParams).forEach(key => { From abcf09cb746e25c250dfb249f9a796befb281fcd Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Wed, 16 Nov 2016 16:17:35 +0530 Subject: [PATCH 13/14] Add tests for shortcuts module. --- .../shortcuts/actions/__tests__/shortcuts.js | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/modules/shortcuts/actions/__tests__/shortcuts.js diff --git a/src/modules/shortcuts/actions/__tests__/shortcuts.js b/src/modules/shortcuts/actions/__tests__/shortcuts.js new file mode 100644 index 0000000..d2f567c --- /dev/null +++ b/src/modules/shortcuts/actions/__tests__/shortcuts.js @@ -0,0 +1,41 @@ +import actions from '../shortcuts'; +import { expect } from 'chai'; +const { describe, it } = global; + +class MockClientStore { + update(cb) { + this.updateCallback = cb; + } +} + +describe('manager.shortcuts.actions.shortcuts', () => { + describe('setOptions', () => { + it('should update options', () => { + const clientStore = new MockClientStore(); + actions.setOptions({ clientStore }, { abc: 10 }); + + const state = { + shortcutOptions: { bbc: 50, abc: 40 }, + }; + + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + shortcutOptions: { bbc: 50, abc: 10 }, + }); + }); + + it('should only update options for the key already defined', () => { + const clientStore = new MockClientStore(); + actions.setOptions({ clientStore }, { abc: 10, kki: 50 }); + + const state = { + shortcutOptions: { bbc: 50, abc: 40 }, + }; + + const stateUpdates = clientStore.updateCallback(state); + expect(stateUpdates).to.deep.equal({ + shortcutOptions: { bbc: 50, abc: 10 }, + }); + }); + }); +}); From 3b3c8c74bed2d25d977a72dfe8a2a17232e908bd Mon Sep 17 00:00:00 2001 From: Arunoda Susiripala Date: Wed, 16 Nov 2016 16:56:55 +0530 Subject: [PATCH 14/14] Add tests for the podda loader. --- .../ui/libs/__tests__/gen_podda_loader.js | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 src/modules/ui/libs/__tests__/gen_podda_loader.js diff --git a/src/modules/ui/libs/__tests__/gen_podda_loader.js b/src/modules/ui/libs/__tests__/gen_podda_loader.js new file mode 100644 index 0000000..8b2eb4b --- /dev/null +++ b/src/modules/ui/libs/__tests__/gen_podda_loader.js @@ -0,0 +1,134 @@ +import genPoddaLoader from '../gen_podda_loader'; +import Podda from 'podda'; +import { expect } from 'chai'; +import sinon from 'sinon'; +const { describe, it } = global; + +describe('manager.ui.libs.gen_podda_loader', () => { + describe('mapper', () => { + it('should map the podda store state', () => { + const aa = 10; + const bb = 20; + const cc = 40; + const mapper = (state) => { + return { + aa: state.aa, + bb: state.bb, + }; + }; + + const clientStore = new Podda({ aa, bb, cc }); + const loader = genPoddaLoader(mapper); + + const onData = sinon.stub(); + loader({}, onData, { context: () => ({ clientStore }) }); + expect(onData.args[0]).to.deep.equal([ + null, + { aa, bb }, + ]); + }); + + it('should get props', () => { + const aa = 10; + const bb = 20; + const cc = 40; + const mapper = (state, props) => { + return { + aa: props.aa, + bb: props.bb, + }; + }; + + const clientStore = new Podda(); + const loader = genPoddaLoader(mapper); + + const onData = sinon.stub(); + loader({ aa, bb, cc }, onData, { context: () => ({ clientStore }) }); + expect(onData.args[0]).to.deep.equal([ + null, + { aa, bb }, + ]); + }); + + it('should get env', () => { + const aa = 10; + const bb = 20; + const cc = 40; + const mapper = (state, props, env) => { + return { + aa: env.aa, + bb: env.bb, + }; + }; + + const clientStore = new Podda(); + const loader = genPoddaLoader(mapper); + + const onData = sinon.stub(); + loader({}, onData, { context: () => ({ clientStore }), aa, bb, cc }); + expect(onData.args[0]).to.deep.equal([ + null, + { aa, bb }, + ]); + }); + }); + + describe('core', () => { + it('should handle errors in the mapper', () => { + const mapper = () => { + throw new Error('this is the error'); + }; + + const clientStore = new Podda(); + const loader = genPoddaLoader(mapper); + + const onData = sinon.stub(); + loader({}, onData, { context: () => ({ clientStore }) }); + expect(onData.args[0][0]).to.match(/this is the error/); + }); + + it('should run when the podda store changed', () => { + const aa = 10; + const bb = 20; + const cc = 40; + const mapper = (state) => { + return { + aa: state.aa, + bb: state.bb, + }; + }; + + const clientStore = new Podda({ aa, bb, cc }); + const loader = genPoddaLoader(mapper); + + const onData = sinon.stub(); + loader({ aa, bb, cc }, onData, { context: () => ({ clientStore }) }); + clientStore.set('aa', 1000); + expect(onData.args[1]).to.deep.equal([ + null, + { aa: 1000, bb }, + ]); + }); + + it('should not run when podda subscription stopped', () => { + const aa = 10; + const bb = 20; + const cc = 40; + const mapper = (state) => { + return { + aa: state.aa, + bb: state.bb, + }; + }; + + const clientStore = new Podda({ aa, bb, cc }); + const loader = genPoddaLoader(mapper); + + const onData = sinon.stub(); + const stop = loader({ aa, bb, cc }, onData, { context: () => ({ clientStore }) }); + stop(); + clientStore.set('aa', 1000); + expect(onData.callCount).to.be.equal(1); + }); + }); +});