From 541449b1fced9eea15f415b023206b10724f5315 Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Oct 2017 14:18:12 +0900 Subject: separate console --- src/console/actions/console.js | 44 +++++++++++ src/console/actions/index.js | 9 +++ src/console/components/completion.js | 61 +++++++++++++++ src/console/components/console.js | 148 +++++++++++++++++++++++++++++++++++ src/console/index.html | 20 +++++ src/console/index.js | 34 ++++++++ src/console/reducers/index.js | 94 ++++++++++++++++++++++ src/console/site.scss | 92 ++++++++++++++++++++++ 8 files changed, 502 insertions(+) create mode 100644 src/console/actions/console.js create mode 100644 src/console/actions/index.js create mode 100644 src/console/components/completion.js create mode 100644 src/console/components/console.js create mode 100644 src/console/index.html create mode 100644 src/console/index.js create mode 100644 src/console/reducers/index.js create mode 100644 src/console/site.scss (limited to 'src/console') diff --git a/src/console/actions/console.js b/src/console/actions/console.js new file mode 100644 index 0000000..01d9a9b --- /dev/null +++ b/src/console/actions/console.js @@ -0,0 +1,44 @@ +import actions from 'console/actions'; + +const showCommand = (text) => { + return { + type: actions.CONSOLE_SHOW_COMMAND, + text: text + }; +}; + +const showError = (text) => { + return { + type: actions.CONSOLE_SHOW_ERROR, + text: text + }; +}; + +const hide = () => { + return { + type: actions.CONSOLE_HIDE + }; +}; + +const setCompletions = (completions) => { + return { + type: actions.CONSOLE_SET_COMPLETIONS, + completions: completions + }; +}; + +const completionNext = () => { + return { + type: actions.CONSOLE_COMPLETION_NEXT, + }; +}; + +const completionPrev = () => { + return { + type: actions.CONSOLE_COMPLETION_PREV, + }; +}; + +export { + showCommand, showError, hide, setCompletions, completionNext, completionPrev +}; diff --git a/src/console/actions/index.js b/src/console/actions/index.js new file mode 100644 index 0000000..a5d03bc --- /dev/null +++ b/src/console/actions/index.js @@ -0,0 +1,9 @@ +export default { + // console commands + CONSOLE_SHOW_COMMAND: 'console.show.command', + CONSOLE_SET_COMPLETIONS: 'console.set.completions', + CONSOLE_SHOW_ERROR: 'console.show.error', + CONSOLE_HIDE: 'console.hide', + CONSOLE_COMPLETION_NEXT: 'console.completion.next', + CONSOLE_COMPLETION_PREV: 'console.completion.prev', +}; diff --git a/src/console/components/completion.js b/src/console/components/completion.js new file mode 100644 index 0000000..5033b5c --- /dev/null +++ b/src/console/components/completion.js @@ -0,0 +1,61 @@ +export default class Completion { + constructor(wrapper, store) { + this.wrapper = wrapper; + this.store = store; + this.prevState = {}; + } + + update() { + let state = this.store.getState(); + if (JSON.stringify(this.prevState) === JSON.stringify(state)) { + return; + } + + this.wrapper.innerHTML = ''; + + for (let i = 0; i < state.completions.length; ++i) { + let group = state.completions[i]; + let title = this.createCompletionTitle(group.name); + this.wrapper.append(title); + + for (let j = 0; j < group.items.length; ++j) { + let item = group.items[j]; + let li = this.createCompletionItem(item.icon, item.caption, item.url); + this.wrapper.append(li); + + if (i === state.groupSelection && j === state.itemSelection) { + li.classList.add('vimvixen-completion-selected'); + } + } + } + + this.prevState = state; + } + + createCompletionTitle(text) { + let doc = this.wrapper.ownerDocument; + let li = doc.createElement('li'); + li.className = 'vimvixen-console-completion-title'; + li.textContent = text; + return li; + } + + createCompletionItem(icon, caption, url) { + let doc = this.wrapper.ownerDocument; + + let captionEle = doc.createElement('span'); + captionEle.className = 'vimvixen-console-completion-item-caption'; + captionEle.textContent = caption; + + let urlEle = doc.createElement('span'); + urlEle.className = 'vimvixen-console-completion-item-url'; + urlEle.textContent = url; + + let li = doc.createElement('li'); + li.style.backgroundImage = 'url(' + icon + ')'; + li.className = 'vimvixen-console-completion-item'; + li.append(captionEle); + li.append(urlEle); + return li; + } +} diff --git a/src/console/components/console.js b/src/console/components/console.js new file mode 100644 index 0000000..9023d91 --- /dev/null +++ b/src/console/components/console.js @@ -0,0 +1,148 @@ +import messages from 'shared/messages'; +import * as consoleActions from 'console/actions/console'; + +export default class ConsoleComponent { + constructor(wrapper, store) { + this.wrapper = wrapper; + this.prevValue = ''; + this.prevState = {}; + this.completionOrigin = ''; + this.store = store; + + let doc = this.wrapper.ownerDocument; + let input = doc.querySelector('#vimvixen-console-command-input'); + input.addEventListener('blur', this.onBlur.bind(this)); + input.addEventListener('keydown', this.onKeyDown.bind(this)); + input.addEventListener('keyup', this.onKeyUp.bind(this)); + + this.hideCommand(); + this.hideError(); + } + + onBlur() { + return browser.runtime.sendMessage({ + type: messages.CONSOLE_BLURRED, + }); + } + + onKeyDown(e) { + let doc = this.wrapper.ownerDocument; + let input = doc.querySelector('#vimvixen-console-command-input'); + + switch (e.keyCode) { + case KeyboardEvent.DOM_VK_ESCAPE: + return input.blur(); + case KeyboardEvent.DOM_VK_RETURN: + return browser.runtime.sendMessage({ + type: messages.CONSOLE_ENTERED, + text: e.target.value + }).then(this.onBlur); + case KeyboardEvent.DOM_VK_TAB: + if (e.shiftKey) { + this.store.dispatch(consoleActions.completionPrev()); + } else { + this.store.dispatch(consoleActions.completionNext()); + } + e.stopPropagation(); + e.preventDefault(); + break; + } + } + + onKeyUp(e) { + if (e.keyCode === KeyboardEvent.DOM_VK_TAB) { + return; + } + if (e.target.value === this.prevValue) { + return; + } + + let doc = this.wrapper.ownerDocument; + let input = doc.querySelector('#vimvixen-console-command-input'); + this.completionOrigin = input.value; + + this.prevValue = e.target.value; + return browser.runtime.sendMessage({ + type: messages.CONSOLE_QUERY_COMPLETIONS, + text: e.target.value + }).then((completions) => { + this.store.dispatch(consoleActions.setCompletions(completions)); + }); + } + + update() { + let state = this.store.getState(); + if (!this.prevState.commandShown && state.commandShown) { + this.showCommand(state.commandText); + } else if (!state.commandShown) { + this.hideCommand(); + } + + if (state.errorShown) { + this.setErrorText(state.errorText); + this.showError(); + } else { + this.hideError(); + } + + if (state.groupSelection >= 0 && state.itemSelection >= 0) { + let group = state.completions[state.groupSelection]; + let item = group.items[state.itemSelection]; + this.setCommandValue(item.content); + } else if (state.completions.length > 0 && + JSON.stringify(this.prevState.completions) === + JSON.stringify(state.completions)) { + // Reset input only completion groups not changed (unselected an item in + // completion) in order to avoid to override previous input + this.setCommandCompletionOrigin(); + } + + this.prevState = state; + } + + showCommand(text) { + let doc = this.wrapper.ownerDocument; + let command = doc.querySelector('#vimvixen-console-command'); + let input = doc.querySelector('#vimvixen-console-command-input'); + + command.style.display = 'block'; + input.value = text; + input.focus(); + } + + hideCommand() { + let doc = this.wrapper.ownerDocument; + let command = doc.querySelector('#vimvixen-console-command'); + command.style.display = 'none'; + } + + setCommandValue(value) { + let doc = this.wrapper.ownerDocument; + let input = doc.querySelector('#vimvixen-console-command-input'); + input.value = value; + } + + setCommandCompletionOrigin() { + let doc = this.wrapper.ownerDocument; + let input = doc.querySelector('#vimvixen-console-command-input'); + input.value = this.completionOrigin; + } + + setErrorText(text) { + let doc = this.wrapper.ownerDocument; + let error = doc.querySelector('#vimvixen-console-error'); + error.textContent = text; + } + + showError() { + let doc = this.wrapper.ownerDocument; + let error = doc.querySelector('#vimvixen-console-error'); + error.style.display = 'block'; + } + + hideError() { + let doc = this.wrapper.ownerDocument; + let error = doc.querySelector('#vimvixen-console-error'); + error.style.display = 'none'; + } +} diff --git a/src/console/index.html b/src/console/index.html new file mode 100644 index 0000000..4222f12 --- /dev/null +++ b/src/console/index.html @@ -0,0 +1,20 @@ + + + + + VimVixen console + + + +

+
+ +
+ +
+
+ + diff --git a/src/console/index.js b/src/console/index.js new file mode 100644 index 0000000..1bcf8bc --- /dev/null +++ b/src/console/index.js @@ -0,0 +1,34 @@ +import './site.scss'; +import messages from 'shared/messages'; +import CompletionComponent from 'console/components/completion'; +import ConsoleComponent from 'console/components/console'; +import reducers from 'console/reducers'; +import { createStore } from 'store'; +import * as consoleActions from 'console/actions/console'; + +const store = createStore(reducers); +let completionComponent = null; +let consoleComponent = null; + +window.addEventListener('load', () => { + let wrapper = document.querySelector('#vimvixen-console-completion'); + completionComponent = new CompletionComponent(wrapper, store); + + consoleComponent = new ConsoleComponent(document.body, store); +}); + +store.subscribe(() => { + completionComponent.update(); + consoleComponent.update(); +}); + +browser.runtime.onMessage.addListener((action) => { + switch (action.type) { + case messages.CONSOLE_SHOW_COMMAND: + return store.dispatch(consoleActions.showCommand(action.command)); + case messages.CONSOLE_SHOW_ERROR: + return store.dispatch(consoleActions.showError(action.text)); + case messages.CONSOLE_HIDE: + return store.dispatch(consoleActions.hide(action.command)); + } +}); diff --git a/src/console/reducers/index.js b/src/console/reducers/index.js new file mode 100644 index 0000000..ee9c691 --- /dev/null +++ b/src/console/reducers/index.js @@ -0,0 +1,94 @@ +import actions from 'console/actions'; + +const defaultState = { + errorShown: false, + errorText: '', + commandShown: false, + commandText: '', + completions: [], + groupSelection: -1, + itemSelection: -1, +}; + +const nextSelection = (state) => { + if (state.groupSelection < 0) { + return [0, 0]; + } + + let group = state.completions[state.groupSelection]; + if (state.groupSelection + 1 >= state.completions.length && + state.itemSelection + 1 >= group.items.length) { + return [-1, -1]; + } + if (state.itemSelection + 1 >= group.items.length) { + return [state.groupSelection + 1, 0]; + } + return [state.groupSelection, state.itemSelection + 1]; +}; + +const prevSelection = (state) => { + if (state.groupSelection < 0) { + return [ + state.completions.length - 1, + state.completions[state.completions.length - 1].items.length - 1 + ]; + } + if (state.groupSelection === 0 && state.itemSelection === 0) { + return [-1, -1]; + } else if (state.itemSelection === 0) { + return [ + state.groupSelection - 1, + state.completions[state.groupSelection - 1].items.length - 1 + ]; + } + return [state.groupSelection, state.itemSelection - 1]; +}; + +export default function reducer(state = defaultState, action = {}) { + switch (action.type) { + case actions.CONSOLE_SHOW_COMMAND: + return Object.assign({}, state, { + commandShown: true, + commandText: action.text, + errorShown: false, + completions: [] + }); + case actions.CONSOLE_SHOW_ERROR: + return Object.assign({}, state, { + errorText: action.text, + errorShown: true, + commandShown: false, + }); + case actions.CONSOLE_HIDE: + if (state.errorShown) { + // keep error message if shown + return state; + } + return Object.assign({}, state, { + errorShown: false, + commandShown: false + }); + case actions.CONSOLE_SET_COMPLETIONS: + return Object.assign({}, state, { + completions: action.completions, + groupSelection: -1, + itemSelection: -1, + }); + case actions.CONSOLE_COMPLETION_NEXT: { + let next = nextSelection(state); + return Object.assign({}, state, { + groupSelection: next[0], + itemSelection: next[1], + }); + } + case actions.CONSOLE_COMPLETION_PREV: { + let next = prevSelection(state); + return Object.assign({}, state, { + groupSelection: next[0], + itemSelection: next[1], + }); + } + default: + return state; + } +} diff --git a/src/console/site.scss b/src/console/site.scss new file mode 100644 index 0000000..5823dce --- /dev/null +++ b/src/console/site.scss @@ -0,0 +1,92 @@ +html, body, * { + margin: 0; + padding: 0; +} + +body { + position: absolute; + bottom: 0; + left: 0; + right: 0; + overflow: hidden; +} + +.vimvixen-console { + border-top: 1px solid gray; + bottom: 0; + margin: 0; + padding: 0; + + @mixin consoole-font { + font-style: normal; + font-family: monospace; + font-size: 12px; + line-height: 16px; + } + + &-completion { + background-color: white; + + @include consoole-font; + + &-title { + background-color: lightgray; + font-weight: bold; + margin: 0; + padding: 0; + } + + &-item { + padding-left: 1.5rem; + background-position: 0 center; + background-size: contain; + background-repeat: no-repeat; + white-space: nowrap; + + &.vimvixen-completion-selected { + background-color: yellow; + } + + &-caption { + display: inline-block; + width: 40%; + text-overflow: ellipsis; + overflow: hidden; + } + + &-url { + display: inline-block; + color: green; + width: 60%; + text-overflow: ellipsis; + overflow: hidden; + } + } + } + + &-error { + background-color: red; + font-weight: bold; + color: white; + + @include consoole-font; + } + + &-command { + background-color: white; + display: flex; + + &-prompt:before { + content: ':'; + + @include consoole-font; + } + + &-input { + border: none; + flex-grow: 1; + + @include consoole-font; + } + } +} -- cgit v1.2.3 From 2faf44af7443b3f858e15d63295a490afba83b4e Mon Sep 17 00:00:00 2001 From: Shin'ya Ueoka Date: Sun, 8 Oct 2017 15:11:09 +0900 Subject: move store to shared --- src/background/index.js | 2 +- src/console/index.js | 2 +- src/content/index.js | 2 +- src/settings/index.js | 2 +- src/shared/store/index.js | 53 +++++++++++++++++++ src/store/index.js | 53 ------------------- test/shared/store/index.test.js | 111 ++++++++++++++++++++++++++++++++++++++++ test/store/index.test.js | 111 ---------------------------------------- 8 files changed, 168 insertions(+), 168 deletions(-) create mode 100644 src/shared/store/index.js delete mode 100644 src/store/index.js create mode 100644 test/shared/store/index.test.js delete mode 100644 test/store/index.test.js (limited to 'src/console') diff --git a/src/background/index.js b/src/background/index.js index 587cc0b..6ba37eb 100644 --- a/src/background/index.js +++ b/src/background/index.js @@ -2,7 +2,7 @@ import * as settingsActions from 'settings/actions/setting'; import messages from 'shared/messages'; import BackgroundComponent from 'background/components/background'; import reducers from 'settings/reducers/setting'; -import { createStore } from 'store'; +import { createStore } from 'shared/store'; const store = createStore(reducers, (e, sender) => { console.error('Vim-Vixen:', e); diff --git a/src/console/index.js b/src/console/index.js index 1bcf8bc..7396a96 100644 --- a/src/console/index.js +++ b/src/console/index.js @@ -3,7 +3,7 @@ import messages from 'shared/messages'; import CompletionComponent from 'console/components/completion'; import ConsoleComponent from 'console/components/console'; import reducers from 'console/reducers'; -import { createStore } from 'store'; +import { createStore } from 'shared/store'; import * as consoleActions from 'console/actions/console'; const store = createStore(reducers); diff --git a/src/content/index.js b/src/content/index.js index 00873cc..adea871 100644 --- a/src/content/index.js +++ b/src/content/index.js @@ -1,7 +1,7 @@ import './console-frame.scss'; import * as consoleFrames from './console-frames'; import * as settingActions from 'settings/actions/setting'; -import { createStore } from 'store'; +import { createStore } from 'shared/store'; import ContentInputComponent from 'content/components/content-input'; import KeymapperComponent from 'content/components/keymapper'; import FollowComponent from 'content/components/follow'; diff --git a/src/settings/index.js b/src/settings/index.js index 8c60f80..c8d6cc4 100644 --- a/src/settings/index.js +++ b/src/settings/index.js @@ -1,7 +1,7 @@ import './site.scss'; import SettingComponent from 'settings/components/setting'; import settingReducer from 'settings/reducers/setting'; -import { createStore } from 'store'; +import { createStore } from 'shared/store'; const store = createStore(settingReducer); let settingComponent = null; diff --git a/src/shared/store/index.js b/src/shared/store/index.js new file mode 100644 index 0000000..2fafdf1 --- /dev/null +++ b/src/shared/store/index.js @@ -0,0 +1,53 @@ +class Store { + constructor(reducer, catcher) { + this.reducer = reducer; + this.catcher = catcher; + this.subscribers = []; + try { + this.state = this.reducer(undefined, {}); + } catch (e) { + catcher(e); + } + } + + dispatch(action, sender) { + if (action instanceof Promise) { + action.then((a) => { + this.transitNext(a, sender); + }).catch((e) => { + this.catcher(e, sender); + }); + } else { + try { + this.transitNext(action, sender); + } catch (e) { + this.catcher(e, sender); + } + } + return action; + } + + getState() { + return this.state; + } + + subscribe(callback) { + this.subscribers.push(callback); + } + + transitNext(action, sender) { + let newState = this.reducer(this.state, action); + if (JSON.stringify(this.state) !== JSON.stringify(newState)) { + this.state = newState; + this.subscribers.forEach(f => f(sender)); + } + } +} + +const empty = () => {}; + +const createStore = (reducer, catcher = empty) => { + return new Store(reducer, catcher); +}; + +export { createStore }; diff --git a/src/store/index.js b/src/store/index.js deleted file mode 100644 index 2fafdf1..0000000 --- a/src/store/index.js +++ /dev/null @@ -1,53 +0,0 @@ -class Store { - constructor(reducer, catcher) { - this.reducer = reducer; - this.catcher = catcher; - this.subscribers = []; - try { - this.state = this.reducer(undefined, {}); - } catch (e) { - catcher(e); - } - } - - dispatch(action, sender) { - if (action instanceof Promise) { - action.then((a) => { - this.transitNext(a, sender); - }).catch((e) => { - this.catcher(e, sender); - }); - } else { - try { - this.transitNext(action, sender); - } catch (e) { - this.catcher(e, sender); - } - } - return action; - } - - getState() { - return this.state; - } - - subscribe(callback) { - this.subscribers.push(callback); - } - - transitNext(action, sender) { - let newState = this.reducer(this.state, action); - if (JSON.stringify(this.state) !== JSON.stringify(newState)) { - this.state = newState; - this.subscribers.forEach(f => f(sender)); - } - } -} - -const empty = () => {}; - -const createStore = (reducer, catcher = empty) => { - return new Store(reducer, catcher); -}; - -export { createStore }; diff --git a/test/shared/store/index.test.js b/test/shared/store/index.test.js new file mode 100644 index 0000000..133033b --- /dev/null +++ b/test/shared/store/index.test.js @@ -0,0 +1,111 @@ +import { expect } from "chai"; +import { createStore } from 'shared/store'; + +describe("Store class", () => { + const reducer = (state, action) => { + if (state == undefined) { + return 0; + } + return state + action; + }; + + describe("#dispatch", () => { + it('transit status by immediate action', () => { + let store = createStore(reducer); + store.dispatch(10); + expect(store.getState()).to.equal(10); + + store.dispatch(-20); + expect(store.getState()).to.equal(-10); + }); + + it('returns next state by immediate action', () => { + let store = createStore(reducer); + let dispatchedAction = store.dispatch(11); + expect(dispatchedAction).to.equal(11); + }); + + it('transit status by Promise action', () => { + let store = createStore(reducer); + let p1 = Promise.resolve(10); + + return store.dispatch(p1).then(() => { + expect(store.getState()).to.equal(10); + }).then(() => { + store.dispatch(Promise.resolve(-20)); + }).then(() => { + expect(store.getState()).to.equal(-10); + }); + }); + + it('returns next state by promise action', () => { + let store = createStore(reducer); + let dispatchedAction = store.dispatch(Promise.resolve(11)); + return dispatchedAction.then((value) => { + expect(value).to.equal(11); + }); + }); + }); + + describe("#subscribe", () => { + it('invoke callback', (done) => { + let store = createStore(reducer); + store.subscribe(() => { + expect(store.getState()).to.equal(15); + done(); + }); + store.dispatch(15); + }); + + it('propagate sender object', (done) => { + let store = createStore(reducer); + store.subscribe((sender) => { + expect(sender).to.equal('sender'); + done(); + }); + store.dispatch(15, 'sender'); + }); + }) + + describe("catcher", () => { + it('catch an error in reducer on initializing by immediate action', (done) => { + let store = createStore(() => { + throw new Error(); + }, (e) => { + expect(e).to.be.an('error'); + done(); + }); + }); + + it('catch an error in reducer on initializing by immediate action', (done) => { + let store = createStore((state, action) => { + if (state === undefined) return 0; + throw new Error(); + }, (e) => { + expect(e).to.be.an('error'); + done(); + }); + store.dispatch(20); + }); + + it('catch an error in reducer on initializing by promise action', (done) => { + let store = createStore((state, action) => { + if (state === undefined) return 0; + throw new Error(); + }, (e) => { + expect(e).to.be.an('error'); + done(); + }); + store.dispatch(Promise.resolve(20)); + }); + + it('catch an error in promise action', (done) => { + let store = createStore((state, action) => 0, (e) => { + expect(e).to.be.an('error'); + done(); + }); + store.dispatch(new Promise(() => { throw new Error() })); + }); + }) +}); + diff --git a/test/store/index.test.js b/test/store/index.test.js deleted file mode 100644 index 5dce715..0000000 --- a/test/store/index.test.js +++ /dev/null @@ -1,111 +0,0 @@ -import { expect } from "chai"; -import { createStore } from 'store'; - -describe("Store class", () => { - const reducer = (state, action) => { - if (state == undefined) { - return 0; - } - return state + action; - }; - - describe("#dispatch", () => { - it('transit status by immediate action', () => { - let store = createStore(reducer); - store.dispatch(10); - expect(store.getState()).to.equal(10); - - store.dispatch(-20); - expect(store.getState()).to.equal(-10); - }); - - it('returns next state by immediate action', () => { - let store = createStore(reducer); - let dispatchedAction = store.dispatch(11); - expect(dispatchedAction).to.equal(11); - }); - - it('transit status by Promise action', () => { - let store = createStore(reducer); - let p1 = Promise.resolve(10); - - return store.dispatch(p1).then(() => { - expect(store.getState()).to.equal(10); - }).then(() => { - store.dispatch(Promise.resolve(-20)); - }).then(() => { - expect(store.getState()).to.equal(-10); - }); - }); - - it('returns next state by promise action', () => { - let store = createStore(reducer); - let dispatchedAction = store.dispatch(Promise.resolve(11)); - return dispatchedAction.then((value) => { - expect(value).to.equal(11); - }); - }); - }); - - describe("#subscribe", () => { - it('invoke callback', (done) => { - let store = createStore(reducer); - store.subscribe(() => { - expect(store.getState()).to.equal(15); - done(); - }); - store.dispatch(15); - }); - - it('propagate sender object', (done) => { - let store = createStore(reducer); - store.subscribe((sender) => { - expect(sender).to.equal('sender'); - done(); - }); - store.dispatch(15, 'sender'); - }); - }) - - describe("catcher", () => { - it('catch an error in reducer on initializing by immediate action', (done) => { - let store = createStore(() => { - throw new Error(); - }, (e) => { - expect(e).to.be.an('error'); - done(); - }); - }); - - it('catch an error in reducer on initializing by immediate action', (done) => { - let store = createStore((state, action) => { - if (state === undefined) return 0; - throw new Error(); - }, (e) => { - expect(e).to.be.an('error'); - done(); - }); - store.dispatch(20); - }); - - it('catch an error in reducer on initializing by promise action', (done) => { - let store = createStore((state, action) => { - if (state === undefined) return 0; - throw new Error(); - }, (e) => { - expect(e).to.be.an('error'); - done(); - }); - store.dispatch(Promise.resolve(20)); - }); - - it('catch an error in promise action', (done) => { - let store = createStore((state, action) => 0, (e) => { - expect(e).to.be.an('error'); - done(); - }); - store.dispatch(new Promise(() => { throw new Error() })); - }); - }) -}); - -- cgit v1.2.3