Commit 954a50cd authored by Ezekiel Kigbo's avatar Ezekiel Kigbo Committed by Phil Hughes

Remove type of work vuex from base.vue

Moves the type of work vuex code into
the type_of_work_charts component
parent b6687dbd
...@@ -69,13 +69,6 @@ export default { ...@@ -69,13 +69,6 @@ export default {
'medians', 'medians',
'customStageFormErrors', 'customStageFormErrors',
]), ]),
...mapState('typeOfWork', [
'isLoadingTasksByTypeChart',
'isLoadingTasksByTypeChartTopLabels',
'topRankedLabels',
'subject',
'selectedLabelIds',
]),
...mapGetters([ ...mapGetters([
'hasNoAccessError', 'hasNoAccessError',
'currentGroupPath', 'currentGroupPath',
...@@ -84,7 +77,6 @@ export default { ...@@ -84,7 +77,6 @@ export default {
'enableCustomOrdering', 'enableCustomOrdering',
'cycleAnalyticsRequestParams', 'cycleAnalyticsRequestParams',
]), ]),
...mapGetters('typeOfWork', ['tasksByTypeChartData']),
shouldRenderEmptyState() { shouldRenderEmptyState() {
return !this.selectedGroup; return !this.selectedGroup;
}, },
...@@ -106,24 +98,6 @@ export default { ...@@ -106,24 +98,6 @@ export default {
hasDateRangeSet() { hasDateRangeSet() {
return this.startDate && this.endDate; return this.startDate && this.endDate;
}, },
selectedTasksByTypeFilters() {
const {
selectedGroup,
startDate,
endDate,
selectedProjectIds,
subject,
selectedLabelIds,
} = this;
return {
selectedGroup,
selectedProjectIds,
startDate,
endDate,
subject,
selectedLabelIds,
};
},
query() { query() {
return { return {
group_id: !this.hideGroupDropDown ? this.currentGroupPath : null, group_id: !this.hideGroupDropDown ? this.currentGroupPath : null,
...@@ -162,7 +136,6 @@ export default { ...@@ -162,7 +136,6 @@ export default {
'updateStage', 'updateStage',
'reorderStage', 'reorderStage',
]), ]),
...mapActions('typeOfWork', ['setTasksByTypeFilters']),
onGroupSelect(group) { onGroupSelect(group) {
this.setSelectedGroup(group); this.setSelectedGroup(group);
this.fetchCycleAnalyticsData(); this.fetchCycleAnalyticsData();
...@@ -315,13 +288,7 @@ export default { ...@@ -315,13 +288,7 @@ export default {
</div> </div>
</div> </div>
<duration-chart v-if="shouldDisplayDurationChart" class="mt-3" :stages="activeStages" /> <duration-chart v-if="shouldDisplayDurationChart" class="mt-3" :stages="activeStages" />
<type-of-work-charts <type-of-work-charts v-if="shouldDisplayTypeOfWorkCharts" :is-loading="isLoadingTypeOfWork" />
v-if="shouldDisplayTypeOfWorkCharts"
:is-loading="isLoadingTypeOfWork"
:tasks-by-type-chart-data="tasksByTypeChartData"
:selected-tasks-by-type-filters="selectedTasksByTypeFilters"
@updateFilter="setTasksByTypeFilters"
/>
</div> </div>
</div> </div>
</template> </template>
...@@ -20,16 +20,10 @@ export default { ...@@ -20,16 +20,10 @@ export default {
required: true, required: true,
}, },
}, },
computed: {
hasData() {
return Boolean(this.data.length);
},
},
}; };
</script> </script>
<template> <template>
<gl-stacked-column-chart <gl-stacked-column-chart
v-if="hasData"
:data="data" :data="data"
:group-by="groupBy" :group-by="groupBy"
x-axis-type="category" x-axis-type="category"
...@@ -38,7 +32,4 @@ export default { ...@@ -38,7 +32,4 @@ export default {
:y-axis-title="s__('CycleAnalytics|Number of tasks')" :y-axis-title="s__('CycleAnalytics|Number of tasks')"
:series-names="seriesNames" :series-names="seriesNames"
/> />
<div v-else class="bs-callout bs-callout-info">
<p>{{ __('There is no data available. Please change your selection.') }}</p>
</div>
</template> </template>
...@@ -33,6 +33,10 @@ export default { ...@@ -33,6 +33,10 @@ export default {
type: String, type: String,
required: true, required: true,
}, },
hasData: {
type: Boolean,
required: true,
},
}, },
computed: { computed: {
subjectFilterOptions() { subjectFilterOptions() {
...@@ -87,14 +91,13 @@ export default { ...@@ -87,14 +91,13 @@ export default {
TASKS_BY_TYPE_FILTERS, TASKS_BY_TYPE_FILTERS,
}; };
</script> </script>
<template> <template>
<div <div
class="js-tasks-by-type-chart-filters d-flex flex-row justify-content-between align-items-center" class="js-tasks-by-type-chart-filters d-flex flex-row justify-content-between align-items-center"
> >
<div class="flex-column"> <div class="flex-column">
<h4>{{ s__('CycleAnalytics|Tasks by type') }}</h4> <h4>{{ s__('CycleAnalytics|Tasks by type') }}</h4>
<p>{{ selectedFiltersText }}</p> <p v-if="hasData">{{ selectedFiltersText }}</p>
</div> </div>
<div class="flex-column"> <div class="flex-column">
<labels-selector <labels-selector
......
<script> <script>
import { mapActions, mapGetters, mapState } from 'vuex';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import TasksByTypeChart from './tasks_by_type/tasks_by_type_chart.vue'; import TasksByTypeChart from './tasks_by_type/tasks_by_type_chart.vue';
import TasksByTypeFilters from './tasks_by_type/tasks_by_type_filters.vue'; import TasksByTypeFilters from './tasks_by_type/tasks_by_type_filters.vue';
...@@ -9,21 +10,15 @@ import { TASKS_BY_TYPE_SUBJECT_ISSUE } from '../constants'; ...@@ -9,21 +10,15 @@ import { TASKS_BY_TYPE_SUBJECT_ISSUE } from '../constants';
export default { export default {
name: 'TypeOfWorkCharts', name: 'TypeOfWorkCharts',
components: { GlLoadingIcon, TasksByTypeChart, TasksByTypeFilters }, components: { GlLoadingIcon, TasksByTypeChart, TasksByTypeFilters },
props: { computed: {
isLoading: { ...mapState('typeOfWork', ['isLoadingTasksByTypeChart', 'isLoadingTasksByTypeChartTopLabels']),
type: Boolean, ...mapGetters('typeOfWork', ['selectedTasksByTypeFilters', 'tasksByTypeChartData']),
required: true, hasData() {
}, return Boolean(this.tasksByTypeChartData?.data.length);
tasksByTypeChartData: {
type: Object,
required: true,
}, },
selectedTasksByTypeFilters: { isLoading() {
type: Object, return Boolean(this.isLoadingTasksByTypeChart || this.isLoadingTasksByTypeChartTopLabels);
required: true,
}, },
},
computed: {
summaryDescription() { summaryDescription() {
const { const {
startDate, startDate,
...@@ -54,6 +49,15 @@ export default { ...@@ -54,6 +49,15 @@ export default {
} = this; } = this;
return subject || TASKS_BY_TYPE_SUBJECT_ISSUE; return subject || TASKS_BY_TYPE_SUBJECT_ISSUE;
}, },
selectedLabelIdsFilter() {
return this.selectedTasksByTypeFilters?.selectedLabelIds || [];
},
},
methods: {
...mapActions('typeOfWork', ['setTasksByTypeFilters']),
onUpdateFilter(e) {
this.setTasksByTypeFilters(e);
},
}, },
}; };
</script> </script>
...@@ -64,15 +68,20 @@ export default { ...@@ -64,15 +68,20 @@ export default {
<h3>{{ s__('CycleAnalytics|Type of work') }}</h3> <h3>{{ s__('CycleAnalytics|Type of work') }}</h3>
<p>{{ summaryDescription }}</p> <p>{{ summaryDescription }}</p>
<tasks-by-type-filters <tasks-by-type-filters
:selected-label-ids="selectedTasksByTypeFilters.selectedLabelIds" :has-data="hasData"
:selected-label-ids="selectedLabelIdsFilter"
:subject-filter="selectedSubjectFilter" :subject-filter="selectedSubjectFilter"
@updateFilter="$emit('updateFilter', $event)" @updateFilter="onUpdateFilter"
/> />
<tasks-by-type-chart <tasks-by-type-chart
v-if="hasData"
:data="tasksByTypeChartData.data" :data="tasksByTypeChartData.data"
:group-by="tasksByTypeChartData.groupBy" :group-by="tasksByTypeChartData.groupBy"
:series-names="tasksByTypeChartData.seriesNames" :series-names="tasksByTypeChartData.seriesNames"
/> />
<div v-else class="bs-callout bs-callout-info">
<p>{{ __('There is no data available. Please change your selection.') }}</p>
</div>
</div> </div>
</div> </div>
</template> </template>
import { getTasksByTypeData } from '../../../utils'; import { getTasksByTypeData } from '../../../utils';
export const selectedTasksByTypeFilters = (state = {}, _, rootState = {}) => {
const { selectedLabelIds = [], subject } = state;
const { selectedGroup, selectedProjectIds = [], startDate = null, endDate = null } = rootState;
return {
selectedGroup,
selectedProjectIds,
startDate,
endDate,
selectedLabelIds,
subject,
};
};
export const tasksByTypeChartData = ({ data = [] } = {}, _, rootState = {}) => { export const tasksByTypeChartData = ({ data = [] } = {}, _, rootState = {}) => {
const { startDate = null, endDate = null } = rootState; const { startDate = null, endDate = null } = rootState;
return data.length return data.length
...@@ -10,5 +23,3 @@ export const tasksByTypeChartData = ({ data = [] } = {}, _, rootState = {}) => { ...@@ -10,5 +23,3 @@ export const tasksByTypeChartData = ({ data = [] } = {}, _, rootState = {}) => {
}) })
: { groupBy: [], data: [], seriesNames: [] }; : { groupBy: [], data: [], seriesNames: [] };
}; };
export default () => ({ tasksByTypeChartData });
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TasksByTypeChart no data available should render the no data available message 1`] = ` exports[`TasksByTypeChart no data available should render the no data available message 1`] = `"<gl-stacked-column-chart-stub data=\\"\\" option=\\"[object Object]\\" presentation=\\"stacked\\" groupby=\\"\\" xaxistype=\\"category\\" xaxistitle=\\"Date\\" yaxistitle=\\"Number of tasks\\" seriesnames=\\"\\" legendaveragetext=\\"Avg\\" legendmaxtext=\\"Max\\" y-axis-type=\\"value\\"></gl-stacked-column-chart-stub>"`;
"<div class=\\"bs-callout bs-callout-info\\">
<p>There is no data available. Please change your selection.</p>
</div>"
`;
exports[`TasksByTypeChart with data available should render the loading chart 1`] = `"<gl-stacked-column-chart-stub data=\\"0,1,2,5,2,3,2,4,1\\" option=\\"[object Object]\\" presentation=\\"stacked\\" groupby=\\"Group 1,Group 2,Group 3\\" xaxistype=\\"category\\" xaxistitle=\\"Date\\" yaxistitle=\\"Number of tasks\\" seriesnames=\\"Cool label,Normal label\\" legendaveragetext=\\"Avg\\" legendmaxtext=\\"Max\\" y-axis-type=\\"value\\"></gl-stacked-column-chart-stub>"`; exports[`TasksByTypeChart with data available should render the loading chart 1`] = `"<gl-stacked-column-chart-stub data=\\"0,1,2,5,2,3,2,4,1\\" option=\\"[object Object]\\" presentation=\\"stacked\\" groupby=\\"Group 1,Group 2,Group 3\\" xaxistype=\\"category\\" xaxistitle=\\"Date\\" yaxistitle=\\"Number of tasks\\" seriesnames=\\"Cool label,Normal label\\" legendaveragetext=\\"Avg\\" legendmaxtext=\\"Max\\" y-axis-type=\\"value\\"></gl-stacked-column-chart-stub>"`;
...@@ -3,6 +3,7 @@ import axios from 'axios'; ...@@ -3,6 +3,7 @@ import axios from 'axios';
import MockAdapter from 'axios-mock-adapter'; import MockAdapter from 'axios-mock-adapter';
import { shallowMount, mount, createLocalVue } from '@vue/test-utils'; import { shallowMount, mount, createLocalVue } from '@vue/test-utils';
import { GlDropdownItem, GlSegmentedControl } from '@gitlab/ui'; import { GlDropdownItem, GlSegmentedControl } from '@gitlab/ui';
import createFlash from '~/flash';
import TasksByTypeFilters from 'ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_filters.vue'; import TasksByTypeFilters from 'ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_filters.vue';
import LabelsSelector from 'ee/analytics/cycle_analytics/components/labels_selector.vue'; import LabelsSelector from 'ee/analytics/cycle_analytics/components/labels_selector.vue';
import { import {
...@@ -11,7 +12,6 @@ import { ...@@ -11,7 +12,6 @@ import {
TASKS_BY_TYPE_FILTERS, TASKS_BY_TYPE_FILTERS,
} from 'ee/analytics/cycle_analytics/constants'; } from 'ee/analytics/cycle_analytics/constants';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { shouldFlashAMessage } from '../../helpers';
import { groupLabels } from '../../mock_data'; import { groupLabels } from '../../mock_data';
import createStore from 'ee/analytics/cycle_analytics/store'; import createStore from 'ee/analytics/cycle_analytics/store';
import * as getters from 'ee/analytics/cycle_analytics/store/getters'; import * as getters from 'ee/analytics/cycle_analytics/store/getters';
...@@ -36,7 +36,9 @@ let store = null; ...@@ -36,7 +36,9 @@ let store = null;
const localVue = createLocalVue(); const localVue = createLocalVue();
localVue.use(Vuex); localVue.use(Vuex);
function createComponent({ props = {}, mountFn = shallowMount }) { jest.mock('~/flash');
function createComponent({ props = {}, mountFn = shallowMount } = {}) {
store = createStore(); store = createStore();
return mountFn(TasksByTypeFilters, { return mountFn(TasksByTypeFilters, {
localVue, localVue,
...@@ -51,6 +53,7 @@ function createComponent({ props = {}, mountFn = shallowMount }) { ...@@ -51,6 +53,7 @@ function createComponent({ props = {}, mountFn = shallowMount }) {
selectedLabelIds, selectedLabelIds,
labels: groupLabels, labels: groupLabels,
subjectFilter: TASKS_BY_TYPE_SUBJECT_ISSUE, subjectFilter: TASKS_BY_TYPE_SUBJECT_ISSUE,
hasData: true,
...props, ...props,
}, },
stubs: { stubs: {
...@@ -62,6 +65,7 @@ function createComponent({ props = {}, mountFn = shallowMount }) { ...@@ -62,6 +65,7 @@ function createComponent({ props = {}, mountFn = shallowMount }) {
describe('TasksByTypeFilters', () => { describe('TasksByTypeFilters', () => {
let wrapper = null; let wrapper = null;
let mock = null; let mock = null;
const selectedFilterText = (count = 1) => `Showing Issues and ${count} labels`;
beforeEach(() => { beforeEach(() => {
mock = mockGroupLabelsRequest(); mock = mockGroupLabelsRequest();
...@@ -75,7 +79,7 @@ describe('TasksByTypeFilters', () => { ...@@ -75,7 +79,7 @@ describe('TasksByTypeFilters', () => {
wrapper.destroy(); wrapper.destroy();
}); });
describe('labels', () => { describe('with data', () => {
beforeEach(() => { beforeEach(() => {
mock = mockGroupLabelsRequest(); mock = mockGroupLabelsRequest();
wrapper = createComponent({}); wrapper = createComponent({});
...@@ -83,6 +87,31 @@ describe('TasksByTypeFilters', () => { ...@@ -83,6 +87,31 @@ describe('TasksByTypeFilters', () => {
return waitForPromises(); return waitForPromises();
}); });
it('renders the selectedFiltersText', () => {
expect(wrapper.text()).toContain(selectedFilterText());
});
});
describe('with no data', () => {
beforeEach(() => {
mock = mockGroupLabelsRequest();
wrapper = createComponent({ props: { hasData: false } });
return waitForPromises();
});
it('renders the selectedFiltersText', () => {
expect(wrapper.text()).not.toContain(selectedFilterText());
});
});
describe('labels', () => {
beforeEach(() => {
mock = mockGroupLabelsRequest();
wrapper = createComponent();
return waitForPromises();
});
it('emits the `updateFilter` event when a label is selected', () => { it('emits the `updateFilter` event when a label is selected', () => {
expect(wrapper.emitted('updateFilter')).toBeUndefined(); expect(wrapper.emitted('updateFilter')).toBeUndefined();
...@@ -96,7 +125,6 @@ describe('TasksByTypeFilters', () => { ...@@ -96,7 +125,6 @@ describe('TasksByTypeFilters', () => {
describe('with the warningMessageThreshold label threshold reached', () => { describe('with the warningMessageThreshold label threshold reached', () => {
beforeEach(() => { beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
mock = mockGroupLabelsRequest(); mock = mockGroupLabelsRequest();
wrapper = createComponent({ wrapper = createComponent({
props: { props: {
...@@ -112,11 +140,14 @@ describe('TasksByTypeFilters', () => { ...@@ -112,11 +140,14 @@ describe('TasksByTypeFilters', () => {
it('should indicate how many labels are selected', () => { it('should indicate how many labels are selected', () => {
expect(wrapper.text()).toContain('2 selected (5 max)'); expect(wrapper.text()).toContain('2 selected (5 max)');
}); });
it('renders the selectedFiltersText', () => {
expect(wrapper.text()).toContain(selectedFilterText(2));
});
}); });
describe('with maximum labels selected', () => { describe('with maximum labels selected', () => {
beforeEach(() => { beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
mock = mockGroupLabelsRequest(); mock = mockGroupLabelsRequest();
wrapper = createComponent({ wrapper = createComponent({
...@@ -141,7 +172,10 @@ describe('TasksByTypeFilters', () => { ...@@ -141,7 +172,10 @@ describe('TasksByTypeFilters', () => {
}); });
it('should display a message', () => { it('should display a message', () => {
shouldFlashAMessage('Only 2 labels can be selected at this time'); expect(createFlash).toHaveBeenCalledWith(
'Only 2 labels can be selected at this time',
'notice',
);
}); });
}); });
}); });
......
import { shallowMount } from '@vue/test-utils'; import Vuex from 'vuex';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import { GlLoadingIcon } from '@gitlab/ui'; import { GlLoadingIcon } from '@gitlab/ui';
import tasksByTypeStore from 'ee/analytics/cycle_analytics/store/modules/type_of_work';
import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue'; import TypeOfWorkCharts from 'ee/analytics/cycle_analytics/components/type_of_work_charts.vue';
import TasksByTypeChart from 'ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_chart.vue'; import TasksByTypeChart from 'ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_chart.vue';
import TasksByTypeFilters from 'ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_filters.vue'; import TasksByTypeFilters from 'ee/analytics/cycle_analytics/components/tasks_by_type/tasks_by_type_filters.vue';
...@@ -9,18 +11,39 @@ import { ...@@ -9,18 +11,39 @@ import {
TASKS_BY_TYPE_FILTERS, TASKS_BY_TYPE_FILTERS,
} from 'ee/analytics/cycle_analytics/constants'; } from 'ee/analytics/cycle_analytics/constants';
const localVue = createLocalVue();
localVue.use(Vuex);
const actionSpies = {
setTasksByTypeFilters: jest.fn(),
};
const fakeStore = ({ initialGetters, initialState }) =>
new Vuex.Store({
modules: {
typeOfWork: {
...tasksByTypeStore,
getters: {
tasksByTypeChartData: () => tasksByTypeData,
selectedTasksByTypeFilters: () => taskByTypeFilters,
...initialGetters,
},
state: {
...initialState,
},
},
},
});
describe('TypeOfWorkCharts', () => { describe('TypeOfWorkCharts', () => {
function createComponent({ props = {}, stubs = {} } = {}) { function createComponent({ stubs = {}, initialGetters, initialState } = {}) {
return shallowMount(TypeOfWorkCharts, { return shallowMount(TypeOfWorkCharts, {
propsData: { localVue,
isLoading: false, store: fakeStore({ initialGetters, initialState }),
tasksByTypeChartData: tasksByTypeData, methods: actionSpies,
selectedTasksByTypeFilters: taskByTypeFilters,
...props,
},
stubs: { stubs: {
TasksByTypeChart: false, TasksByTypeChart: true,
TasksByTypeFilters: false, TasksByTypeFilters: true,
...stubs, ...stubs,
}, },
}); });
...@@ -31,6 +54,8 @@ describe('TypeOfWorkCharts', () => { ...@@ -31,6 +54,8 @@ describe('TypeOfWorkCharts', () => {
const findSubjectFilters = _wrapper => _wrapper.find(TasksByTypeFilters); const findSubjectFilters = _wrapper => _wrapper.find(TasksByTypeFilters);
const findTasksByTypeChart = _wrapper => _wrapper.find(TasksByTypeChart); const findTasksByTypeChart = _wrapper => _wrapper.find(TasksByTypeChart);
const findLoader = _wrapper => _wrapper.find(GlLoadingIcon); const findLoader = _wrapper => _wrapper.find(GlLoadingIcon);
const selectedFilterText =
"Type of work Showing data for group 'Gitlab Org' from Dec 11, 2019 to Jan 10, 2020";
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
...@@ -45,11 +70,33 @@ describe('TypeOfWorkCharts', () => { ...@@ -45,11 +70,33 @@ describe('TypeOfWorkCharts', () => {
expect(findTasksByTypeChart(wrapper).exists()).toBe(true); expect(findTasksByTypeChart(wrapper).exists()).toBe(true);
}); });
it('renders a description of the current filters', () => {
expect(wrapper.text()).toContain(selectedFilterText);
});
it('does not render the loading icon', () => { it('does not render the loading icon', () => {
expect(findLoader(wrapper).exists()).toBe(false); expect(findLoader(wrapper).exists()).toBe(false);
}); });
}); });
describe('with no data', () => {
beforeEach(() => {
wrapper = createComponent({
initialGetters: {
tasksByTypeChartData: () => ({ groupBy: [], data: [], seriesNames: [] }),
},
});
});
it('does not renders the task by type chart', () => {
expect(findTasksByTypeChart(wrapper).exists()).toBe(false);
});
it('renders the no data available message', () => {
expect(wrapper.text()).toContain('There is no data available. Please change your selection.');
});
});
describe('when a filter is selected', () => { describe('when a filter is selected', () => {
const payload = { const payload = {
filter: TASKS_BY_TYPE_FILTERS.SUBJECT, filter: TASKS_BY_TYPE_FILTERS.SUBJECT,
...@@ -62,15 +109,18 @@ describe('TypeOfWorkCharts', () => { ...@@ -62,15 +109,18 @@ describe('TypeOfWorkCharts', () => {
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
}); });
it('emits the `updateFilter` event', () => { it('calls the setTasksByTypeFilters method', () => {
expect(wrapper.emitted('updateFilter')).toBeDefined(); expect(actionSpies.setTasksByTypeFilters).toHaveBeenCalledWith(payload);
expect(wrapper.emitted('updateFilter')[0]).toEqual([payload]);
}); });
}); });
describe('while loading', () => { describe.each`
stateKey | value
${'isLoadingTasksByTypeChart'} | ${true}
${'isLoadingTasksByTypeChartTopLabels'} | ${true}
`('when $stateKey=$value', ({ stateKey, value }) => {
beforeEach(() => { beforeEach(() => {
wrapper = createComponent({ props: { isLoading: true } }); wrapper = createComponent({ initialState: { [stateKey]: value } });
}); });
it('renders loading icon', () => { it('renders loading icon', () => {
......
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