@mozaic-ds/vue 2.7.0 → 2.9.0
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/dist/mozaic-vue.css +1 -1
- package/dist/mozaic-vue.d.ts +183 -100
- package/dist/mozaic-vue.js +2918 -978
- package/dist/mozaic-vue.js.map +1 -1
- package/dist/mozaic-vue.umd.cjs +5 -1
- package/dist/mozaic-vue.umd.cjs.map +1 -1
- package/package.json +3 -2
- package/src/components/drawer/MDrawer.spec.ts +144 -5
- package/src/components/drawer/MDrawer.vue +94 -40
- package/src/components/drawer/README.md +2 -0
- package/src/components/field/MField.spec.ts +94 -85
- package/src/components/field/MField.stories.ts +16 -0
- package/src/components/field/MField.vue +8 -1
- package/src/components/field/README.md +1 -0
- package/src/components/iconbutton/MIconButton.vue +5 -0
- package/src/components/loader/MLoader.spec.ts +41 -0
- package/src/components/loader/MLoader.vue +7 -1
- package/src/components/loader/README.md +1 -1
- package/src/components/loadingoverlay/MLoadingOverlay.stories.ts +1 -1
- package/src/components/modal/MModal.spec.ts +63 -3
- package/src/components/modal/MModal.vue +50 -8
- package/src/components/modal/README.md +2 -0
- package/src/components/phonenumber/MPhoneNumber.spec.ts +294 -0
- package/src/components/phonenumber/MPhoneNumber.stories.ts +88 -0
- package/src/components/phonenumber/MPhoneNumber.vue +271 -0
- package/src/components/phonenumber/README.md +26 -0
- package/src/components/pincode/README.md +1 -1
- package/src/components/quantityselector/MQuantitySelector.stories.ts +0 -7
- package/src/components/togglegroup/MToggleGroup.vue +0 -2
- package/src/components/togglegroup/README.md +1 -1
- package/src/main.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mozaic-ds/vue",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Mozaic-Vue is the Vue.js implementation of ADEO Design system",
|
|
6
6
|
"author": "ADEO - ADEO Design system",
|
|
@@ -82,7 +82,8 @@
|
|
|
82
82
|
"vite-plugin-dts": "^4.5.3",
|
|
83
83
|
"vitest": "^3.0.9",
|
|
84
84
|
"vue-component-meta": "^3.0.8",
|
|
85
|
-
"vue-eslint-parser": "^10.1.1"
|
|
85
|
+
"vue-eslint-parser": "^10.1.1",
|
|
86
|
+
"libphonenumber-js": "^1.12.23"
|
|
86
87
|
},
|
|
87
88
|
"bugs": {
|
|
88
89
|
"url": "https://github.com/adeo/mozaic-vue/issues"
|
|
@@ -2,6 +2,19 @@ import { describe, it, expect } from 'vitest';
|
|
|
2
2
|
import { mount } from '@vue/test-utils';
|
|
3
3
|
import MDrawer from '@/components/drawer/MDrawer.vue';
|
|
4
4
|
|
|
5
|
+
// Stubs for child components
|
|
6
|
+
const stubs = {
|
|
7
|
+
ArrowBack24: { template: '<svg />' },
|
|
8
|
+
Cross24: { template: '<svg />' },
|
|
9
|
+
MIconButton: {
|
|
10
|
+
template: `<button @click="$emit('click')"><slot name="icon"/></button>`,
|
|
11
|
+
},
|
|
12
|
+
MOverlay: {
|
|
13
|
+
name: 'MOverlay',
|
|
14
|
+
template: `<div class="overlay" @click="$emit('click')"><slot/></div>`,
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
|
|
5
18
|
describe('MDrawer component', () => {
|
|
6
19
|
it('renders title and contentTitle when provided', () => {
|
|
7
20
|
const wrapper = mount(MDrawer, {
|
|
@@ -13,6 +26,7 @@ describe('MDrawer component', () => {
|
|
|
13
26
|
slots: {
|
|
14
27
|
default: '<p>Drawer content</p>',
|
|
15
28
|
},
|
|
29
|
+
global: { stubs },
|
|
16
30
|
});
|
|
17
31
|
|
|
18
32
|
expect(wrapper.find('.mc-drawer__title').text()).toBe('Main Drawer Title');
|
|
@@ -30,13 +44,15 @@ describe('MDrawer component', () => {
|
|
|
30
44
|
open: true,
|
|
31
45
|
title: 'Test Title',
|
|
32
46
|
},
|
|
47
|
+
global: { stubs },
|
|
33
48
|
});
|
|
34
49
|
|
|
35
50
|
const closeButton = wrapper.find('.mc-drawer__close');
|
|
36
51
|
await closeButton.trigger('click');
|
|
37
52
|
|
|
38
|
-
|
|
39
|
-
expect(
|
|
53
|
+
const emitted = wrapper.emitted('update:open');
|
|
54
|
+
expect(emitted).toBeTruthy();
|
|
55
|
+
expect(emitted![emitted!.length - 1]).toEqual([false]);
|
|
40
56
|
});
|
|
41
57
|
|
|
42
58
|
it('emits back event when back button is clicked', async () => {
|
|
@@ -46,6 +62,7 @@ describe('MDrawer component', () => {
|
|
|
46
62
|
title: 'Test Title',
|
|
47
63
|
back: true,
|
|
48
64
|
},
|
|
65
|
+
global: { stubs },
|
|
49
66
|
});
|
|
50
67
|
|
|
51
68
|
const backButton = wrapper.find('.mc-drawer__back');
|
|
@@ -63,6 +80,7 @@ describe('MDrawer component', () => {
|
|
|
63
80
|
slots: {
|
|
64
81
|
footer: '<button>Footer Button</button>',
|
|
65
82
|
},
|
|
83
|
+
global: { stubs },
|
|
66
84
|
});
|
|
67
85
|
|
|
68
86
|
expect(wrapper.find('.mc-drawer__footer').exists()).toBe(true);
|
|
@@ -79,6 +97,7 @@ describe('MDrawer component', () => {
|
|
|
79
97
|
extended: true,
|
|
80
98
|
position: 'left',
|
|
81
99
|
},
|
|
100
|
+
global: { stubs },
|
|
82
101
|
});
|
|
83
102
|
|
|
84
103
|
const section = wrapper.find('section.mc-drawer');
|
|
@@ -93,6 +112,7 @@ describe('MDrawer component', () => {
|
|
|
93
112
|
open: true,
|
|
94
113
|
title: 'Test Title',
|
|
95
114
|
},
|
|
115
|
+
global: { stubs },
|
|
96
116
|
});
|
|
97
117
|
|
|
98
118
|
expect(wrapper.find('.mc-drawer__back').exists()).toBe(false);
|
|
@@ -104,6 +124,7 @@ describe('MDrawer component', () => {
|
|
|
104
124
|
title: 'Test Title',
|
|
105
125
|
},
|
|
106
126
|
attachTo: document.body,
|
|
127
|
+
global: { stubs },
|
|
107
128
|
});
|
|
108
129
|
|
|
109
130
|
const titleElement = wrapper.find('.mc-drawer__title').element;
|
|
@@ -112,17 +133,135 @@ describe('MDrawer component', () => {
|
|
|
112
133
|
expect(document.activeElement).toBe(titleElement);
|
|
113
134
|
});
|
|
114
135
|
|
|
115
|
-
it('does not
|
|
136
|
+
it('does not refocus the title when the drawer closes', async () => {
|
|
116
137
|
const wrapper = mount(MDrawer, {
|
|
117
138
|
props: {
|
|
118
139
|
title: 'Test Title',
|
|
119
|
-
open: true
|
|
140
|
+
open: true,
|
|
120
141
|
},
|
|
121
142
|
attachTo: document.body,
|
|
143
|
+
global: { stubs },
|
|
122
144
|
});
|
|
123
145
|
|
|
124
146
|
const titleElement = wrapper.find('.mc-drawer__title').element;
|
|
147
|
+
|
|
148
|
+
expect(document.activeElement).toBe(titleElement);
|
|
149
|
+
|
|
125
150
|
await wrapper.setProps({ open: false });
|
|
126
|
-
|
|
151
|
+
await wrapper.vm.$nextTick();
|
|
152
|
+
|
|
153
|
+
expect(document.activeElement).toBe(titleElement);
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it('emits update:open false when overlay is clicked and closeOnOverlay is true', async () => {
|
|
157
|
+
const wrapper = mount(MDrawer, {
|
|
158
|
+
props: {
|
|
159
|
+
open: true,
|
|
160
|
+
title: 'Test Title',
|
|
161
|
+
closeOnOverlay: true,
|
|
162
|
+
},
|
|
163
|
+
global: { stubs },
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await wrapper.find('.overlay').trigger('click');
|
|
167
|
+
|
|
168
|
+
const emitted = wrapper.emitted('update:open');
|
|
169
|
+
expect(emitted).toBeTruthy();
|
|
170
|
+
expect(emitted![emitted!.length - 1]).toEqual([false]);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('does not emit update:open when overlay is clicked and closeOnOverlay is false', async () => {
|
|
174
|
+
const wrapper = mount(MDrawer, {
|
|
175
|
+
props: {
|
|
176
|
+
open: true,
|
|
177
|
+
title: 'Test Title',
|
|
178
|
+
closeOnOverlay: false,
|
|
179
|
+
},
|
|
180
|
+
global: { stubs },
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
await wrapper.find('.overlay').trigger('click');
|
|
184
|
+
|
|
185
|
+
const emitted = wrapper.emitted('update:open');
|
|
186
|
+
expect(emitted).toBeTruthy();
|
|
187
|
+
expect(emitted?.length).toBe(1);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('does not emit update:open when overlay is clicked and closeOnOverlay is not set', async () => {
|
|
191
|
+
const wrapper = mount(MDrawer, {
|
|
192
|
+
props: {
|
|
193
|
+
open: true,
|
|
194
|
+
title: 'Test Title',
|
|
195
|
+
},
|
|
196
|
+
global: { stubs },
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
await wrapper.find('.overlay').trigger('click');
|
|
200
|
+
|
|
201
|
+
const emitted = wrapper.emitted('update:open');
|
|
202
|
+
expect(emitted).toBeTruthy();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('emits update:open false when pressing ESC key', async () => {
|
|
206
|
+
const wrapper = mount(MDrawer, {
|
|
207
|
+
props: {
|
|
208
|
+
open: true,
|
|
209
|
+
title: 'Test Title',
|
|
210
|
+
},
|
|
211
|
+
global: { stubs },
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
await wrapper.find('section.mc-drawer').trigger('keydown.esc');
|
|
215
|
+
expect(wrapper.emitted('update:open')).toBeTruthy();
|
|
216
|
+
expect(wrapper.emitted('update:open')!.at(-1)).toEqual([false]);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('locks and unlocks scroll when scroll=false and open changes', async () => {
|
|
220
|
+
const wrapper = mount(MDrawer, {
|
|
221
|
+
props: {
|
|
222
|
+
title: 'Scroll Test',
|
|
223
|
+
open: false,
|
|
224
|
+
scroll: false,
|
|
225
|
+
},
|
|
226
|
+
global: { stubs },
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(document.body.style.overflow).toBe('');
|
|
230
|
+
|
|
231
|
+
await wrapper.setProps({ open: true });
|
|
232
|
+
expect(document.body.style.overflow).toBe('hidden');
|
|
233
|
+
|
|
234
|
+
await wrapper.setProps({ open: false });
|
|
235
|
+
expect(document.body.style.overflow).toBe('');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('restores scroll when unmounted', async () => {
|
|
239
|
+
const wrapper = mount(MDrawer, {
|
|
240
|
+
props: {
|
|
241
|
+
open: true,
|
|
242
|
+
title: 'Unmount Test',
|
|
243
|
+
scroll: false,
|
|
244
|
+
},
|
|
245
|
+
global: { stubs },
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
await wrapper.setProps({ open: true });
|
|
249
|
+
expect(document.body.style.overflow).toBe('hidden');
|
|
250
|
+
|
|
251
|
+
wrapper.unmount();
|
|
252
|
+
expect(document.body.style.overflow).toBe('');
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('emits update:open on mount reflecting initial state', () => {
|
|
256
|
+
const wrapper = mount(MDrawer, {
|
|
257
|
+
props: {
|
|
258
|
+
open: true,
|
|
259
|
+
title: 'Initial Test',
|
|
260
|
+
},
|
|
261
|
+
global: { stubs },
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
expect(wrapper.emitted('update:open')).toBeTruthy();
|
|
265
|
+
expect(wrapper.emitted('update:open')![0]).toEqual([true]);
|
|
127
266
|
});
|
|
128
267
|
});
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<MOverlay
|
|
2
|
+
<MOverlay
|
|
3
|
+
:is-visible="open"
|
|
4
|
+
dialogLabel="drawerTitle"
|
|
5
|
+
@click="onClickOverlay"
|
|
6
|
+
>
|
|
3
7
|
<section
|
|
4
8
|
class="mc-drawer"
|
|
5
9
|
:class="classObject"
|
|
@@ -10,6 +14,7 @@
|
|
|
10
14
|
:aria-hidden="!open"
|
|
11
15
|
v-bind="$attrs"
|
|
12
16
|
@keydown.esc="onClose"
|
|
17
|
+
@click.stop
|
|
13
18
|
>
|
|
14
19
|
<div class="mc-drawer__dialog" role="document">
|
|
15
20
|
<div class="mc-drawer__header">
|
|
@@ -24,7 +29,14 @@
|
|
|
24
29
|
<ArrowBack24 aria-hidden="true" />
|
|
25
30
|
</template>
|
|
26
31
|
</MIconButton>
|
|
27
|
-
<h2
|
|
32
|
+
<h2
|
|
33
|
+
class="mc-drawer__title"
|
|
34
|
+
tabindex="-1"
|
|
35
|
+
id="drawerTitle"
|
|
36
|
+
ref="titleRef"
|
|
37
|
+
>
|
|
38
|
+
{{ title }}
|
|
39
|
+
</h2>
|
|
28
40
|
<MIconButton
|
|
29
41
|
class="mc-drawer__close"
|
|
30
42
|
aria-label="Close"
|
|
@@ -53,7 +65,7 @@
|
|
|
53
65
|
</template>
|
|
54
66
|
|
|
55
67
|
<script setup lang="ts">
|
|
56
|
-
import { computed, watch, type VNode, ref } from 'vue';
|
|
68
|
+
import { computed, watch, type VNode, ref, onMounted, onUnmounted } from 'vue';
|
|
57
69
|
import ArrowBack24 from '@mozaic-ds/icons-vue/src/components/ArrowBack24/ArrowBack24.vue';
|
|
58
70
|
import Cross24 from '@mozaic-ds/icons-vue/src/components/Cross24/Cross24.vue';
|
|
59
71
|
import MIconButton from '../iconbutton/MIconButton.vue';
|
|
@@ -61,32 +73,45 @@ import MOverlay from '../overlay/MOverlay.vue';
|
|
|
61
73
|
/**
|
|
62
74
|
* A drawer is a sliding panel that appears from the side of the screen, providing additional content, settings, or actions without disrupting the main view. It is often used for filtering options, or contextual details. It enhances usability by keeping interfaces clean while offering expandable functionality.
|
|
63
75
|
*/
|
|
64
|
-
const props =
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
76
|
+
const props = withDefaults(
|
|
77
|
+
defineProps<{
|
|
78
|
+
/**
|
|
79
|
+
* If `true`, display the drawer.
|
|
80
|
+
*/
|
|
81
|
+
open?: boolean;
|
|
82
|
+
/**
|
|
83
|
+
* Position of the drawer.
|
|
84
|
+
*/
|
|
85
|
+
position?: 'left' | 'right';
|
|
86
|
+
/**
|
|
87
|
+
* If `true`, the drawer have a bigger width.
|
|
88
|
+
*/
|
|
89
|
+
extended?: boolean;
|
|
90
|
+
/**
|
|
91
|
+
* If `true`, display the back button.
|
|
92
|
+
*/
|
|
93
|
+
back?: boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Title of the drawer.
|
|
96
|
+
*/
|
|
97
|
+
title: string;
|
|
98
|
+
/**
|
|
99
|
+
* Title of the content of the drawer.
|
|
100
|
+
*/
|
|
101
|
+
contentTitle?: string;
|
|
102
|
+
/**
|
|
103
|
+
* if `false`, lock the scroll when open.
|
|
104
|
+
*/
|
|
105
|
+
scroll?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* if `true`, close the drawer when clicking the overlay.
|
|
108
|
+
*/
|
|
109
|
+
closeOnOverlay?: boolean;
|
|
110
|
+
}>(),
|
|
111
|
+
{
|
|
112
|
+
scroll: true,
|
|
113
|
+
},
|
|
114
|
+
);
|
|
90
115
|
|
|
91
116
|
defineSlots<{
|
|
92
117
|
/**
|
|
@@ -108,20 +133,49 @@ const classObject = computed(() => {
|
|
|
108
133
|
};
|
|
109
134
|
});
|
|
110
135
|
|
|
111
|
-
watch(
|
|
112
|
-
() => props.open,
|
|
113
|
-
(newValue) => {
|
|
114
|
-
emit('update:open', newValue);
|
|
115
|
-
},
|
|
116
|
-
);
|
|
117
|
-
|
|
118
136
|
const titleRef = ref<HTMLElement | null>(null);
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
137
|
+
const isClient =
|
|
138
|
+
typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
139
|
+
|
|
140
|
+
const lockScroll = () => {
|
|
141
|
+
if (!isClient) return;
|
|
142
|
+
document.body.style.overflow = 'hidden';
|
|
143
|
+
document.documentElement.style.overflow = 'hidden';
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const unlockScroll = () => {
|
|
147
|
+
if (!isClient) return;
|
|
148
|
+
document.body.style.overflow = '';
|
|
149
|
+
document.documentElement.style.overflow = '';
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
onMounted(() => {
|
|
153
|
+
watch(
|
|
154
|
+
() => props.open,
|
|
155
|
+
(isOpen) => {
|
|
156
|
+
emit('update:open', isOpen);
|
|
157
|
+
if (isOpen && titleRef.value) {
|
|
158
|
+
titleRef.value.focus();
|
|
159
|
+
}
|
|
160
|
+
if (props.scroll === false) {
|
|
161
|
+
if (isOpen) lockScroll();
|
|
162
|
+
else unlockScroll();
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
{ immediate: true },
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
onUnmounted(() => {
|
|
170
|
+
unlockScroll();
|
|
123
171
|
});
|
|
124
172
|
|
|
173
|
+
const onClickOverlay = () => {
|
|
174
|
+
if (props.closeOnOverlay) {
|
|
175
|
+
onClose();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
125
179
|
const onClose = () => {
|
|
126
180
|
emit('update:open', false);
|
|
127
181
|
};
|
|
@@ -13,6 +13,8 @@ A drawer is a sliding panel that appears from the side of the screen, providing
|
|
|
13
13
|
| `back` | If `true`, display the back button. | `boolean` | - |
|
|
14
14
|
| `title*` | Title of the drawer. | `string` | - |
|
|
15
15
|
| `contentTitle` | Title of the content of the drawer. | `string` | - |
|
|
16
|
+
| `scroll` | if `false`, lock the scroll when open. | `boolean` | `true` |
|
|
17
|
+
| `closeOnOverlay` | if `true`, close the drawer when clicking the overlay. | `boolean` | - |
|
|
16
18
|
|
|
17
19
|
## Slots
|
|
18
20
|
|
|
@@ -1,166 +1,175 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import { describe, it, expect } from 'vitest';
|
|
3
3
|
import MField from './MField.vue';
|
|
4
|
+
import MLoader from '../loader/MLoader.vue';
|
|
4
5
|
|
|
5
6
|
describe('MField component', () => {
|
|
6
|
-
it('
|
|
7
|
+
it('renders the label and associates it with the correct id', () => {
|
|
7
8
|
const wrapper = mount(MField, {
|
|
8
9
|
props: {
|
|
9
|
-
id: '
|
|
10
|
-
label: '
|
|
10
|
+
id: 'username',
|
|
11
|
+
label: 'Username',
|
|
11
12
|
},
|
|
12
13
|
});
|
|
13
14
|
|
|
14
|
-
const label = wrapper.find('
|
|
15
|
+
const label = wrapper.find('.mc-field__label');
|
|
15
16
|
expect(label.exists()).toBe(true);
|
|
16
|
-
expect(label.text()).
|
|
17
|
+
expect(label.text()).toContain('Username');
|
|
18
|
+
expect(label.attributes('for')).toBe('username');
|
|
17
19
|
});
|
|
18
20
|
|
|
19
|
-
it('
|
|
21
|
+
it('renders the requirement text when provided', () => {
|
|
20
22
|
const wrapper = mount(MField, {
|
|
21
23
|
props: {
|
|
22
|
-
id: '
|
|
23
|
-
label: '
|
|
24
|
+
id: 'email',
|
|
25
|
+
label: 'Email',
|
|
24
26
|
requirementText: 'required',
|
|
25
27
|
},
|
|
26
28
|
});
|
|
27
29
|
|
|
28
|
-
const
|
|
29
|
-
expect(
|
|
30
|
-
expect(
|
|
30
|
+
const requirement = wrapper.find('.mc-field__requirement');
|
|
31
|
+
expect(requirement.exists()).toBe(true);
|
|
32
|
+
expect(requirement.text()).toContain('(required)');
|
|
31
33
|
});
|
|
32
34
|
|
|
33
|
-
it('
|
|
35
|
+
it('does not render requirement text when not provided', () => {
|
|
34
36
|
const wrapper = mount(MField, {
|
|
35
|
-
props: {
|
|
36
|
-
id: 'input-id',
|
|
37
|
-
label: 'Field Label',
|
|
38
|
-
},
|
|
37
|
+
props: { id: 'email', label: 'Email' },
|
|
39
38
|
});
|
|
40
39
|
|
|
41
|
-
const
|
|
42
|
-
expect(
|
|
40
|
+
const requirement = wrapper.find('.mc-field__requirement');
|
|
41
|
+
expect(requirement.exists()).toBe(false);
|
|
43
42
|
});
|
|
44
43
|
|
|
45
|
-
it('
|
|
44
|
+
it('renders help text when helpId and helpText are provided', () => {
|
|
46
45
|
const wrapper = mount(MField, {
|
|
47
46
|
props: {
|
|
48
|
-
id: '
|
|
49
|
-
label: '
|
|
50
|
-
helpText: '
|
|
51
|
-
helpId: 'help
|
|
47
|
+
id: 'password',
|
|
48
|
+
label: 'Password',
|
|
49
|
+
helpText: 'At least 8 characters.',
|
|
50
|
+
helpId: 'password-help',
|
|
52
51
|
},
|
|
53
52
|
});
|
|
54
53
|
|
|
55
|
-
const
|
|
56
|
-
expect(
|
|
57
|
-
expect(
|
|
54
|
+
const help = wrapper.find('.mc-field__help');
|
|
55
|
+
expect(help.exists()).toBe(true);
|
|
56
|
+
expect(help.attributes('id')).toBe('password-help');
|
|
57
|
+
expect(help.text()).toBe('At least 8 characters.');
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
-
it('
|
|
60
|
+
it('does not render help text if helpId or helpText is missing', () => {
|
|
61
61
|
const wrapper = mount(MField, {
|
|
62
62
|
props: {
|
|
63
|
-
id: '
|
|
64
|
-
label: '
|
|
63
|
+
id: 'password',
|
|
64
|
+
label: 'Password',
|
|
65
|
+
helpText: 'Help text',
|
|
65
66
|
},
|
|
66
67
|
});
|
|
67
68
|
|
|
68
|
-
const
|
|
69
|
-
expect(
|
|
69
|
+
const help = wrapper.find('.mc-field__help');
|
|
70
|
+
expect(help.exists()).toBe(false);
|
|
70
71
|
});
|
|
71
72
|
|
|
72
|
-
it('
|
|
73
|
+
it('renders slot content inside .mc-field__content', () => {
|
|
73
74
|
const wrapper = mount(MField, {
|
|
74
|
-
props: {
|
|
75
|
-
|
|
76
|
-
label: 'Field Label',
|
|
77
|
-
isValid: true,
|
|
78
|
-
message: 'Valid input',
|
|
79
|
-
},
|
|
75
|
+
props: { id: 'field1', label: 'Field Label' },
|
|
76
|
+
slots: { default: '<input id="field1" />' },
|
|
80
77
|
});
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
);
|
|
85
|
-
expect(wrapper.find('.mc-field__validation-message').text()).toBe(
|
|
86
|
-
'Valid input',
|
|
87
|
-
);
|
|
79
|
+
const content = wrapper.find('.mc-field__content');
|
|
80
|
+
expect(content.exists()).toBe(true);
|
|
81
|
+
expect(content.find('input').exists()).toBe(true);
|
|
88
82
|
});
|
|
89
83
|
|
|
90
|
-
it('
|
|
84
|
+
it('renders validation message when message and valid state are provided', () => {
|
|
91
85
|
const wrapper = mount(MField, {
|
|
92
86
|
props: {
|
|
93
|
-
id: '
|
|
94
|
-
label: '
|
|
95
|
-
|
|
96
|
-
|
|
87
|
+
id: 'email',
|
|
88
|
+
label: 'Email',
|
|
89
|
+
isValid: true,
|
|
90
|
+
messageId: 'msg1',
|
|
91
|
+
message: 'Looks good!',
|
|
97
92
|
},
|
|
98
93
|
});
|
|
99
94
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
);
|
|
103
|
-
expect(
|
|
104
|
-
|
|
105
|
-
);
|
|
95
|
+
const message = wrapper.find('.mc-field__validation-message');
|
|
96
|
+
expect(message.exists()).toBe(true);
|
|
97
|
+
expect(message.attributes('id')).toBe('msg1');
|
|
98
|
+
expect(message.text()).toContain('Looks good!');
|
|
99
|
+
expect(message.classes()).toContain('is-valid');
|
|
106
100
|
});
|
|
107
101
|
|
|
108
|
-
it('
|
|
102
|
+
it('renders validation message when invalid', () => {
|
|
109
103
|
const wrapper = mount(MField, {
|
|
110
104
|
props: {
|
|
111
|
-
id: '
|
|
112
|
-
label: '
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
id: 'email',
|
|
106
|
+
label: 'Email',
|
|
107
|
+
isInvalid: true,
|
|
108
|
+
messageId: 'msg2',
|
|
109
|
+
message: 'Invalid email address.',
|
|
115
110
|
},
|
|
116
111
|
});
|
|
117
112
|
|
|
118
|
-
const
|
|
119
|
-
expect(
|
|
120
|
-
expect(
|
|
113
|
+
const message = wrapper.find('.mc-field__validation-message');
|
|
114
|
+
expect(message.exists()).toBe(true);
|
|
115
|
+
expect(message.text()).toContain('Invalid email address.');
|
|
116
|
+
expect(message.classes()).toContain('is-invalid');
|
|
121
117
|
});
|
|
122
118
|
|
|
123
|
-
it('
|
|
119
|
+
it('renders loader and message when loading', () => {
|
|
124
120
|
const wrapper = mount(MField, {
|
|
125
121
|
props: {
|
|
126
|
-
id: '
|
|
127
|
-
label: '
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
122
|
+
id: 'loadingField',
|
|
123
|
+
label: 'Loading Label',
|
|
124
|
+
isLoading: true,
|
|
125
|
+
messageId: 'loading-msg',
|
|
126
|
+
message: 'Loading...',
|
|
131
127
|
},
|
|
132
128
|
});
|
|
133
129
|
|
|
134
|
-
const
|
|
135
|
-
expect(
|
|
136
|
-
expect(
|
|
130
|
+
const message = wrapper.find('.mc-field__validation-message');
|
|
131
|
+
expect(message.exists()).toBe(true);
|
|
132
|
+
expect(message.classes()).toContain('is-loading');
|
|
133
|
+
|
|
134
|
+
const loader = wrapper.findComponent(MLoader);
|
|
135
|
+
expect(loader.exists()).toBe(true);
|
|
136
|
+
expect(loader.props('size')).toBe('xs');
|
|
137
137
|
});
|
|
138
138
|
|
|
139
|
-
it('
|
|
139
|
+
it('does not render validation message when no state (valid/invalid/loading)', () => {
|
|
140
140
|
const wrapper = mount(MField, {
|
|
141
141
|
props: {
|
|
142
|
-
id: '
|
|
143
|
-
label: '
|
|
144
|
-
|
|
145
|
-
message: '
|
|
142
|
+
id: 'email',
|
|
143
|
+
label: 'Email',
|
|
144
|
+
messageId: 'msg3',
|
|
145
|
+
message: 'Message without state',
|
|
146
146
|
},
|
|
147
147
|
});
|
|
148
148
|
|
|
149
|
-
const
|
|
150
|
-
expect(
|
|
151
|
-
expect(validationMessage.text()).toBe('There is an error');
|
|
149
|
+
const message = wrapper.find('.mc-field__validation-message');
|
|
150
|
+
expect(message.exists()).toBe(false);
|
|
152
151
|
});
|
|
153
152
|
|
|
154
|
-
it('
|
|
153
|
+
it('renders multiple validation states correctly', async () => {
|
|
155
154
|
const wrapper = mount(MField, {
|
|
156
155
|
props: {
|
|
157
|
-
id: '
|
|
158
|
-
label: 'Field
|
|
156
|
+
id: 'stateField',
|
|
157
|
+
label: 'Field',
|
|
158
|
+
messageId: 'msg4',
|
|
159
|
+
message: 'State message',
|
|
159
160
|
isValid: true,
|
|
160
161
|
},
|
|
161
162
|
});
|
|
162
163
|
|
|
163
|
-
|
|
164
|
-
expect(
|
|
164
|
+
let message = wrapper.find('.mc-field__validation-message');
|
|
165
|
+
expect(message.classes()).toContain('is-valid');
|
|
166
|
+
|
|
167
|
+
await wrapper.setProps({ isValid: false, isInvalid: true });
|
|
168
|
+
message = wrapper.find('.mc-field__validation-message');
|
|
169
|
+
expect(message.classes()).toContain('is-invalid');
|
|
170
|
+
|
|
171
|
+
await wrapper.setProps({ isInvalid: false, isLoading: true });
|
|
172
|
+
message = wrapper.find('.mc-field__validation-message');
|
|
173
|
+
expect(message.classes()).toContain('is-loading');
|
|
165
174
|
});
|
|
166
175
|
});
|