@eturnity/eturnity_reusable_components 9.28.1 → 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/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/package.json
CHANGED
|
@@ -1,77 +1,116 @@
|
|
|
1
|
+
import { ref } from 'vue'
|
|
1
2
|
import Toggle from './index.vue'
|
|
3
|
+
import theme from '@/assets/theme'
|
|
4
|
+
|
|
5
|
+
// import Toggle from "@eturnity/eturnity_reusable_components/src/components/inputs/toggle"
|
|
6
|
+
// To use:
|
|
7
|
+
// <toggle
|
|
8
|
+
// @on-toggle-change="onInputChange($event)"
|
|
9
|
+
// :isChecked="toggleValue" // Boolean
|
|
10
|
+
// label="My Label Text"
|
|
11
|
+
// toggleColor="red"
|
|
12
|
+
// size="small" // small, medium
|
|
13
|
+
// backgroundColor="blue"
|
|
14
|
+
// labelAlign="right"
|
|
15
|
+
// fontColor="black"
|
|
16
|
+
// :disabled="true"
|
|
17
|
+
// infoTextMessage="My info message"
|
|
18
|
+
// />
|
|
2
19
|
|
|
3
20
|
export default {
|
|
4
21
|
title: 'Components/Inputs/Toggle',
|
|
5
22
|
component: Toggle,
|
|
6
|
-
|
|
23
|
+
tags: ['autodocs'],
|
|
24
|
+
parameters: {
|
|
25
|
+
layout: 'centered',
|
|
26
|
+
},
|
|
27
|
+
argTypes: {
|
|
28
|
+
isChecked: { control: 'boolean' },
|
|
29
|
+
label: { control: 'text' },
|
|
30
|
+
size: { control: 'select', options: ['small', 'medium'] },
|
|
31
|
+
labelAlign: { control: 'select', options: ['left', 'right'] },
|
|
32
|
+
toggleColor: { control: 'color' },
|
|
33
|
+
backgroundColor: { control: 'color' },
|
|
34
|
+
fontColor: { control: 'color' },
|
|
35
|
+
disabled: { control: 'boolean' },
|
|
36
|
+
isRequiredLabel: { control: 'boolean' },
|
|
37
|
+
infoTextMessage: { control: 'text' },
|
|
38
|
+
},
|
|
7
39
|
}
|
|
8
40
|
|
|
9
|
-
const Template = (args
|
|
10
|
-
// Components used in your story `template` are defined in the `components` object
|
|
41
|
+
const Template = (args) => ({
|
|
11
42
|
components: { Toggle },
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
43
|
+
setup() {
|
|
44
|
+
const isChecked = ref(args.isChecked)
|
|
45
|
+
return { args, isChecked }
|
|
46
|
+
},
|
|
47
|
+
template: `
|
|
48
|
+
<div data-test-id="toggle-story" style="padding: 16px; min-width: 240px;">
|
|
49
|
+
<Toggle
|
|
50
|
+
v-bind="args"
|
|
51
|
+
:isChecked="isChecked"
|
|
52
|
+
@on-toggle-change="isChecked = $event"
|
|
53
|
+
/>
|
|
54
|
+
<p style="margin-top: 12px; font-size: 12px; color: #666;">isChecked: {{ isChecked }}</p>
|
|
55
|
+
</div>
|
|
56
|
+
`,
|
|
57
|
+
provide: {
|
|
58
|
+
theme,
|
|
59
|
+
},
|
|
29
60
|
})
|
|
30
61
|
|
|
31
62
|
export const Default = Template.bind({})
|
|
32
63
|
Default.args = {
|
|
33
64
|
isChecked: true,
|
|
34
|
-
size: 'small',
|
|
65
|
+
size: 'small',
|
|
35
66
|
disabled: false,
|
|
36
67
|
}
|
|
37
68
|
|
|
38
69
|
export const Medium = Template.bind({})
|
|
39
70
|
Medium.args = {
|
|
40
|
-
|
|
41
|
-
size: 'medium',
|
|
42
|
-
disabled: false,
|
|
71
|
+
...Default.args,
|
|
72
|
+
size: 'medium',
|
|
43
73
|
}
|
|
44
74
|
|
|
45
75
|
export const CustomColor = Template.bind({})
|
|
46
76
|
CustomColor.args = {
|
|
47
|
-
|
|
48
|
-
size: 'small', // "small" or "medium"
|
|
49
|
-
disabled: false,
|
|
77
|
+
...Default.args,
|
|
50
78
|
toggleColor: 'red',
|
|
51
79
|
backgroundColor: 'blue',
|
|
52
80
|
}
|
|
53
81
|
|
|
54
82
|
export const LabelLeft = Template.bind({})
|
|
55
83
|
LabelLeft.args = {
|
|
56
|
-
|
|
57
|
-
size: 'small', // "small" or "medium"
|
|
84
|
+
...Default.args,
|
|
58
85
|
label: 'My Label Text',
|
|
59
|
-
disabled: false,
|
|
60
86
|
labelAlign: 'left',
|
|
61
87
|
}
|
|
62
88
|
|
|
63
89
|
export const LabelRight = Template.bind({})
|
|
64
90
|
LabelRight.args = {
|
|
65
|
-
|
|
66
|
-
size: 'small', // "small" or "medium"
|
|
91
|
+
...Default.args,
|
|
67
92
|
label: 'My Label Text',
|
|
68
|
-
disabled: false,
|
|
69
93
|
labelAlign: 'right',
|
|
70
94
|
}
|
|
71
95
|
|
|
96
|
+
export const WithInfoText = Template.bind({})
|
|
97
|
+
WithInfoText.args = {
|
|
98
|
+
...Default.args,
|
|
99
|
+
label: 'My Label Text',
|
|
100
|
+
infoTextMessage: 'Applies to this location only.',
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export const Required = Template.bind({})
|
|
104
|
+
Required.args = {
|
|
105
|
+
...Default.args,
|
|
106
|
+
label: 'My Label Text',
|
|
107
|
+
labelAlign: 'left',
|
|
108
|
+
isRequiredLabel: true,
|
|
109
|
+
}
|
|
110
|
+
|
|
72
111
|
export const Disabled = Template.bind({})
|
|
73
112
|
Disabled.args = {
|
|
113
|
+
...Default.args,
|
|
74
114
|
isChecked: false,
|
|
75
|
-
size: 'small', // "small" or "medium"
|
|
76
115
|
disabled: true,
|
|
77
116
|
}
|
|
@@ -1,102 +1,141 @@
|
|
|
1
1
|
/* eslint-disable */
|
|
2
2
|
import { mount } from '@vue/test-utils'
|
|
3
3
|
import RCToggle from '@/components/inputs/toggle'
|
|
4
|
-
|
|
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.
|
|
5
8
|
|
|
6
9
|
jest.mock('@/components/icon/iconCache.mjs', () => ({
|
|
7
10
|
// need to mock this due to how jest handles import.meta
|
|
8
11
|
fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
|
|
9
12
|
}))
|
|
10
13
|
|
|
11
|
-
describe('RCToggle
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
props: { isChecked: true, label: 'Test label', disabled: false },
|
|
15
|
-
global: {
|
|
16
|
-
provide: {
|
|
17
|
-
theme,
|
|
18
|
-
},
|
|
19
|
-
},
|
|
20
|
-
})
|
|
21
|
-
const toggleWrapper = wrapper.find('[data-test-id="toggle_wrapper"]')
|
|
14
|
+
describe('RCToggle', () => {
|
|
15
|
+
const mountToggle = (props = {}) =>
|
|
16
|
+
mount(RCToggle, { props: { isChecked: false, ...props } })
|
|
22
17
|
|
|
23
|
-
|
|
18
|
+
it('renders the toggle and reflects the checked state', () => {
|
|
19
|
+
const wrapper = mountToggle({ isChecked: true })
|
|
20
|
+
const toggleWrapper = wrapper.find('[data-test-id="toggle_wrapper"]')
|
|
24
21
|
expect(toggleWrapper.exists()).toBe(true)
|
|
25
22
|
expect(toggleWrapper.attributes('checked')).toBe('true')
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('reflects the unchecked state', () => {
|
|
26
|
+
const wrapper = mountToggle({ isChecked: false })
|
|
27
|
+
const toggleWrapper = wrapper.find('[data-test-id="toggle_wrapper"]')
|
|
28
|
+
expect(toggleWrapper.attributes('checked')).toBe('false')
|
|
29
|
+
})
|
|
26
30
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
it('emits on-toggle-change with the negated value when clicked', async () => {
|
|
32
|
+
const wrapper = mountToggle({ isChecked: true })
|
|
33
|
+
await wrapper.find('[data-test-id="page_wrapper"]').trigger('click')
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
expect(
|
|
35
|
+
const emitted = wrapper.emitted('on-toggle-change')
|
|
36
|
+
expect(emitted).toHaveLength(1)
|
|
37
|
+
expect(emitted[0]).toEqual([false])
|
|
38
|
+
})
|
|
33
39
|
|
|
34
|
-
|
|
40
|
+
it('emits on-toggle-change when the space key is pressed', async () => {
|
|
41
|
+
const wrapper = mountToggle({ isChecked: false })
|
|
42
|
+
await wrapper
|
|
43
|
+
.find('[data-test-id="toggle_wrapper"]')
|
|
44
|
+
.trigger('keydown.space')
|
|
45
|
+
|
|
46
|
+
const emitted = wrapper.emitted('on-toggle-change')
|
|
47
|
+
expect(emitted).toHaveLength(1)
|
|
48
|
+
expect(emitted[0]).toEqual([true])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('does not emit when disabled and clicked', async () => {
|
|
52
|
+
const wrapper = mountToggle({ isChecked: false, disabled: true })
|
|
53
|
+
await wrapper.find('[data-test-id="page_wrapper"]').trigger('click')
|
|
54
|
+
|
|
55
|
+
expect(wrapper.emitted('on-toggle-change')).toBeUndefined()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('toggles back and forth across successive clicks', async () => {
|
|
59
|
+
const wrapper = mountToggle({ isChecked: false })
|
|
35
60
|
const pageWrapper = wrapper.find('[data-test-id="page_wrapper"]')
|
|
61
|
+
|
|
36
62
|
await pageWrapper.trigger('click')
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
expect(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
63
|
+
await wrapper.setProps({ isChecked: true })
|
|
64
|
+
await pageWrapper.trigger('click')
|
|
65
|
+
await wrapper.setProps({ isChecked: false })
|
|
66
|
+
await pageWrapper.trigger('click')
|
|
67
|
+
|
|
68
|
+
const emitted = wrapper.emitted('on-toggle-change')
|
|
69
|
+
expect(emitted).toHaveLength(3)
|
|
70
|
+
expect(emitted[0]).toEqual([true])
|
|
71
|
+
expect(emitted[1]).toEqual([false])
|
|
72
|
+
expect(emitted[2]).toEqual([true])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('renders no label container when no label is provided', () => {
|
|
76
|
+
const wrapper = mountToggle()
|
|
77
|
+
expect(wrapper.find('[data-test-id="label_left_container"]').exists()).toBe(
|
|
78
|
+
false
|
|
79
|
+
)
|
|
80
|
+
expect(wrapper.find('[data-test-id="label_right_container"]').exists()).toBe(
|
|
81
|
+
false
|
|
82
|
+
)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('renders the label on the right by default', () => {
|
|
86
|
+
const wrapper = mountToggle({ label: 'My Label' })
|
|
87
|
+
expect(wrapper.find('[data-test-id="label_right_container"]').exists()).toBe(
|
|
88
|
+
true
|
|
89
|
+
)
|
|
90
|
+
expect(wrapper.find('[data-test-id="label_left_container"]').exists()).toBe(
|
|
91
|
+
false
|
|
92
|
+
)
|
|
93
|
+
expect(wrapper.text()).toContain('My Label')
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('renders the label on the left when labelAlign is "left"', () => {
|
|
97
|
+
const wrapper = mountToggle({ label: 'My Label', labelAlign: 'left' })
|
|
98
|
+
expect(wrapper.find('[data-test-id="label_left_container"]').exists()).toBe(
|
|
99
|
+
true
|
|
100
|
+
)
|
|
101
|
+
expect(wrapper.find('[data-test-id="label_right_container"]').exists()).toBe(
|
|
102
|
+
false
|
|
103
|
+
)
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
it('renders the info text only when infoTextMessage is provided', () => {
|
|
107
|
+
expect(
|
|
108
|
+
mountToggle({ label: 'My Label', labelAlign: 'left' })
|
|
109
|
+
.find('[data-test-id="infoText_container"]')
|
|
110
|
+
.exists()
|
|
111
|
+
).toBe(false)
|
|
112
|
+
|
|
113
|
+
expect(
|
|
114
|
+
mountToggle({
|
|
115
|
+
label: 'My Label',
|
|
116
|
+
labelAlign: 'left',
|
|
117
|
+
infoTextMessage: 'Some info',
|
|
75
118
|
})
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
expect(emittedEvent[0]).toEqual([true]) // Check the payload of the first click
|
|
87
|
-
await wrapper.setProps({ isChecked: true }) // manually update the props
|
|
88
|
-
|
|
89
|
-
// Trigger click again and check events
|
|
90
|
-
await pageWrapper.trigger('click')
|
|
91
|
-
emittedEvent = wrapper.emitted('on-toggle-change')
|
|
92
|
-
expect(emittedEvent).toHaveLength(2) // Ensure that the event count has increased
|
|
93
|
-
expect(emittedEvent[1]).toEqual([false]) // Check the payload of the second click
|
|
94
|
-
await wrapper.setProps({ isChecked: false }) // manually update the props
|
|
95
|
-
|
|
96
|
-
// Trigger click again and check events
|
|
97
|
-
await pageWrapper.trigger('click')
|
|
98
|
-
emittedEvent = wrapper.emitted('on-toggle-change')
|
|
99
|
-
expect(emittedEvent).toHaveLength(3) // Ensure that the event count has increased
|
|
100
|
-
expect(emittedEvent[2]).toEqual([true]) // Check the payload of the third click
|
|
119
|
+
.find('[data-test-id="infoText_container"]')
|
|
120
|
+
.exists()
|
|
121
|
+
).toBe(true)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('renders the required star only when isRequiredLabel is true', () => {
|
|
125
|
+
const withStar = mountToggle({
|
|
126
|
+
label: 'My Label',
|
|
127
|
+
labelAlign: 'left',
|
|
128
|
+
isRequiredLabel: true,
|
|
101
129
|
})
|
|
130
|
+
expect(withStar.text()).toContain('*')
|
|
131
|
+
|
|
132
|
+
const withoutStar = mountToggle({ label: 'My Label', labelAlign: 'left' })
|
|
133
|
+
expect(withoutStar.text()).not.toContain('*')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
it('exposes dataId and dataQaId as data attributes', () => {
|
|
137
|
+
const wrapper = mountToggle({ dataId: 'my_toggle', dataQaId: 'qa_toggle' })
|
|
138
|
+
expect(wrapper.find('[data-id="my_toggle"]').exists()).toBe(true)
|
|
139
|
+
expect(wrapper.find('[data-qa-id="qa_toggle"]').exists()).toBe(true)
|
|
140
|
+
})
|
|
102
141
|
})
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import Label from '@/components/label'
|
|
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
|
+
jest.mock('@/components/icon/iconCache.mjs', () => ({
|
|
10
|
+
// need to mock this due to how jest handles import.meta
|
|
11
|
+
fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
describe('Label', () => {
|
|
15
|
+
const mountLabel = (props = {}, slot = 'My label') =>
|
|
16
|
+
mount(Label, { props, slots: { default: slot } })
|
|
17
|
+
|
|
18
|
+
it('renders the default slot content', () => {
|
|
19
|
+
const wrapper = mountLabel({}, 'Project name')
|
|
20
|
+
expect(wrapper.text()).toContain('Project name')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('renders the required star only when isRequiredLabel is true', () => {
|
|
24
|
+
expect(mountLabel({ isRequiredLabel: true }).text()).toContain('*')
|
|
25
|
+
expect(mountLabel({ isRequiredLabel: false }).text()).not.toContain('*')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('renders the optional label only when labelOptional is true', () => {
|
|
29
|
+
expect(mountLabel({ labelOptional: true }).text()).toContain('(optional)')
|
|
30
|
+
expect(mountLabel({ labelOptional: false }).text()).not.toContain(
|
|
31
|
+
'(optional)'
|
|
32
|
+
)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
it('renders the info text only when infoTextMessage is provided', () => {
|
|
36
|
+
expect(
|
|
37
|
+
mountLabel().find('[data-test-id="infoText_container"]').exists()
|
|
38
|
+
).toBe(false)
|
|
39
|
+
|
|
40
|
+
expect(
|
|
41
|
+
mountLabel({ infoTextMessage: 'Some info' })
|
|
42
|
+
.find('[data-test-id="infoText_container"]')
|
|
43
|
+
.exists()
|
|
44
|
+
).toBe(true)
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('renders both the required star and the optional label together', () => {
|
|
48
|
+
const text = mountLabel({
|
|
49
|
+
isRequiredLabel: true,
|
|
50
|
+
labelOptional: true,
|
|
51
|
+
}).text()
|
|
52
|
+
expect(text).toContain('*')
|
|
53
|
+
expect(text).toContain('(optional)')
|
|
54
|
+
})
|
|
55
|
+
})
|
|
@@ -16,6 +16,7 @@ export default {
|
|
|
16
16
|
labelOptional: { control: 'boolean' },
|
|
17
17
|
infoTextMessage: { control: 'text' },
|
|
18
18
|
infoTextAlign: { control: 'select', options: ['left', 'right'] },
|
|
19
|
+
appTheme: { control: 'select', options: ['light', 'dark'] },
|
|
19
20
|
},
|
|
20
21
|
}
|
|
21
22
|
|
|
@@ -67,3 +68,38 @@ HorizontalLayout.args = {
|
|
|
67
68
|
slotLabel: 'Budget (CHF)',
|
|
68
69
|
labelAlign: 'horizontal',
|
|
69
70
|
}
|
|
71
|
+
|
|
72
|
+
export const CustomFontStyle = Template.bind({})
|
|
73
|
+
CustomFontStyle.args = {
|
|
74
|
+
...Default.args,
|
|
75
|
+
slotLabel: 'Section heading',
|
|
76
|
+
fontSize: '20px',
|
|
77
|
+
labelFontColor: 'green',
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const DarkTemplate = (args) => ({
|
|
81
|
+
components: { LabelText },
|
|
82
|
+
setup() {
|
|
83
|
+
return { args }
|
|
84
|
+
},
|
|
85
|
+
template: `
|
|
86
|
+
<div
|
|
87
|
+
data-test-id="label-story"
|
|
88
|
+
style="padding: 16px; max-width: 420px; background: #1f1f1f;"
|
|
89
|
+
>
|
|
90
|
+
<LabelText v-bind="args">
|
|
91
|
+
{{ args.slotLabel }}
|
|
92
|
+
</LabelText>
|
|
93
|
+
</div>
|
|
94
|
+
`,
|
|
95
|
+
provide: {
|
|
96
|
+
theme,
|
|
97
|
+
},
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
export const DarkTheme = DarkTemplate.bind({})
|
|
101
|
+
DarkTheme.args = {
|
|
102
|
+
...Default.args,
|
|
103
|
+
slotLabel: 'Project name',
|
|
104
|
+
appTheme: 'dark',
|
|
105
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
import { mount } from '@vue/test-utils'
|
|
3
|
+
import MarkerItem from '@/components/markerItem'
|
|
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
|
+
jest.mock('@/components/icon/iconCache.mjs', () => ({
|
|
10
|
+
// need to mock this due to how jest handles import.meta
|
|
11
|
+
fetchIcon: jest.fn(() => Promise.resolve('mocked-icon-url.svg')),
|
|
12
|
+
}))
|
|
13
|
+
|
|
14
|
+
describe('MarkerItem', () => {
|
|
15
|
+
const mountMarker = (props = {}) =>
|
|
16
|
+
mount(MarkerItem, { props: { label: 'Marker', ...props } })
|
|
17
|
+
|
|
18
|
+
it('renders the label text', () => {
|
|
19
|
+
const wrapper = mountMarker({ label: 'Highlights' })
|
|
20
|
+
expect(wrapper.text()).toContain('Highlights')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('exposes the label as the span title attribute', () => {
|
|
24
|
+
const wrapper = mountMarker({ label: 'Highlights' })
|
|
25
|
+
expect(wrapper.find('span').attributes('title')).toBe('Highlights')
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
it('renders an icon only when iconName is provided', () => {
|
|
29
|
+
expect(
|
|
30
|
+
mountMarker({ iconName: 'House' })
|
|
31
|
+
.find('[data-test-id="icon_wrapper"]')
|
|
32
|
+
.exists()
|
|
33
|
+
).toBe(true)
|
|
34
|
+
|
|
35
|
+
expect(
|
|
36
|
+
mountMarker().find('[data-test-id="icon_wrapper"]').exists()
|
|
37
|
+
).toBe(false)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('passes the iconName through to the Icon component', () => {
|
|
41
|
+
const wrapper = mountMarker({ iconName: 'House' })
|
|
42
|
+
expect(wrapper.findComponent({ name: 'EturnityIcon' }).exists()).toBe(true)
|
|
43
|
+
// EturnityIcon forwards `name` to the inner Icon, which declares it as a prop
|
|
44
|
+
expect(wrapper.findComponent({ name: 'Icon' }).props('name')).toBe('House')
|
|
45
|
+
})
|
|
46
|
+
})
|
|
@@ -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
|
+
})
|