@eturnity/eturnity_reusable_components 9.28.0 → 9.28.2
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/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/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/toggle/Toggle.stories.js +73 -34
- package/src/components/inputs/toggle/toggle.spec.js +121 -82
- package/src/components/label/label.spec.js +55 -0
- package/src/components/label/label.stories.js +36 -0
- package/src/components/markerItem/markerItem.spec.js +46 -0
- package/src/components/markerItem/markerItem.stories.js +7 -0
- package/src/components/modals/modal/modal.stories.js +17 -7
- package/src/components/navigationTabs/navigationTabs.spec.js +95 -0
- package/src/components/sideMenu/sideMenu.spec.js +109 -0
- package/src/components/sideMenu/sideMenu.stories.js +43 -0
|
@@ -10,7 +10,11 @@ export default {
|
|
|
10
10
|
docs: {
|
|
11
11
|
description: {
|
|
12
12
|
component:
|
|
13
|
-
'Low-level modal shell: backdrop, centered container, optional
|
|
13
|
+
'Low-level modal shell: backdrop, centered container, optional ' +
|
|
14
|
+
'close control, loading state, and a default slot for any body ' +
|
|
15
|
+
'content. Parent handles layout and actions inside the slot. Emits ' +
|
|
16
|
+
'`on-close` when the close button is used or Escape is pressed ' +
|
|
17
|
+
'(if `closeOnEscape` is true).',
|
|
14
18
|
},
|
|
15
19
|
},
|
|
16
20
|
},
|
|
@@ -21,7 +25,8 @@ export default {
|
|
|
21
25
|
},
|
|
22
26
|
isLoading: {
|
|
23
27
|
control: 'boolean',
|
|
24
|
-
description:
|
|
28
|
+
description:
|
|
29
|
+
'Shows a large spinner and hides slot content and close button',
|
|
25
30
|
},
|
|
26
31
|
hideClose: {
|
|
27
32
|
control: 'boolean',
|
|
@@ -55,7 +60,8 @@ export default {
|
|
|
55
60
|
},
|
|
56
61
|
addPaddingTop: {
|
|
57
62
|
control: 'boolean',
|
|
58
|
-
description:
|
|
63
|
+
description:
|
|
64
|
+
'Adds top padding to the overlay (e.g. clear a fixed header)',
|
|
59
65
|
},
|
|
60
66
|
disableDefaultMediaQuery: {
|
|
61
67
|
control: 'boolean',
|
|
@@ -131,7 +137,8 @@ export const WithoutCloseButton = {
|
|
|
131
137
|
parameters: {
|
|
132
138
|
docs: {
|
|
133
139
|
description: {
|
|
134
|
-
story:
|
|
140
|
+
story:
|
|
141
|
+
'Use when the user must complete an action in the slot (e.g. explicit Save/Cancel inside the body).',
|
|
135
142
|
},
|
|
136
143
|
},
|
|
137
144
|
},
|
|
@@ -156,7 +163,8 @@ export const InsetNotFullScreen = {
|
|
|
156
163
|
parameters: {
|
|
157
164
|
docs: {
|
|
158
165
|
description: {
|
|
159
|
-
story:
|
|
166
|
+
story:
|
|
167
|
+
'Overlay does not cover the full viewport; useful when embedding preview areas need visible context.',
|
|
160
168
|
},
|
|
161
169
|
},
|
|
162
170
|
},
|
|
@@ -219,7 +227,8 @@ export const WithTopPadding = {
|
|
|
219
227
|
parameters: {
|
|
220
228
|
docs: {
|
|
221
229
|
description: {
|
|
222
|
-
story:
|
|
230
|
+
story:
|
|
231
|
+
'Extra top padding on the overlay when a fixed app bar occupies the top of the screen.',
|
|
223
232
|
},
|
|
224
233
|
},
|
|
225
234
|
},
|
|
@@ -285,7 +294,8 @@ export const InteractiveOpenAndClose = {
|
|
|
285
294
|
parameters: {
|
|
286
295
|
docs: {
|
|
287
296
|
description: {
|
|
288
|
-
story:
|
|
297
|
+
story:
|
|
298
|
+
'Minimal host page with local `isOpen` state to demonstrate open/close behaviour.',
|
|
289
299
|
},
|
|
290
300
|
},
|
|
291
301
|
},
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import NavigationTabs from '@/components/navigationTabs'
|
|
4
|
+
|
|
5
|
+
// `theme` and `$gettext` are provided globally by jest.setup.js, so specs do
|
|
6
|
+
// not need to re-provide them. See the canonical example at
|
|
7
|
+
// src/components/buttons/closeButton/closeButton.spec.js.
|
|
8
|
+
//
|
|
9
|
+
// The component renders only styled-components (no data-test-id hooks) and must
|
|
10
|
+
// not be changed, so the tab elements are selected structurally: the root
|
|
11
|
+
// wrapper holds one Tab div per item followed by a trailing BottomLine div.
|
|
12
|
+
|
|
13
|
+
jest.mock('@/components/icon/iconCache.mjs', () => ({
|
|
14
|
+
// need to mock this due to how jest handles import.meta
|
|
15
|
+
fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
|
|
16
|
+
}))
|
|
17
|
+
|
|
18
|
+
const tabsData = [
|
|
19
|
+
{ id: 1, slug: 'overview', label: 'Overview' },
|
|
20
|
+
{
|
|
21
|
+
id: 2,
|
|
22
|
+
slug: 'billing',
|
|
23
|
+
label: 'Billing',
|
|
24
|
+
labelInfoText: 'Invoices and payment methods',
|
|
25
|
+
labelInfoAlign: 'left',
|
|
26
|
+
},
|
|
27
|
+
{ id: 3, slug: 'users', label: 'Users', isDisabled: true },
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
describe('NavigationTabs', () => {
|
|
31
|
+
const mountTabs = (props = {}) =>
|
|
32
|
+
mount(NavigationTabs, {
|
|
33
|
+
props: {
|
|
34
|
+
tabsData,
|
|
35
|
+
activeTab: 'billing',
|
|
36
|
+
tabKey: 'slug',
|
|
37
|
+
tabLabel: 'label',
|
|
38
|
+
...props,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// The Tab divs are every direct child of the root except the trailing
|
|
43
|
+
// BottomLine div.
|
|
44
|
+
const getTabs = (wrapper) => {
|
|
45
|
+
const tabEls = Array.from(wrapper.element.children).slice(0, -1)
|
|
46
|
+
return wrapper.findAll('div').filter((d) => tabEls.includes(d.element))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
it('renders one tab per item with its label', () => {
|
|
50
|
+
const wrapper = mountTabs()
|
|
51
|
+
const tabs = getTabs(wrapper)
|
|
52
|
+
expect(tabs).toHaveLength(tabsData.length)
|
|
53
|
+
expect(wrapper.text()).toContain('Overview')
|
|
54
|
+
expect(wrapper.text()).toContain('Billing')
|
|
55
|
+
expect(wrapper.text()).toContain('Users')
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('emits tab-click with the tabKey value when isIndexKey is false', async () => {
|
|
59
|
+
const wrapper = mountTabs()
|
|
60
|
+
const tabs = getTabs(wrapper)
|
|
61
|
+
|
|
62
|
+
await tabs[0].trigger('click')
|
|
63
|
+
await tabs[2].trigger('click')
|
|
64
|
+
|
|
65
|
+
const emitted = wrapper.emitted('tab-click')
|
|
66
|
+
expect(emitted).toHaveLength(2)
|
|
67
|
+
expect(emitted[0]).toEqual(['overview'])
|
|
68
|
+
expect(emitted[1]).toEqual(['users'])
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
it('emits tab-click with the index when isIndexKey is true', async () => {
|
|
72
|
+
const wrapper = mountTabs({ activeTab: 0, isIndexKey: true })
|
|
73
|
+
const tabs = getTabs(wrapper)
|
|
74
|
+
|
|
75
|
+
await tabs[2].trigger('click')
|
|
76
|
+
|
|
77
|
+
const emitted = wrapper.emitted('tab-click')
|
|
78
|
+
expect(emitted).toHaveLength(1)
|
|
79
|
+
expect(emitted[0]).toEqual([2])
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('styles the active tab differently from an inactive tab', () => {
|
|
83
|
+
const wrapper = mountTabs()
|
|
84
|
+
const tabs = getTabs(wrapper)
|
|
85
|
+
// activeTab is 'billing' (index 1); its generated class must differ from an
|
|
86
|
+
// inactive tab's class.
|
|
87
|
+
expect(tabs[1].classes()).not.toEqual(tabs[0].classes())
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('renders info text only for tabs that provide labelInfoText', () => {
|
|
91
|
+
const wrapper = mountTabs()
|
|
92
|
+
const infoTexts = wrapper.findAll('[data-test-id="infoText_container"]')
|
|
93
|
+
expect(infoTexts).toHaveLength(1)
|
|
94
|
+
})
|
|
95
|
+
})
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { mount } from '@vue/test-utils'
|
|
2
|
+
import SideMenu from '@/components/sideMenu'
|
|
3
|
+
import Spinner from '@/components/spinner'
|
|
4
|
+
|
|
5
|
+
jest.mock('@/components/icon/iconCache.mjs', () => ({
|
|
6
|
+
// need to mock this due to how jest handles import.meta
|
|
7
|
+
fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
|
|
8
|
+
}))
|
|
9
|
+
|
|
10
|
+
const tabsData = [
|
|
11
|
+
{ key: 'dashboard', label: 'Dashboard', icon: 'house' },
|
|
12
|
+
{ key: 'projects', label: 'Projects', icon: 'house' },
|
|
13
|
+
{
|
|
14
|
+
key: 'settings',
|
|
15
|
+
label: 'Settings',
|
|
16
|
+
icon: 'gear',
|
|
17
|
+
children: [
|
|
18
|
+
{ key: 'general', label: 'General' },
|
|
19
|
+
{ key: 'billing', label: 'Billing' },
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
const mountMenu = (props = {}) =>
|
|
25
|
+
mount(SideMenu, {
|
|
26
|
+
props: { tabsData, activeTab: 'dashboard', ...props },
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
const item = (wrapper, key) =>
|
|
30
|
+
wrapper.find(`[data-id="sub_menu_settings_${key}"]`)
|
|
31
|
+
|
|
32
|
+
describe('sideMenu/index.vue', () => {
|
|
33
|
+
it('shows a spinner and no menu items while tabsData is empty', () => {
|
|
34
|
+
const wrapper = mount(SideMenu, {
|
|
35
|
+
props: { tabsData: [], activeTab: 'dashboard' },
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
expect(wrapper.findComponent(Spinner).exists()).toBe(true)
|
|
39
|
+
expect(item(wrapper, 'dashboard').exists()).toBe(false)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('renders a menu item with its label for each flat tab', () => {
|
|
43
|
+
const wrapper = mountMenu()
|
|
44
|
+
|
|
45
|
+
expect(item(wrapper, 'dashboard').exists()).toBe(true)
|
|
46
|
+
expect(item(wrapper, 'dashboard').text()).toContain('Dashboard')
|
|
47
|
+
expect(item(wrapper, 'projects').text()).toContain('Projects')
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('marks the active tab with data-active="true"', () => {
|
|
51
|
+
const wrapper = mountMenu({ activeTab: 'projects' })
|
|
52
|
+
|
|
53
|
+
expect(item(wrapper, 'projects').attributes('data-active')).toBe('true')
|
|
54
|
+
expect(item(wrapper, 'dashboard').attributes('data-active')).toBe('false')
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
it('emits tab-click with the activeKey when a flat item is clicked', async () => {
|
|
58
|
+
const wrapper = mountMenu()
|
|
59
|
+
|
|
60
|
+
await item(wrapper, 'projects').trigger('click')
|
|
61
|
+
|
|
62
|
+
expect(wrapper.emitted('tab-click')).toHaveLength(1)
|
|
63
|
+
expect(wrapper.emitted('tab-click')[0]).toEqual([{ activeKey: 'projects' }])
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
it('hides nested children until the parent is expanded', async () => {
|
|
67
|
+
const wrapper = mountMenu()
|
|
68
|
+
|
|
69
|
+
expect(item(wrapper, 'general').exists()).toBe(false)
|
|
70
|
+
|
|
71
|
+
await item(wrapper, 'settings').trigger('click')
|
|
72
|
+
expect(item(wrapper, 'general').exists()).toBe(true)
|
|
73
|
+
expect(item(wrapper, 'billing').exists()).toBe(true)
|
|
74
|
+
|
|
75
|
+
// clicking the parent again collapses it
|
|
76
|
+
await item(wrapper, 'settings').trigger('click')
|
|
77
|
+
expect(item(wrapper, 'general').exists()).toBe(false)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('emits tab-click with activeKey and parentKey when a child is clicked', async () => {
|
|
81
|
+
const wrapper = mountMenu()
|
|
82
|
+
|
|
83
|
+
await item(wrapper, 'settings').trigger('click')
|
|
84
|
+
await item(wrapper, 'general').trigger('click')
|
|
85
|
+
|
|
86
|
+
expect(wrapper.emitted('tab-click')[0]).toEqual([
|
|
87
|
+
{ activeKey: 'general', parentKey: 'settings' },
|
|
88
|
+
])
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
it('renders the logout button and app version, and emits on-logout on click', async () => {
|
|
92
|
+
const wrapper = mountMenu({ hasLogout: true, appVersion: 'v1.2.3' })
|
|
93
|
+
|
|
94
|
+
const logout = wrapper.find('[data-id="button_settings_logout"]')
|
|
95
|
+
expect(logout.exists()).toBe(true)
|
|
96
|
+
expect(wrapper.text()).toContain('v1.2.3')
|
|
97
|
+
|
|
98
|
+
await logout.trigger('click')
|
|
99
|
+
expect(wrapper.emitted('on-logout')).toHaveLength(1)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('hides the logout section when hasLogout is false', () => {
|
|
103
|
+
const wrapper = mountMenu({ hasLogout: false })
|
|
104
|
+
|
|
105
|
+
expect(wrapper.find('[data-id="button_settings_logout"]').exists()).toBe(
|
|
106
|
+
false
|
|
107
|
+
)
|
|
108
|
+
})
|
|
109
|
+
})
|
|
@@ -7,6 +7,19 @@ const flatMenu = [
|
|
|
7
7
|
{ key: 'settings', label: 'Settings', icon: 'House' },
|
|
8
8
|
]
|
|
9
9
|
|
|
10
|
+
const nestedMenu = [
|
|
11
|
+
{ key: 'dashboard', label: 'Dashboard', icon: 'House' },
|
|
12
|
+
{
|
|
13
|
+
key: 'settings',
|
|
14
|
+
label: 'Settings',
|
|
15
|
+
icon: 'House',
|
|
16
|
+
children: [
|
|
17
|
+
{ key: 'general', label: 'General' },
|
|
18
|
+
{ key: 'billing', label: 'Billing' },
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
]
|
|
22
|
+
|
|
10
23
|
export default {
|
|
11
24
|
title: 'Components/Navigation/SideMenu',
|
|
12
25
|
component: SideMenu,
|
|
@@ -51,3 +64,33 @@ SecondActive.args = {
|
|
|
51
64
|
...FlatItems.args,
|
|
52
65
|
activeTab: 'projects',
|
|
53
66
|
}
|
|
67
|
+
|
|
68
|
+
// A collapsible parent item — click "Settings" to expand its children.
|
|
69
|
+
export const NestedItems = Template.bind({})
|
|
70
|
+
NestedItems.args = {
|
|
71
|
+
tabsData: nestedMenu,
|
|
72
|
+
activeTab: 'general',
|
|
73
|
+
activeParentTab: 'settings',
|
|
74
|
+
hasLogout: false,
|
|
75
|
+
appVersion: 'Storybook',
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Bottom section with the logout button and the app version.
|
|
79
|
+
export const WithLogout = Template.bind({})
|
|
80
|
+
WithLogout.args = {
|
|
81
|
+
tabsData: flatMenu,
|
|
82
|
+
activeTab: 'dashboard',
|
|
83
|
+
activeParentTab: null,
|
|
84
|
+
hasLogout: true,
|
|
85
|
+
appVersion: 'v1.2.3',
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// While tabsData is empty the menu shows a loading spinner.
|
|
89
|
+
export const Loading = Template.bind({})
|
|
90
|
+
Loading.args = {
|
|
91
|
+
tabsData: [],
|
|
92
|
+
activeTab: 'dashboard',
|
|
93
|
+
activeParentTab: null,
|
|
94
|
+
hasLogout: false,
|
|
95
|
+
appVersion: 'Storybook',
|
|
96
|
+
}
|