Commit 8b9fb322 authored by Chad Woolley's avatar Chad Woolley Committed by Paul Slaughter

Refactor Web IDE collapsible sidebar logic

Make it reusable for left sidebar as well as right.

This supports merge request https://gitlab.com/gitlab-org/gitlab/merge_requests/21010
parent 0e09920a
<script> <script>
import Vue from 'vue'; import Vue from 'vue';
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapActions, mapGetters, mapState } from 'vuex';
import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { __ } from '~/locale'; import { __ } from '~/locale';
import FindFile from '~/vue_shared/components/file_finder/index.vue'; import FindFile from '~/vue_shared/components/file_finder/index.vue';
......
<script>
import { mapActions, mapState } from 'vuex';
import _ from 'underscore';
import tooltip from '~/vue_shared/directives/tooltip';
import Icon from '~/vue_shared/components/icon.vue';
import ResizablePanel from '../resizable_panel.vue';
import { GlSkeletonLoading } from '@gitlab/ui';
export default {
name: 'CollapsibleSidebar',
directives: {
tooltip,
},
components: {
Icon,
ResizablePanel,
GlSkeletonLoading,
},
props: {
extensionTabs: {
type: Array,
required: false,
default: () => [],
},
side: {
type: String,
required: true,
},
width: {
type: Number,
required: true,
},
},
computed: {
...mapState(['loading']),
...mapState({
isOpen(state) {
return state[this.namespace].isOpen;
},
currentView(state) {
return state[this.namespace].currentView;
},
isActiveView(state, getters) {
return getters[`${this.namespace}/isActiveView`];
},
isAliveView(_state, getters) {
return getters[`${this.namespace}/isAliveView`];
},
}),
namespace() {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
return `${this.side}Pane`;
},
tabs() {
return this.extensionTabs.filter(tab => tab.show);
},
tabViews() {
return _.flatten(this.tabs.map(tab => tab.views));
},
aliveTabViews() {
return this.tabViews.filter(view => this.isAliveView(view.name));
},
otherSide() {
return this.side === 'right' ? 'left' : 'right';
},
},
methods: {
...mapActions({
toggleOpen(dispatch) {
return dispatch(`${this.namespace}/toggleOpen`);
},
open(dispatch, view) {
return dispatch(`${this.namespace}/open`, view);
},
}),
clickTab(e, tab) {
e.target.blur();
if (this.isActiveTab(tab)) {
this.toggleOpen();
} else {
this.open(tab.views[0]);
}
},
isActiveTab(tab) {
return tab.views.some(view => this.isActiveView(view.name));
},
buttonClasses(tab) {
return [
this.side === 'right' ? 'is-right' : '',
this.isActiveTab(tab) && this.isOpen ? 'active' : '',
...(tab.buttonClasses || []),
];
},
},
};
</script>
<template>
<div
:class="`ide-${side}-sidebar`"
:data-qa-selector="`ide_${side}_sidebar`"
class="multi-file-commit-panel ide-sidebar"
>
<resizable-panel
v-show="isOpen"
:collapsible="false"
:initial-width="width"
:min-size="width"
:class="`ide-${side}-sidebar-${currentView}`"
:side="side"
class="multi-file-commit-panel-inner"
>
<div class="h-100 d-flex flex-column align-items-stretch">
<slot v-if="isOpen" name="header"></slot>
<div
v-for="tabView in aliveTabViews"
v-show="isActiveView(tabView.name)"
:key="tabView.name"
class="flex-fill js-tab-view"
>
<component :is="tabView.component" />
</div>
<slot name="footer"></slot>
</div>
</resizable-panel>
<nav class="ide-activity-bar">
<ul class="list-unstyled">
<li>
<slot name="header-icon"></slot>
</li>
<li v-for="tab of tabs" :key="tab.title">
<button
v-tooltip
:title="tab.title"
:aria-label="tab.title"
:class="buttonClasses(tab)"
data-container="body"
:data-placement="otherSide"
:data-qa-selector="`${tab.title.toLowerCase()}_tab_button`"
class="ide-sidebar-link"
type="button"
@click="clickTab($event, tab)"
>
<icon :size="16" :name="tab.icon" />
</button>
</li>
</ul>
</nav>
</div>
</template>
<script> <script>
import { mapActions, mapState, mapGetters } from 'vuex'; import { mapGetters, mapState } from 'vuex';
import _ from 'underscore';
import { __ } from '~/locale'; import { __ } from '~/locale';
import tooltip from '../../../vue_shared/directives/tooltip'; import CollapsibleSidebar from './collapsible_sidebar.vue';
import Icon from '../../../vue_shared/components/icon.vue';
import { rightSidebarViews } from '../../constants'; import { rightSidebarViews } from '../../constants';
import MergeRequestInfo from '../merge_requests/info.vue';
import PipelinesList from '../pipelines/list.vue'; import PipelinesList from '../pipelines/list.vue';
import JobsDetail from '../jobs/detail.vue'; import JobsDetail from '../jobs/detail.vue';
import MergeRequestInfo from '../merge_requests/info.vue';
import ResizablePanel from '../resizable_panel.vue';
import Clientside from '../preview/clientside.vue'; import Clientside from '../preview/clientside.vue';
export default { export default {
directives: { name: 'RightPane',
tooltip,
},
components: { components: {
Icon, CollapsibleSidebar,
PipelinesList,
JobsDetail,
ResizablePanel,
MergeRequestInfo,
Clientside,
}, },
props: { props: {
extensionTabs: { extensionTabs: {
...@@ -32,103 +22,40 @@ export default { ...@@ -32,103 +22,40 @@ export default {
}, },
computed: { computed: {
...mapState(['currentMergeRequestId', 'clientsidePreviewEnabled']), ...mapState(['currentMergeRequestId', 'clientsidePreviewEnabled']),
...mapState('rightPane', ['isOpen', 'currentView']),
...mapGetters(['packageJson']), ...mapGetters(['packageJson']),
...mapGetters('rightPane', ['isActiveView', 'isAliveView']),
showLivePreview() { showLivePreview() {
return this.packageJson && this.clientsidePreviewEnabled; return this.packageJson && this.clientsidePreviewEnabled;
}, },
defaultTabs() { rightExtensionTabs() {
return [ return [
{ {
show: this.currentMergeRequestId, show: Boolean(this.currentMergeRequestId),
title: __('Merge Request'), title: __('Merge Request'),
views: [rightSidebarViews.mergeRequestInfo], views: [{ component: MergeRequestInfo, ...rightSidebarViews.mergeRequestInfo }],
icon: 'text-description', icon: 'text-description',
}, },
{ {
show: true, show: true,
title: __('Pipelines'), title: __('Pipelines'),
views: [rightSidebarViews.pipelines, rightSidebarViews.jobsDetail], views: [
{ component: PipelinesList, ...rightSidebarViews.pipelines },
{ component: JobsDetail, ...rightSidebarViews.jobsDetail },
],
icon: 'rocket', icon: 'rocket',
}, },
{ {
show: this.showLivePreview, show: this.showLivePreview,
title: __('Live preview'), title: __('Live preview'),
views: [rightSidebarViews.clientSidePreview], views: [{ component: Clientside, ...rightSidebarViews.clientSidePreview }],
icon: 'live-preview', icon: 'live-preview',
}, },
...this.extensionTabs,
]; ];
}, },
tabs() {
return this.defaultTabs.concat(this.extensionTabs).filter(tab => tab.show);
},
tabViews() {
return _.flatten(this.tabs.map(tab => tab.views));
},
aliveTabViews() {
return this.tabViews.filter(view => this.isAliveView(view.name));
},
},
methods: {
...mapActions('rightPane', ['toggleOpen', 'open']),
clickTab(e, tab) {
e.target.blur();
if (this.isActiveTab(tab)) {
this.toggleOpen();
} else {
this.open(tab.views[0]);
}
},
isActiveTab(tab) {
return tab.views.some(view => this.isActiveView(view.name));
},
}, },
}; };
</script> </script>
<template> <template>
<div class="multi-file-commit-panel ide-right-sidebar" data-qa-selector="ide_right_sidebar"> <collapsible-sidebar :extension-tabs="rightExtensionTabs" side="right" :width="350" />
<resizable-panel
v-show="isOpen"
:collapsible="false"
:initial-width="350"
:min-size="350"
:class="`ide-right-sidebar-${currentView}`"
side="right"
class="multi-file-commit-panel-inner"
>
<div
v-for="tabView in aliveTabViews"
v-show="isActiveView(tabView.name)"
:key="tabView.name"
class="h-100"
>
<component :is="tabView.component || tabView.name" />
</div>
</resizable-panel>
<nav class="ide-activity-bar">
<ul class="list-unstyled">
<li v-for="tab of tabs" :key="tab.title">
<button
v-tooltip
:title="tab.title"
:aria-label="tab.title"
:class="{
active: isActiveTab(tab) && isOpen,
}"
data-container="body"
data-placement="left"
:data-qa-selector="`${tab.title.toLowerCase()}_tab_button`"
class="ide-sidebar-link is-right"
type="button"
@click="clickTab($event, tab)"
>
<icon :size="16" :name="tab.icon" />
</button>
</li>
</ul>
</nav>
</div>
</template> </template>
...@@ -33,19 +33,6 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { ...@@ -33,19 +33,6 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => {
} }
}; };
export const toggleRightPanelCollapsed = ({ dispatch, state }, e = undefined) => {
if (e) {
$(e.currentTarget)
.tooltip('hide')
.blur();
}
dispatch('setPanelCollapsedStatus', {
side: 'right',
collapsed: !state.rightPanelCollapsed,
});
};
export const setResizingStatus = ({ commit }, resizing) => { export const setResizingStatus = ({ commit }, resizing) => {
commit(types.SET_RESIZING_STATUS, resizing); commit(types.SET_RESIZING_STATUS, resizing);
}; };
......
import * as types from './mutation_types'; import * as types from './mutation_types';
export const toggleOpen = ({ dispatch, state }, view) => { export const toggleOpen = ({ dispatch, state }) => {
if (state.isOpen) { if (state.isOpen) {
dispatch('close'); dispatch('close');
} else { } else {
dispatch('open', view); dispatch('open');
} }
}; };
export const open = ({ commit }, view) => { export const open = ({ state, commit }, view) => {
commit(types.SET_OPEN, true); commit(types.SET_OPEN, true);
if (view) { if (view && view.name !== state.currentView) {
const { name, keepAlive } = view; const { name, keepAlive } = view;
commit(types.SET_CURRENT_VIEW, name); commit(types.SET_CURRENT_VIEW, name);
......
...@@ -11,7 +11,6 @@ export const SET_LINKS = 'SET_LINKS'; ...@@ -11,7 +11,6 @@ export const SET_LINKS = 'SET_LINKS';
// Project Mutation Types // Project Mutation Types
export const SET_PROJECT = 'SET_PROJECT'; export const SET_PROJECT = 'SET_PROJECT';
export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT'; export const SET_CURRENT_PROJECT = 'SET_CURRENT_PROJECT';
export const TOGGLE_PROJECT_OPEN = 'TOGGLE_PROJECT_OPEN';
export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE'; export const TOGGLE_EMPTY_STATE = 'TOGGLE_EMPTY_STATE';
// Merge Request Mutation Types // Merge Request Mutation Types
......
...@@ -740,6 +740,7 @@ $ide-commit-header-height: 48px; ...@@ -740,6 +740,7 @@ $ide-commit-header-height: 48px;
.ide-sidebar-link { .ide-sidebar-link {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
position: relative; position: relative;
height: 60px; height: 60px;
width: 100%; width: 100%;
...@@ -1076,10 +1077,12 @@ $ide-commit-header-height: 48px; ...@@ -1076,10 +1077,12 @@ $ide-commit-header-height: 48px;
} }
} }
.ide-right-sidebar { .ide-sidebar {
width: auto; width: auto;
min-width: 60px; min-width: 60px;
}
.ide-right-sidebar {
.ide-activity-bar { .ide-activity-bar {
border-left: 1px solid $white-dark; border-left: 1px solid $white-dark;
} }
......
import { shallowMount, createLocalVue } from '@vue/test-utils'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import Vuex from 'vuex'; import Vuex from 'vuex';
import EERightPane from 'ee/ide/components/panes/right.vue'; import EERightPane from 'ee/ide/components/panes/right.vue';
import RightPane from '~/ide/components/panes/right.vue'; import RightPane from '~/ide/components/panes/right.vue';
...@@ -29,6 +29,7 @@ describe('IDE EERightPane', () => { ...@@ -29,6 +29,7 @@ describe('IDE EERightPane', () => {
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('adds terminal tab', () => { it('adds terminal tab', () => {
......
...@@ -8,8 +8,8 @@ module QA ...@@ -8,8 +8,8 @@ module QA
module WebTerminalPanel module WebTerminalPanel
def self.prepended(page) def self.prepended(page)
page.module_eval do page.module_eval do
view 'app/assets/javascripts/ide/components/panes/right.vue' do view 'app/assets/javascripts/ide/components/panes/collapsible_sidebar.vue' do
element :ide_right_sidebar element :ide_right_sidebar, %q(:data-qa-selector="`ide_${side}_sidebar`") # rubocop:disable QA/ElementWithPattern
element :terminal_tab_button, %q(:data-qa-selector="`${tab.title.toLowerCase()}_tab_button`") # rubocop:disable QA/ElementWithPattern element :terminal_tab_button, %q(:data-qa-selector="`${tab.title.toLowerCase()}_tab_button`") # rubocop:disable QA/ElementWithPattern
end end
......
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { createStore } from '~/ide/stores';
import paneModule from '~/ide/stores/modules/pane';
import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue';
import Vuex from 'vuex';
const localVue = createLocalVue();
localVue.use(Vuex);
describe('ide/components/panes/collapsible_sidebar.vue', () => {
let wrapper;
let store;
const width = 350;
const fakeComponentName = 'fake-component';
const createComponent = props => {
wrapper = shallowMount(CollapsibleSidebar, {
localVue,
store,
propsData: {
extensionTabs: [],
side: 'right',
width,
...props,
},
slots: {
'header-icon': '<div class=".header-icon-slot">SLOT ICON</div>',
header: '<div class=".header-slot"/>',
footer: '<div class=".footer-slot"/>',
},
});
};
const findTabButton = () => wrapper.find(`[data-qa-selector="${fakeComponentName}_tab_button"]`);
beforeEach(() => {
store = createStore();
store.registerModule('leftPane', paneModule());
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('with a tab', () => {
let fakeView;
let extensionTabs;
beforeEach(() => {
const FakeComponent = localVue.component(fakeComponentName, {
render: () => {},
});
fakeView = {
name: fakeComponentName,
keepAlive: true,
component: FakeComponent,
};
extensionTabs = [
{
show: true,
title: fakeComponentName,
views: [fakeView],
icon: 'text-description',
buttonClasses: ['button-class-1', 'button-class-2'],
},
];
});
describe.each`
side
${'left'}
${'right'}
`('when side=$side', ({ side }) => {
it('correctly renders side specific attributes', () => {
createComponent({ extensionTabs, side });
const button = findTabButton();
return wrapper.vm.$nextTick().then(() => {
expect(wrapper.classes()).toContain('multi-file-commit-panel');
expect(wrapper.classes()).toContain(`ide-${side}-sidebar`);
expect(wrapper.find('.multi-file-commit-panel-inner')).not.toBe(null);
expect(wrapper.find(`.ide-${side}-sidebar-${fakeComponentName}`)).not.toBe(null);
expect(button.attributes('data-placement')).toEqual(side === 'left' ? 'right' : 'left');
if (side === 'right') {
// this class is only needed on the right side; there is no 'is-left'
expect(button.classes()).toContain('is-right');
} else {
expect(button.classes()).not.toContain('is-right');
}
});
});
});
describe('when default side', () => {
let button;
beforeEach(() => {
createComponent({ extensionTabs });
button = findTabButton();
});
it('correctly renders tab-specific classes', () => {
store.state.rightPane.currentView = fakeComponentName;
return wrapper.vm.$nextTick().then(() => {
expect(button.classes()).toContain('button-class-1');
expect(button.classes()).toContain('button-class-2');
});
});
it('can show an open pane tab with an active view', () => {
store.state.rightPane.isOpen = true;
store.state.rightPane.currentView = fakeComponentName;
return wrapper.vm.$nextTick().then(() => {
expect(button.classes()).toEqual(expect.arrayContaining(['ide-sidebar-link', 'active']));
expect(button.attributes('data-original-title')).toEqual(fakeComponentName);
expect(wrapper.find('.js-tab-view').exists()).toBe(true);
});
});
it('does not show a pane which is not open', () => {
store.state.rightPane.isOpen = false;
store.state.rightPane.currentView = fakeComponentName;
return wrapper.vm.$nextTick().then(() => {
expect(button.classes()).not.toEqual(
expect.arrayContaining(['ide-sidebar-link', 'active']),
);
expect(wrapper.find('.js-tab-view').exists()).toBe(false);
});
});
describe('when button is clicked', () => {
it('opens view', () => {
button.trigger('click');
expect(store.state.rightPane.isOpen).toBeTruthy();
});
it('toggles open view if tab is currently active', () => {
button.trigger('click');
expect(store.state.rightPane.isOpen).toBeTruthy();
button.trigger('click');
expect(store.state.rightPane.isOpen).toBeFalsy();
});
});
it('shows header-icon', () => {
expect(wrapper.find('.header-icon-slot')).not.toBeNull();
});
it('shows header', () => {
expect(wrapper.find('.header-slot')).not.toBeNull();
});
it('shows footer', () => {
expect(wrapper.find('.footer-slot')).not.toBeNull();
});
});
});
});
import Vue from 'vue'; import Vue from 'vue';
import '~/behaviors/markdown/render_gfm'; import Vuex from 'vuex';
import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import { createLocalVue, shallowMount } from '@vue/test-utils';
import { createStore } from '~/ide/stores'; import { createStore } from '~/ide/stores';
import RightPane from '~/ide/components/panes/right.vue'; import RightPane from '~/ide/components/panes/right.vue';
import CollapsibleSidebar from '~/ide/components/panes/collapsible_sidebar.vue';
import { rightSidebarViews } from '~/ide/constants'; import { rightSidebarViews } from '~/ide/constants';
describe('IDE right pane', () => { const localVue = createLocalVue();
let Component; localVue.use(Vuex);
let vm;
beforeAll(() => { describe('ide/components/panes/right.vue', () => {
Component = Vue.extend(RightPane); let wrapper;
let store;
const createComponent = props => {
wrapper = shallowMount(RightPane, {
localVue,
store,
propsData: {
...props,
},
}); });
};
beforeEach(() => { beforeEach(() => {
const store = createStore(); store = createStore();
vm = createComponentWithStore(Component, store).$mount();
}); });
afterEach(() => { afterEach(() => {
vm.$destroy(); wrapper.destroy();
wrapper = null;
}); });
describe('active', () => { it('allows tabs to be added via extensionTabs prop', () => {
it('renders merge request button as active', done => { createComponent({
vm.$store.state.rightPane.isOpen = true; extensionTabs: [
vm.$store.state.rightPane.currentView = rightSidebarViews.mergeRequestInfo.name; {
vm.$store.state.currentMergeRequestId = '123'; show: true,
vm.$store.state.currentProjectId = 'gitlab-ce'; title: 'FakeTab',
vm.$store.state.currentMergeRequestId = 1;
vm.$store.state.projects['gitlab-ce'] = {
mergeRequests: {
1: {
iid: 1,
title: 'Testing',
title_html: '<span class="title-html">Testing</span>',
description: 'Description',
description_html: '<p class="description-html">Description HTML</p>',
}, },
}, ],
};
vm.$nextTick()
.then(() => {
expect(vm.$el.querySelector('.ide-sidebar-link.active')).not.toBe(null);
expect(
vm.$el.querySelector('.ide-sidebar-link.active').getAttribute('data-original-title'),
).toBe('Merge Request');
})
.then(done)
.catch(done.fail);
});
}); });
describe('click', () => { expect(wrapper.find(CollapsibleSidebar).props('extensionTabs')).toEqual(
beforeEach(() => { expect.arrayContaining([
jest.spyOn(vm, 'open').mockReturnValue(); expect.objectContaining({
show: true,
title: 'FakeTab',
}),
]),
);
}); });
it('sets view to merge request', done => { describe('pipelines tab', () => {
vm.$store.state.currentMergeRequestId = '123'; it('is always shown', () => {
createComponent();
vm.$nextTick(() => { expect(wrapper.find(CollapsibleSidebar).props('extensionTabs')).toEqual(
vm.$el.querySelector('.ide-sidebar-link').click(); expect.arrayContaining([
expect.objectContaining({
show: true,
title: 'Pipelines',
views: expect.arrayContaining([
expect.objectContaining({
name: rightSidebarViews.pipelines.name,
}),
expect.objectContaining({
name: rightSidebarViews.jobsDetail.name,
}),
]),
}),
]),
);
});
});
expect(vm.open).toHaveBeenCalledWith(rightSidebarViews.mergeRequestInfo); describe('merge request tab', () => {
it('is shown if there is a currentMergeRequestId', () => {
store.state.currentMergeRequestId = 1;
done(); createComponent();
});
expect(wrapper.find(CollapsibleSidebar).props('extensionTabs')).toEqual(
expect.arrayContaining([
expect.objectContaining({
show: true,
title: 'Merge Request',
views: expect.arrayContaining([
expect.objectContaining({
name: rightSidebarViews.mergeRequestInfo.name,
}),
]),
}),
]),
);
}); });
}); });
describe('live preview', () => { describe('clientside live preview tab', () => {
it('renders live preview button', done => { it('is shown if there is a packageJson and clientsidePreviewEnabled', () => {
Vue.set(vm.$store.state.entries, 'package.json', { Vue.set(store.state.entries, 'package.json', {
name: 'package.json', name: 'package.json',
}); });
vm.$store.state.clientsidePreviewEnabled = true; store.state.clientsidePreviewEnabled = true;
vm.$nextTick(() => { createComponent();
expect(vm.$el.querySelector('button[aria-label="Live preview"]')).not.toBeNull();
done(); expect(wrapper.find(CollapsibleSidebar).props('extensionTabs')).toEqual(
}); expect.arrayContaining([
expect.objectContaining({
show: true,
title: 'Live preview',
views: expect.arrayContaining([
expect.objectContaining({
name: rightSidebarViews.clientSidePreview.name,
}),
]),
}),
]),
);
}); });
}); });
}); });
...@@ -8,14 +8,7 @@ describe('IDE pane module actions', () => { ...@@ -8,14 +8,7 @@ describe('IDE pane module actions', () => {
describe('toggleOpen', () => { describe('toggleOpen', () => {
it('dispatches open if closed', done => { it('dispatches open if closed', done => {
testAction( testAction(actions.toggleOpen, TEST_VIEW, { isOpen: false }, [], [{ type: 'open' }], done);
actions.toggleOpen,
TEST_VIEW,
{ isOpen: false },
[],
[{ type: 'open', payload: TEST_VIEW }],
done,
);
}); });
it('dispatches close if opened', done => { it('dispatches close if opened', done => {
...@@ -24,11 +17,8 @@ describe('IDE pane module actions', () => { ...@@ -24,11 +17,8 @@ describe('IDE pane module actions', () => {
}); });
describe('open', () => { describe('open', () => {
it('commits SET_OPEN', done => { describe('with a view specified', () => {
testAction(actions.open, null, {}, [{ type: types.SET_OPEN, payload: true }], [], done); it('commits SET_OPEN and SET_CURRENT_VIEW', done => {
});
it('commits SET_CURRENT_VIEW if view is given', done => {
testAction( testAction(
actions.open, actions.open,
TEST_VIEW, TEST_VIEW,
...@@ -58,6 +48,20 @@ describe('IDE pane module actions', () => { ...@@ -58,6 +48,20 @@ describe('IDE pane module actions', () => {
}); });
}); });
describe('without a view specified', () => {
it('commits SET_OPEN', done => {
testAction(
actions.open,
undefined,
{},
[{ type: types.SET_OPEN, payload: true }],
[],
done,
);
});
});
});
describe('close', () => { describe('close', () => {
it('commits SET_OPEN', done => { it('commits SET_OPEN', done => {
testAction(actions.close, null, {}, [{ type: types.SET_OPEN, payload: false }], [], done); testAction(actions.close, null, {}, [{ type: types.SET_OPEN, payload: false }], [], done);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment