@eturnity/eturnity_reusable_components 9.25.17 → 9.28.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/components/collapsableInfoText/collapsableInfoText.spec.js +57 -0
- package/src/components/collapsableInfoText/collapsableInfoText.stories.js +8 -0
- package/src/components/dropdown/Dropdown.stories.js +9 -0
- package/src/components/dropdown/dropdown.spec.js +99 -26
- package/src/components/dropdown/index.vue +2 -0
- package/src/components/filter/Filter.stories.js +177 -0
- package/src/components/filter/filter.spec.js +183 -0
- package/src/components/inputs/inputNumber/InputNumber.stories.js +87 -103
- package/src/components/inputs/inputNumber/inputNumber.spec.js +133 -0
- package/src/components/inputs/inputNumberQuestion/inputNumberQuestion.spec.js +103 -0
- package/src/components/inputs/inputNumberQuestion/inputNumberQuestion.stories.js +31 -10
- package/src/components/inputs/select/select.spec.js +154 -0
- package/src/components/inputs/select/select.stories.js +230 -56
- package/src/components/inputs/slider/slider.spec.js +59 -0
- package/src/components/inputs/slider/slider.stories.js +4 -2
- package/src/components/inputs/switchField/switchField.spec.js +88 -0
- package/src/components/inputs/textAreaInput/TextAreaInput.stories.js +78 -74
- package/src/components/inputs/textAreaInput/textAreaInput.spec.js +90 -0
- package/src/components/sideMenu/sideMenu.spec.js +109 -0
- package/src/components/sideMenu/sideMenu.stories.js +43 -0
package/package.json
CHANGED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import CollapsableInfoText from '@/components/collapsableInfoText'
|
|
4
|
+
|
|
5
|
+
// `theme` and the `$gettext` mock are provided globally by jest.setup.js.
|
|
6
|
+
// $gettext returns the message unchanged, so the toggle renders the literal
|
|
7
|
+
// "Show more" / "Show less" labels.
|
|
8
|
+
|
|
9
|
+
// The component only renders the toggle once the text is longer than 170 chars.
|
|
10
|
+
const LONG_TEXT = 'A long info text. '.repeat(20) // > 170 chars
|
|
11
|
+
const SHORT_TEXT = 'A short info text.' // <= 170 chars
|
|
12
|
+
|
|
13
|
+
describe('CollapsableInfoText', () => {
|
|
14
|
+
const mountText = (props = {}) =>
|
|
15
|
+
mount(CollapsableInfoText, { props: { text: LONG_TEXT, ...props } })
|
|
16
|
+
|
|
17
|
+
const findButton = (wrapper, label) =>
|
|
18
|
+
wrapper.findAll('p').find((p) => p.text() === label)
|
|
19
|
+
|
|
20
|
+
it('renders the provided text', () => {
|
|
21
|
+
const wrapper = mountText({ text: 'Hello integrators' })
|
|
22
|
+
expect(wrapper.text()).toContain('Hello integrators')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('shows the "Show more" toggle when the text is long', () => {
|
|
26
|
+
const wrapper = mountText({ text: LONG_TEXT })
|
|
27
|
+
expect(findButton(wrapper, 'Show more')).toBeDefined()
|
|
28
|
+
expect(findButton(wrapper, 'Show less')).toBeUndefined()
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('hides the toggle when the text is short', () => {
|
|
32
|
+
const wrapper = mountText({ text: SHORT_TEXT })
|
|
33
|
+
expect(findButton(wrapper, 'Show more')).toBeUndefined()
|
|
34
|
+
expect(findButton(wrapper, 'Show less')).toBeUndefined()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('expands to "Show less" when the toggle is clicked', async () => {
|
|
38
|
+
const wrapper = mountText({ text: LONG_TEXT })
|
|
39
|
+
|
|
40
|
+
await findButton(wrapper, 'Show more').trigger('click')
|
|
41
|
+
|
|
42
|
+
expect(wrapper.vm.showAll).toBe(true)
|
|
43
|
+
expect(findButton(wrapper, 'Show less')).toBeDefined()
|
|
44
|
+
expect(findButton(wrapper, 'Show more')).toBeUndefined()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('collapses back to "Show more" on a second click', async () => {
|
|
48
|
+
const wrapper = mountText({ text: LONG_TEXT })
|
|
49
|
+
|
|
50
|
+
await findButton(wrapper, 'Show more').trigger('click')
|
|
51
|
+
await findButton(wrapper, 'Show less').trigger('click')
|
|
52
|
+
|
|
53
|
+
expect(wrapper.vm.showAll).toBe(false)
|
|
54
|
+
expect(findButton(wrapper, 'Show more')).toBeDefined()
|
|
55
|
+
expect(findButton(wrapper, 'Show less')).toBeUndefined()
|
|
56
|
+
})
|
|
57
|
+
})
|
|
@@ -123,3 +123,12 @@ DropdownBackgroundColor.args = {
|
|
|
123
123
|
...defaultDropdownProps,
|
|
124
124
|
backgroundColor: theme.colors.red,
|
|
125
125
|
}
|
|
126
|
+
|
|
127
|
+
// Long, unbroken option names should wrap inside the dropdown instead of
|
|
128
|
+
// overflowing horizontally (EPDM-11708).
|
|
129
|
+
export const DropdownLongContent = Template.bind({})
|
|
130
|
+
DropdownLongContent.args = {
|
|
131
|
+
...defaultDropdownProps,
|
|
132
|
+
openingMode: 'click',
|
|
133
|
+
dropdown: `<div>ThisIsAnExtremelyLongVariantNameWithoutAnySpacesThatUsedToCauseHorizontalScroll</div><div>Another duplicated variant with a very long descriptive name that keeps going</div>`,
|
|
134
|
+
}
|
|
@@ -8,48 +8,121 @@ jest.mock('@/components/icon/iconCache.mjs', () => ({
|
|
|
8
8
|
fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
|
|
9
9
|
}))
|
|
10
10
|
|
|
11
|
+
const mountDropdown = (props = {}) =>
|
|
12
|
+
mount(RCDropdown, {
|
|
13
|
+
props: { ...defaultProps, ...props },
|
|
14
|
+
slots: { ...defaultProps },
|
|
15
|
+
global: { provide: { theme } },
|
|
16
|
+
})
|
|
17
|
+
|
|
11
18
|
describe('dropdown/index.vue', () => {
|
|
12
|
-
it('
|
|
13
|
-
const wrapper =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
it('renders the trigger and dropdown slots', () => {
|
|
20
|
+
const wrapper = mountDropdown()
|
|
21
|
+
|
|
22
|
+
const trigger = wrapper.find('[data-test-id="dropdown_trigger"]')
|
|
23
|
+
const content = wrapper.find('[data-test-id="dropdown_dropdown_content"]')
|
|
24
|
+
|
|
25
|
+
expect(trigger.exists()).toBe(true)
|
|
26
|
+
expect(trigger.text()).toContain(defaultProps.trigger)
|
|
27
|
+
expect(content.html()).toContain('DROPDOWN OPTION 1')
|
|
28
|
+
expect(content.html()).toContain('DROPDOWN OPTION 2')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('opens on click only when opening-mode is "click"', async () => {
|
|
32
|
+
const wrapper = mountDropdown({ openingMode: 'click' })
|
|
33
|
+
|
|
34
|
+
const trigger = wrapper.find('[data-test-id="dropdown_trigger"]')
|
|
35
|
+
const dropdownWrapper = wrapper.find(
|
|
36
|
+
'[data-test-id="dropdown_dropdown_wrapper"]'
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
expect(dropdownWrapper.classes('openDropdown')).toBe(false)
|
|
40
|
+
|
|
41
|
+
await trigger.trigger('hover')
|
|
42
|
+
expect(dropdownWrapper.classes('openDropdown')).toBe(false)
|
|
43
|
+
|
|
44
|
+
await trigger.trigger('click')
|
|
45
|
+
expect(dropdownWrapper.classes('openDropdown')).toBe(true)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('does not apply the openDropdown class in hover mode', async () => {
|
|
49
|
+
const wrapper = mountDropdown({ openingMode: 'hover' })
|
|
50
|
+
|
|
51
|
+
const trigger = wrapper.find('[data-test-id="dropdown_trigger"]')
|
|
52
|
+
const dropdownWrapper = wrapper.find(
|
|
53
|
+
'[data-test-id="dropdown_dropdown_wrapper"]'
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
await trigger.trigger('click')
|
|
57
|
+
|
|
58
|
+
expect(dropdownWrapper.classes('openDropdown')).toBe(false)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('toggles open and closed on repeated clicks', async () => {
|
|
62
|
+
const wrapper = mountDropdown({ openingMode: 'click' })
|
|
63
|
+
|
|
64
|
+
const trigger = wrapper.find('[data-test-id="dropdown_trigger"]')
|
|
65
|
+
const dropdownWrapper = wrapper.find(
|
|
66
|
+
'[data-test-id="dropdown_dropdown_wrapper"]'
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
await trigger.trigger('click')
|
|
70
|
+
expect(dropdownWrapper.classes('openDropdown')).toBe(true)
|
|
22
71
|
|
|
23
|
-
|
|
72
|
+
await trigger.trigger('click')
|
|
73
|
+
expect(dropdownWrapper.classes('openDropdown')).toBe(false)
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
it('emits on-dropdown-toggle with the new open state', async () => {
|
|
77
|
+
const wrapper = mountDropdown({ openingMode: 'click' })
|
|
78
|
+
|
|
79
|
+
const trigger = wrapper.find('[data-test-id="dropdown_trigger"]')
|
|
24
80
|
|
|
25
|
-
|
|
26
|
-
|
|
81
|
+
await trigger.trigger('click')
|
|
82
|
+
await trigger.trigger('click')
|
|
83
|
+
|
|
84
|
+
expect(wrapper.emitted('on-dropdown-toggle')).toHaveLength(2)
|
|
85
|
+
expect(wrapper.emitted('on-dropdown-toggle')[0]).toEqual([true])
|
|
86
|
+
expect(wrapper.emitted('on-dropdown-toggle')[1]).toEqual([false])
|
|
27
87
|
})
|
|
28
88
|
|
|
29
|
-
it('
|
|
89
|
+
it('closes when clicking outside in click mode', async () => {
|
|
90
|
+
// attachTo so the mounted click-outside listener sees a connected element
|
|
30
91
|
const wrapper = mount(RCDropdown, {
|
|
31
92
|
props: { ...defaultProps, openingMode: 'click' },
|
|
32
93
|
slots: { ...defaultProps },
|
|
33
|
-
global: {
|
|
34
|
-
|
|
35
|
-
theme,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
94
|
+
global: { provide: { theme } },
|
|
95
|
+
attachTo: document.body,
|
|
38
96
|
})
|
|
39
97
|
|
|
40
|
-
const
|
|
41
|
-
const
|
|
98
|
+
const trigger = wrapper.find('[data-test-id="dropdown_trigger"]')
|
|
99
|
+
const dropdownWrapper = wrapper.find(
|
|
42
100
|
'[data-test-id="dropdown_dropdown_wrapper"]'
|
|
43
101
|
)
|
|
44
102
|
|
|
45
|
-
|
|
103
|
+
await trigger.trigger('click')
|
|
104
|
+
expect(dropdownWrapper.classes('openDropdown')).toBe(true)
|
|
46
105
|
|
|
47
|
-
|
|
106
|
+
document.body.click()
|
|
107
|
+
await wrapper.vm.$nextTick()
|
|
48
108
|
|
|
49
|
-
expect(
|
|
109
|
+
expect(dropdownWrapper.classes('openDropdown')).toBe(false)
|
|
110
|
+
wrapper.unmount()
|
|
111
|
+
})
|
|
50
112
|
|
|
51
|
-
|
|
113
|
+
it('renders long unbroken content inside the dropdown window (EPDM-11708)', () => {
|
|
114
|
+
const longName =
|
|
115
|
+
'ThisIsAnExtremelyLongVariantNameWithoutAnySpacesThatUsedToCauseHorizontalScroll'
|
|
116
|
+
const wrapper = mount(RCDropdown, {
|
|
117
|
+
props: { ...defaultProps },
|
|
118
|
+
slots: {
|
|
119
|
+
trigger: defaultProps.trigger,
|
|
120
|
+
dropdown: `<div>${longName}</div>`,
|
|
121
|
+
},
|
|
122
|
+
global: { provide: { theme } },
|
|
123
|
+
})
|
|
52
124
|
|
|
53
|
-
|
|
125
|
+
const content = wrapper.find('[data-test-id="dropdown_dropdown_content"]')
|
|
126
|
+
expect(content.text()).toContain(longName)
|
|
54
127
|
})
|
|
55
128
|
})
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import FilterComponent from './index.vue'
|
|
2
|
+
import theme from '@/assets/theme'
|
|
3
|
+
|
|
4
|
+
// To use:
|
|
5
|
+
// import Filter from '@eturnity/eturnity_reusable_components/src/components/filter'
|
|
6
|
+
// <Filter
|
|
7
|
+
// :filter-data="filterData"
|
|
8
|
+
// :filter-views="filterViews"
|
|
9
|
+
// :button-text="buttonText"
|
|
10
|
+
// active-view="Default view"
|
|
11
|
+
// @on-filter-settings-change="..."
|
|
12
|
+
// @on-reset-filters="..."
|
|
13
|
+
// />
|
|
14
|
+
|
|
15
|
+
// The panel (`FilterSettings`) is data-driven: each `filterData` entry is a
|
|
16
|
+
// column of type `columns` (draggable visibility checkboxes) or `filter`
|
|
17
|
+
// (selectable / range filters). `filter_type` drives which control renders.
|
|
18
|
+
const FILTER_DATA = [
|
|
19
|
+
{
|
|
20
|
+
type: 'columns',
|
|
21
|
+
columnName: 'Columns',
|
|
22
|
+
dataOptions: [
|
|
23
|
+
{ choice: 'name', text: 'Name', selected: true },
|
|
24
|
+
{ choice: 'status', text: 'Status', selected: true },
|
|
25
|
+
{ choice: 'created', text: 'Created', selected: false },
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
type: 'filter',
|
|
30
|
+
columnName: 'Filters',
|
|
31
|
+
dataOptions: [
|
|
32
|
+
{
|
|
33
|
+
field: 'status',
|
|
34
|
+
label: 'Status',
|
|
35
|
+
filter_type: 'multi_select_string',
|
|
36
|
+
dataId: 'filter_status',
|
|
37
|
+
selectedText: 'All',
|
|
38
|
+
choices: [
|
|
39
|
+
{ text: 'Active', choice: 'active', selected: true },
|
|
40
|
+
{ text: 'Archived', choice: 'archived', selected: false },
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
field: 'is_favourite',
|
|
45
|
+
label: 'Favourite',
|
|
46
|
+
filter_type: 'boolean',
|
|
47
|
+
dataId: 'filter_favourite',
|
|
48
|
+
selectedText: 'Any',
|
|
49
|
+
choices: [
|
|
50
|
+
{ text: 'Yes', choice: true, selected: false },
|
|
51
|
+
{ text: 'No', choice: false, selected: false },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
field: 'power',
|
|
56
|
+
label: 'Power (kW)',
|
|
57
|
+
filter_type: 'integer_range',
|
|
58
|
+
dataId: 'filter_power',
|
|
59
|
+
selectedText: '',
|
|
60
|
+
unit: 'kW',
|
|
61
|
+
choices: [],
|
|
62
|
+
range: { start: null, end: null },
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
},
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
const FILTER_VIEWS = [{ name: 'Default view' }, { name: 'My projects' }]
|
|
69
|
+
|
|
70
|
+
const BUTTON_TEXT = {
|
|
71
|
+
apply_view: 'Apply view',
|
|
72
|
+
save_view: 'Save view',
|
|
73
|
+
cancel: 'Cancel',
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Two applied filters → the trigger badge shows "2".
|
|
77
|
+
const CURRENT_FILTER_DATA = {
|
|
78
|
+
filters: [{ field: 'status' }, { field: 'is_favourite' }],
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export default {
|
|
82
|
+
title: 'Components/Filter/Filter',
|
|
83
|
+
component: FilterComponent,
|
|
84
|
+
tags: ['autodocs'],
|
|
85
|
+
parameters: {
|
|
86
|
+
docs: {
|
|
87
|
+
description: {
|
|
88
|
+
component:
|
|
89
|
+
'Dropdown filter built from a `ParentDropdown` trigger and a ' +
|
|
90
|
+
'`FilterSettings` panel. Click the trigger to open the panel; the ' +
|
|
91
|
+
'badge shows how many filters are currently applied.',
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
argTypes: {
|
|
96
|
+
dropdownText: { control: 'text' },
|
|
97
|
+
componentName: { control: 'text' },
|
|
98
|
+
showActiveFilter: { control: 'boolean' },
|
|
99
|
+
activeView: { control: 'text' },
|
|
100
|
+
filterData: { control: false },
|
|
101
|
+
filterViews: { control: false },
|
|
102
|
+
buttonText: { control: false },
|
|
103
|
+
currentFilterData: { control: false },
|
|
104
|
+
},
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const Template = (args) => ({
|
|
108
|
+
components: { FilterComponent },
|
|
109
|
+
setup() {
|
|
110
|
+
return { args }
|
|
111
|
+
},
|
|
112
|
+
template: `
|
|
113
|
+
<div data-test-id="filter-story" style="padding: 24px; min-height: 360px;">
|
|
114
|
+
<FilterComponent v-bind="args" />
|
|
115
|
+
</div>
|
|
116
|
+
`,
|
|
117
|
+
provide: { theme },
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
export const Default = Template.bind({})
|
|
121
|
+
Default.args = {
|
|
122
|
+
componentName: 'demo',
|
|
123
|
+
dropdownText: 'Default view',
|
|
124
|
+
filterData: FILTER_DATA,
|
|
125
|
+
filterViews: FILTER_VIEWS,
|
|
126
|
+
buttonText: BUTTON_TEXT,
|
|
127
|
+
activeView: 'Default view',
|
|
128
|
+
showActiveFilter: true,
|
|
129
|
+
currentFilterData: CURRENT_FILTER_DATA,
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Without any saved views the top section (active-view select + reset) is
|
|
134
|
+
* hidden and only the filter columns are shown.
|
|
135
|
+
*/
|
|
136
|
+
export const NoSavedViews = Template.bind({})
|
|
137
|
+
NoSavedViews.args = {
|
|
138
|
+
componentName: 'demo',
|
|
139
|
+
dropdownText: 'Filters',
|
|
140
|
+
filterData: FILTER_DATA,
|
|
141
|
+
filterViews: [],
|
|
142
|
+
buttonText: BUTTON_TEXT,
|
|
143
|
+
activeView: null,
|
|
144
|
+
showActiveFilter: true,
|
|
145
|
+
currentFilterData: null,
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* `showActiveFilter: false` hides the active-view select while keeping the
|
|
150
|
+
* reset action.
|
|
151
|
+
*/
|
|
152
|
+
export const WithoutActiveFilterSelect = Template.bind({})
|
|
153
|
+
WithoutActiveFilterSelect.args = {
|
|
154
|
+
componentName: 'demo',
|
|
155
|
+
dropdownText: 'Default view',
|
|
156
|
+
filterData: FILTER_DATA,
|
|
157
|
+
filterViews: FILTER_VIEWS,
|
|
158
|
+
buttonText: BUTTON_TEXT,
|
|
159
|
+
activeView: 'Default view',
|
|
160
|
+
showActiveFilter: false,
|
|
161
|
+
currentFilterData: CURRENT_FILTER_DATA,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* A custom trigger label and a single applied filter (badge shows "1").
|
|
166
|
+
*/
|
|
167
|
+
export const CustomTriggerText = Template.bind({})
|
|
168
|
+
CustomTriggerText.args = {
|
|
169
|
+
componentName: 'demo',
|
|
170
|
+
dropdownText: 'Project filters',
|
|
171
|
+
filterData: FILTER_DATA,
|
|
172
|
+
filterViews: FILTER_VIEWS,
|
|
173
|
+
buttonText: BUTTON_TEXT,
|
|
174
|
+
activeView: 'Default view',
|
|
175
|
+
showActiveFilter: true,
|
|
176
|
+
currentFilterData: { filters: [{ field: 'status' }] },
|
|
177
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { flushPromises, mount } from '@vue/test-utils'
|
|
3
|
+
import FilterComponent from '@/components/filter'
|
|
4
|
+
|
|
5
|
+
// The real children pull in heavy deps (date picker, draggable, teleported
|
|
6
|
+
// selects, icon cache) that can't run under Jest. Stub them so we can test the
|
|
7
|
+
// host component's own contract: the applied-filters badge, open/close
|
|
8
|
+
// behaviour, and the emit wiring between the panel and the parent.
|
|
9
|
+
jest.mock('@/components/filter/parentDropdown', () => ({
|
|
10
|
+
__esModule: true,
|
|
11
|
+
default: {
|
|
12
|
+
name: 'ParentDropdown',
|
|
13
|
+
props: ['componentName', 'dropdownText', 'isOpen', 'numberOfFiltersApplied'],
|
|
14
|
+
emits: ['on-toggle'],
|
|
15
|
+
template:
|
|
16
|
+
'<button data-test-id="parent-dropdown" @click="$emit(\'on-toggle\')">{{ numberOfFiltersApplied }}</button>',
|
|
17
|
+
},
|
|
18
|
+
}))
|
|
19
|
+
jest.mock('@/components/filter/filterSettings', () => ({
|
|
20
|
+
__esModule: true,
|
|
21
|
+
default: {
|
|
22
|
+
name: 'FilterSettings',
|
|
23
|
+
template: '<div data-test-id="filter-settings"></div>',
|
|
24
|
+
},
|
|
25
|
+
}))
|
|
26
|
+
|
|
27
|
+
// requestIdleCallback is absent in jsdom; stub it as a no-op so the panel only
|
|
28
|
+
// mounts on an explicit toggle (no background prewarm timers in tests).
|
|
29
|
+
beforeAll(() => {
|
|
30
|
+
global.requestIdleCallback = jest.fn()
|
|
31
|
+
global.cancelIdleCallback = jest.fn()
|
|
32
|
+
})
|
|
33
|
+
afterAll(() => {
|
|
34
|
+
delete global.requestIdleCallback
|
|
35
|
+
delete global.cancelIdleCallback
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const baseProps = { filterData: [], filterViews: [] }
|
|
39
|
+
|
|
40
|
+
const mountFilter = (props = {}) =>
|
|
41
|
+
mount(FilterComponent, { props: { ...baseProps, ...props } })
|
|
42
|
+
|
|
43
|
+
const parentDropdown = (wrapper) =>
|
|
44
|
+
wrapper.findComponent({ name: 'ParentDropdown' })
|
|
45
|
+
const filterSettings = (wrapper) =>
|
|
46
|
+
wrapper.findComponent({ name: 'FilterSettings' })
|
|
47
|
+
|
|
48
|
+
const badge = (props) =>
|
|
49
|
+
parentDropdown(mountFilter(props)).props('numberOfFiltersApplied')
|
|
50
|
+
|
|
51
|
+
describe('FilterComponent', () => {
|
|
52
|
+
describe('numberOfFiltersApplied badge', () => {
|
|
53
|
+
it('counts live custom_view_filter rows', () => {
|
|
54
|
+
expect(
|
|
55
|
+
badge({
|
|
56
|
+
currentFilterData: {
|
|
57
|
+
filters: [
|
|
58
|
+
{ custom_view_filter: true },
|
|
59
|
+
{ custom_view_filter: false },
|
|
60
|
+
{ custom_view_filter: true },
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
).toBe(2)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
it('counts distinct fields for flat filter rows', () => {
|
|
68
|
+
expect(
|
|
69
|
+
badge({
|
|
70
|
+
currentFilterData: {
|
|
71
|
+
filters: [{ field: 'a' }, { field: 'a' }, { field: 'b' }],
|
|
72
|
+
},
|
|
73
|
+
})
|
|
74
|
+
).toBe(2)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('falls back to the row count for plain value filters', () => {
|
|
78
|
+
expect(
|
|
79
|
+
badge({ currentFilterData: { filters: ['x', 'y', 'z'] } })
|
|
80
|
+
).toBe(3)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('falls back to the active view filters when no live data is present', () => {
|
|
84
|
+
expect(
|
|
85
|
+
badge({
|
|
86
|
+
currentFilterData: null,
|
|
87
|
+
activeFilterView: {
|
|
88
|
+
filters: [{ field: 'x' }, { field: 'y' }],
|
|
89
|
+
columns: ['a', 'b'],
|
|
90
|
+
},
|
|
91
|
+
})
|
|
92
|
+
).toBe(2)
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
it('counts custom_view_filter columns of the active view when present', () => {
|
|
96
|
+
expect(
|
|
97
|
+
badge({
|
|
98
|
+
currentFilterData: null,
|
|
99
|
+
activeFilterView: {
|
|
100
|
+
filters: [{ field: 'x' }],
|
|
101
|
+
columns: [
|
|
102
|
+
{ custom_view_filter: true },
|
|
103
|
+
{ custom_view_filter: true },
|
|
104
|
+
{ custom_view_filter: false },
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
})
|
|
108
|
+
).toBe(2)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('is zero with no filters applied', () => {
|
|
112
|
+
expect(badge({})).toBe(0)
|
|
113
|
+
})
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
describe('open / close behaviour', () => {
|
|
117
|
+
it('mounts the panel and emits on-filter-panel-open when opened', async () => {
|
|
118
|
+
const wrapper = mountFilter()
|
|
119
|
+
expect(filterSettings(wrapper).exists()).toBe(false)
|
|
120
|
+
|
|
121
|
+
await parentDropdown(wrapper).vm.$emit('on-toggle')
|
|
122
|
+
await flushPromises()
|
|
123
|
+
|
|
124
|
+
expect(filterSettings(wrapper).exists()).toBe(true)
|
|
125
|
+
expect(filterSettings(wrapper).isVisible()).toBe(true)
|
|
126
|
+
expect(wrapper.emitted('on-filter-panel-open')).toHaveLength(1)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('keeps the panel mounted but hidden when toggled closed', async () => {
|
|
130
|
+
const wrapper = mountFilter()
|
|
131
|
+
await parentDropdown(wrapper).vm.$emit('on-toggle')
|
|
132
|
+
await parentDropdown(wrapper).vm.$emit('on-toggle')
|
|
133
|
+
|
|
134
|
+
expect(filterSettings(wrapper).exists()).toBe(true)
|
|
135
|
+
expect(filterSettings(wrapper).isVisible()).toBe(false)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('closes when the closeDropdown prop becomes true', async () => {
|
|
139
|
+
const wrapper = mountFilter()
|
|
140
|
+
await parentDropdown(wrapper).vm.$emit('on-toggle')
|
|
141
|
+
expect(wrapper.vm.isDropdownOpen).toBe(true)
|
|
142
|
+
|
|
143
|
+
await wrapper.setProps({ closeDropdown: true })
|
|
144
|
+
expect(wrapper.vm.isDropdownOpen).toBe(false)
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('panel event wiring', () => {
|
|
149
|
+
const openWrapper = async () => {
|
|
150
|
+
const wrapper = mountFilter()
|
|
151
|
+
await parentDropdown(wrapper).vm.$emit('on-toggle')
|
|
152
|
+
return wrapper
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
it('re-emits a view selection and closes the dropdown', async () => {
|
|
156
|
+
const wrapper = await openWrapper()
|
|
157
|
+
const view = { name: 'My view' }
|
|
158
|
+
|
|
159
|
+
await filterSettings(wrapper).vm.$emit('on-view-select', view)
|
|
160
|
+
|
|
161
|
+
expect(wrapper.emitted('on-filter-view-select')[0]).toEqual([view])
|
|
162
|
+
expect(wrapper.vm.isDropdownOpen).toBe(false)
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('re-emits a reset and closes the dropdown', async () => {
|
|
166
|
+
const wrapper = await openWrapper()
|
|
167
|
+
|
|
168
|
+
await filterSettings(wrapper).vm.$emit('on-reset-filters')
|
|
169
|
+
|
|
170
|
+
expect(wrapper.emitted('on-reset-filters')).toHaveLength(1)
|
|
171
|
+
expect(wrapper.vm.isDropdownOpen).toBe(false)
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
it('forwards filter changes as on-filter-settings-change', async () => {
|
|
175
|
+
const wrapper = await openWrapper()
|
|
176
|
+
const change = { field: 'status', value: true }
|
|
177
|
+
|
|
178
|
+
await filterSettings(wrapper).vm.$emit('on-filter-change', change)
|
|
179
|
+
|
|
180
|
+
expect(wrapper.emitted('on-filter-settings-change')[0]).toEqual([change])
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
})
|