@weni/unnnic-system 3.12.3-alpha-teleports.1 → 3.12.3-alpha.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.
Files changed (97) hide show
  1. package/dist/components/ChartFunnel/DefaultFunnel/ChartDefaultFunnelBase.vue.d.ts.map +1 -1
  2. package/dist/components/Checkbox/Checkbox.vue.d.ts.map +1 -1
  3. package/dist/components/CheckboxGroup/CheckboxGroup.vue.d.ts.map +1 -1
  4. package/dist/components/DateFilter/DateFilter.vue.d.ts +81 -2
  5. package/dist/components/Drawer/Drawer.vue.d.ts.map +1 -1
  6. package/dist/components/FormElement/FormElement.vue.d.ts.map +1 -1
  7. package/dist/components/Input/BaseInput.vue.d.ts +22 -0
  8. package/dist/components/Input/BaseInput.vue.d.ts.map +1 -1
  9. package/dist/components/Input/Input.vue.d.ts +81 -2
  10. package/dist/components/Input/Input.vue.d.ts.map +1 -1
  11. package/dist/components/Input/TextInput.vue.d.ts +52 -1
  12. package/dist/components/Input/TextInput.vue.d.ts.map +1 -1
  13. package/dist/components/InputNext/InputNext.vue.d.ts +1 -1
  14. package/dist/components/ModalNext/ModalNext.vue.d.ts +81 -2
  15. package/dist/components/MultiSelect/MultSelectOption.vue.d.ts +17 -0
  16. package/dist/components/MultiSelect/MultSelectOption.vue.d.ts.map +1 -0
  17. package/dist/components/MultiSelect/index.vue.d.ts +44 -0
  18. package/dist/components/MultiSelect/index.vue.d.ts.map +1 -0
  19. package/dist/components/Radio/Radio.vue.d.ts.map +1 -1
  20. package/dist/components/Select/index.vue.d.ts +44 -0
  21. package/dist/components/Select/index.vue.d.ts.map +1 -0
  22. package/dist/components/SelectSmart/SelectSmart.vue.d.ts +52 -1
  23. package/dist/components/SelectTime/index.vue.d.ts +52 -1
  24. package/dist/components/Switch/Switch.vue.d.ts.map +1 -1
  25. package/dist/components/TemplatePreview/TemplatePreview.vue.d.ts.map +1 -1
  26. package/dist/components/Toast/Toast.vue.d.ts.map +1 -1
  27. package/dist/components/index.d.ts +23720 -0
  28. package/dist/components/index.d.ts.map +1 -0
  29. package/dist/components/ui/popover/PopoverOption.vue.d.ts +18 -0
  30. package/dist/components/ui/popover/PopoverOption.vue.d.ts.map +1 -0
  31. package/dist/components/ui/popover/index.d.ts +1 -0
  32. package/dist/components/ui/popover/index.d.ts.map +1 -1
  33. package/dist/{es-4aa88754.mjs → es-239c29c2.mjs} +1 -1
  34. package/dist/{index-ec96f88a.mjs → index-40e176e4.mjs} +9498 -9233
  35. package/dist/lib/layer-manager.d.ts.map +1 -1
  36. package/dist/locales/en.json.d.ts +2 -1
  37. package/dist/locales/es.json.d.ts +2 -1
  38. package/dist/locales/pt_br.json.d.ts +2 -1
  39. package/dist/{pt-br-ec1ec185.mjs → pt-br-5004a35e.mjs} +1 -1
  40. package/dist/style.css +1 -1
  41. package/dist/unnnic.mjs +176 -172
  42. package/dist/unnnic.umd.js +33 -33
  43. package/package.json +1 -1
  44. package/src/assets/scss/scheme-colors.scss +223 -223
  45. package/src/components/ChartFunnel/DefaultFunnel/ChartDefaultFunnelBase.vue +1 -2
  46. package/src/components/ChartFunnel/SvgFunnel/ChartFunnelTwoRows.vue +60 -61
  47. package/src/components/Checkbox/Checkbox.vue +9 -3
  48. package/src/components/CheckboxGroup/CheckboxGroup.vue +7 -5
  49. package/src/components/Chip/Chip.vue +1 -1
  50. package/src/components/Drawer/Drawer.vue +20 -9
  51. package/src/components/Drawer/__tests__/Drawer.spec.js +11 -9
  52. package/src/components/Drawer/__tests__/__snapshots__/Drawer.spec.js.snap +9 -9
  53. package/src/components/FormElement/FormElement.vue +96 -87
  54. package/src/components/Input/BaseInput.vue +21 -2
  55. package/src/components/Input/Input.scss +2 -3
  56. package/src/components/Input/Input.vue +21 -3
  57. package/src/components/Input/TextInput.vue +58 -22
  58. package/src/components/Input/__test__/__snapshots__/Input.spec.js.snap +5 -1
  59. package/src/components/Input/__test__/__snapshots__/TextInput.spec.js.snap +7 -1
  60. package/src/components/ModalDialog/ModalDialog.vue +2 -2
  61. package/src/components/MultiSelect/MultSelectOption.vue +49 -0
  62. package/src/components/MultiSelect/__tests__/MultiSelect.spec.js +556 -0
  63. package/src/components/MultiSelect/__tests__/MultiSelectOption.spec.js +229 -0
  64. package/src/components/MultiSelect/__tests__/__snapshots__/MultiSelect.spec.js.snap +81 -0
  65. package/src/components/MultiSelect/__tests__/__snapshots__/MultiSelectOption.spec.js.snap +51 -0
  66. package/src/components/MultiSelect/index.vue +224 -0
  67. package/src/components/Radio/Radio.vue +12 -6
  68. package/src/components/Radio/__test__/Radio.spec.js +3 -1
  69. package/src/components/RadioGroup/RadioGroup.vue +18 -10
  70. package/src/components/Select/__tests__/Select.spec.js +422 -0
  71. package/src/components/Select/__tests__/SelectItem.spec.js +330 -0
  72. package/src/components/Select/__tests__/__snapshots__/Popover.spec.js.snap +8 -0
  73. package/src/components/Select/__tests__/__snapshots__/Select.spec.js.snap +65 -0
  74. package/src/components/Select/__tests__/__snapshots__/SelectItem.spec.js.snap +15 -0
  75. package/src/components/Select/__tests__/__snapshots__/SelectOption.spec.js.snap +25 -0
  76. package/src/components/Select/__tests__/__snapshots__/SelectPopover.spec.js.snap +8 -0
  77. package/src/components/Select/index.vue +261 -0
  78. package/src/components/Switch/Switch.vue +10 -3
  79. package/src/components/TemplatePreview/TemplatePreview.vue +28 -25
  80. package/src/components/TemplatePreview/TemplatePreviewModal.vue +10 -10
  81. package/src/components/TemplatePreview/types.d.ts +3 -3
  82. package/src/components/Toast/Toast.vue +12 -8
  83. package/src/components/index.ts +7 -7
  84. package/src/components/ui/dialog/DialogClose.vue +3 -3
  85. package/src/components/ui/drawer/DrawerClose.vue +9 -3
  86. package/src/components/ui/popover/PopoverOption.vue +4 -0
  87. package/src/lib/layer-manager.ts +12 -20
  88. package/src/locales/en.json +2 -1
  89. package/src/locales/es.json +2 -1
  90. package/src/locales/pt_br.json +2 -1
  91. package/src/stories/MultiSelect.stories.js +142 -46
  92. package/src/stories/Select.stories.js +158 -0
  93. package/src/stories/TemplatePreview.stories.js +27 -27
  94. package/src/stories/TemplatePreviewModal.stories.js +31 -31
  95. package/dist/components/MultiSelect/MultiSelect.vue.d.ts +0 -163
  96. package/dist/components/MultiSelect/MultiSelect.vue.d.ts.map +0 -1
  97. package/src/components/MultiSelect/MultiSelect.vue +0 -297
@@ -6,7 +6,11 @@ exports[`Input.vue > matches the snapshot 1`] = `
6
6
  <p data-v-7f222291="" class="unnnic-label__label">Sample Label</p>
7
7
  <!--v-if-->
8
8
  </section>
9
- <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-form-input" hascloudycolor="false" mask="####-####"><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-form-input input-itself input size-md normal input--has-icon-left input--has-icon-right unnnic-form-input input-itself" hascloudycolor="false" placeholder="Enter text" iconleft="search" iconright="clear" iconleftclickable="true" iconrightclickable="true" type="text" value=""><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-left clickable" data-testid="material-icon" translate="no">search</span><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-right clickable" data-testid="material-icon" translate="no">clear</span></div>
9
+ <div data-v-a0d36167="" data-v-d890ad85="" class="text-input size--md unnnic-form-input" hascloudycolor="false" mask="####-####"><input data-v-86533b41="" data-v-a0d36167="" class="unnnic-form-input input-itself input size-md normal input--has-icon-left input--has-icon-right unnnic-form-input input-itself" hascloudycolor="false" placeholder="Enter text" iconleft="search" iconright="clear" iconleftclickable="true" iconrightclickable="true" showclear="false" type="text" value=""><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-left clickable" data-testid="material-icon" translate="no">search</span>
10
+ <section data-v-a0d36167="" class="icon-right-container">
11
+ <!--v-if--><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-right clickable" data-testid="material-icon" translate="no">clear</span>
12
+ </section>
13
+ </div>
10
14
  <section data-v-9f8d6c86="" class="unnnic-form-element__hints-container">
11
15
  <section data-v-9f8d6c86="" class="unnnic-form-element__message-container">
12
16
  <p data-v-9f8d6c86="" class="unnnic-form-element__message">Error message</p>
@@ -1,3 +1,9 @@
1
1
  // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
2
 
3
- exports[`TextInput.vue > matches the snapshot 1`] = `"<div data-v-a0d36167="" class="text-input size--md"><input data-v-86533b41="" data-v-a0d36167="" placeholder="Enter text" iconleft="search" iconright="clear" iconleftclickable="true" iconrightclickable="true" allowtogglepassword="false" class="input-itself input size-md normal input--has-icon-left input--has-icon-right input-itself" type="text" value=""><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-left clickable" data-testid="material-icon" translate="no">search</span><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-right clickable" data-testid="material-icon" translate="no">clear</span></div>"`;
3
+ exports[`TextInput.vue > matches the snapshot 1`] = `
4
+ "<div data-v-a0d36167="" class="text-input size--md"><input data-v-86533b41="" data-v-a0d36167="" placeholder="Enter text" iconleft="search" iconright="clear" iconleftclickable="true" iconrightclickable="true" allowtogglepassword="false" showclear="false" class="input-itself input size-md normal input--has-icon-left input--has-icon-right input-itself" type="text" value=""><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-left clickable" data-testid="material-icon" translate="no">search</span>
5
+ <section data-v-a0d36167="" class="icon-right-container">
6
+ <!--v-if--><span data-v-26446d8e="" data-v-a0d36167="" class="unnnic-icon material-symbols-rounded unnnic-icon-scheme--fg-base unnnic-icon-size--ant unnnic-icon__size--ant unnnic--clickable icon-right clickable" data-testid="material-icon" translate="no">clear</span>
7
+ </section>
8
+ </div>"
9
+ `;
@@ -77,8 +77,8 @@
77
77
  class="unnnic-modal-dialog__container__actions__primary-button"
78
78
  @click.stop="$emit('primaryButtonClick')"
79
79
  />
80
- </UnnnicDialogFooter>
81
- </section>
80
+ </UnnnicDialogFooter>
81
+ </section>
82
82
  </UnnnicDialogContent>
83
83
  </UnnnicDialog>
84
84
  </template>
@@ -0,0 +1,49 @@
1
+ <template>
2
+ <div :class="['unnnic-multi-select-option']">
3
+ <UnnnicCheckbox
4
+ :modelValue="props.active"
5
+ :disabled="props.disabled"
6
+ :label="props.label"
7
+ @update:model-value="emit('update:modelValue', !props.active)"
8
+ />
9
+ </div>
10
+ </template>
11
+
12
+ <script setup lang="ts">
13
+ import UnnnicCheckbox from '../Checkbox/Checkbox.vue';
14
+
15
+ defineOptions({
16
+ name: 'UnnnicMultiSelectOption',
17
+ });
18
+
19
+ const emit = defineEmits<{
20
+ 'update:modelValue': [boolean];
21
+ }>();
22
+
23
+ interface SelectOptionProps {
24
+ label: string;
25
+ disabled?: boolean;
26
+ active?: boolean;
27
+ focused?: boolean;
28
+ }
29
+
30
+ const props = withDefaults(defineProps<SelectOptionProps>(), {
31
+ disabled: false,
32
+ active: false,
33
+ focused: false,
34
+ });
35
+ </script>
36
+
37
+ <style lang="scss" scoped>
38
+ @use '@/assets/scss/unnnic' as *;
39
+ * {
40
+ margin: 0;
41
+ padding: 0;
42
+ box-sizing: border-box;
43
+ }
44
+
45
+ .unnnic-multi-select-option {
46
+ border-radius: $unnnic-radius-1;
47
+ font: $unnnic-font-emphasis;
48
+ }
49
+ </style>
@@ -0,0 +1,556 @@
1
+ import { mount } from '@vue/test-utils';
2
+ import { beforeEach, describe, expect, afterEach, test, vi } from 'vitest';
3
+ import UnnnicMultiSelect from '../index.vue';
4
+
5
+ vi.mock('../../mixins/i18n', () => ({
6
+ default: {
7
+ methods: {
8
+ $t: (key) => key,
9
+ },
10
+ },
11
+ }));
12
+
13
+ describe('UnnnicMultiSelect.vue', () => {
14
+ let wrapper;
15
+
16
+ const defaultProps = {
17
+ options: [
18
+ { label: 'Option 1', value: 'option1' },
19
+ { label: 'Option 2', value: 'option2' },
20
+ { label: 'Option 3', value: 'option3' },
21
+ ],
22
+ modelValue: [],
23
+ };
24
+
25
+ const mountWrapper = (props = {}, slots = {}) => {
26
+ return mount(UnnnicMultiSelect, {
27
+ props: {
28
+ ...defaultProps,
29
+ ...props,
30
+ },
31
+ global: {
32
+ mocks: {
33
+ $t: (key) => key,
34
+ },
35
+ },
36
+ slots,
37
+ });
38
+ };
39
+
40
+ beforeEach(() => {
41
+ wrapper = mountWrapper();
42
+ });
43
+
44
+ afterEach(() => {
45
+ wrapper?.unmount();
46
+ });
47
+
48
+ describe('rendering', () => {
49
+ test('renders correctly', () => {
50
+ expect(wrapper.exists()).toBe(true);
51
+ expect(wrapper.find('.unnnic-multi-select').exists()).toBe(true);
52
+ });
53
+
54
+ test('renders input component', () => {
55
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
56
+ expect(input.exists()).toBe(true);
57
+ });
58
+
59
+ test('renders popover component', () => {
60
+ const popover = wrapper.findComponent({ name: 'UnnnicPopover' });
61
+ expect(popover.exists()).toBe(true);
62
+ });
63
+
64
+ test('renders select options when popover is open', async () => {
65
+ wrapper.vm.openPopover = true;
66
+ await wrapper.vm.$nextTick();
67
+ const options = wrapper.findAllComponents({
68
+ name: 'UnnnicMultiSelectOption',
69
+ });
70
+ expect(options.length).toBe(3);
71
+ });
72
+ });
73
+
74
+ describe('props validation', () => {
75
+ test('validates required options prop', () => {
76
+ const { options } = wrapper.vm.$options.props;
77
+ expect(options.required).toBe(true);
78
+ });
79
+
80
+ test('validates required modelValue prop', () => {
81
+ const { modelValue } = wrapper.vm.$options.props;
82
+ expect(modelValue.required).toBe(true);
83
+ });
84
+
85
+ test('applies default values correctly', () => {
86
+ expect(wrapper.vm.size).toBe('md');
87
+ expect(wrapper.vm.type).toBe('normal');
88
+ expect(wrapper.vm.placeholder).toBe('');
89
+ expect(wrapper.vm.optionsLines).toBe(5);
90
+ expect(wrapper.vm.returnObject).toBe(false);
91
+ expect(wrapper.vm.itemLabel).toBe('label');
92
+ expect(wrapper.vm.itemValue).toBe('value');
93
+ expect(wrapper.vm.locale).toBe('en');
94
+ expect(wrapper.vm.enableSearch).toBe(false);
95
+ expect(wrapper.vm.disabled).toBe(false);
96
+ });
97
+ });
98
+
99
+ describe('input display', () => {
100
+ test('displays placeholder when no option is selected', () => {
101
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
102
+ expect(input.props('placeholder')).toBe('');
103
+ });
104
+
105
+ test('displays selected option labels', async () => {
106
+ await wrapper.setProps({ modelValue: ['option1'] });
107
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
108
+ expect(input.props('modelValue')).toBe('Option 1');
109
+ });
110
+
111
+ test('displays multiple selected option labels separated by comma', async () => {
112
+ await wrapper.setProps({ modelValue: ['option1', 'option2'] });
113
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
114
+ expect(input.props('modelValue')).toBe('Option 1, Option 2');
115
+ });
116
+
117
+ test('displays custom placeholder', async () => {
118
+ await wrapper.setProps({ placeholder: 'Select an option' });
119
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
120
+ expect(input.props('placeholder')).toBe('Select an option');
121
+ });
122
+
123
+ test('input is readonly', () => {
124
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
125
+ expect(input.props('readonly')).toBe(true);
126
+ });
127
+
128
+ test('input shows correct icon based on popover state', async () => {
129
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
130
+
131
+ expect(input.props('iconRight')).toBe('keyboard_arrow_down');
132
+
133
+ wrapper.vm.openPopover = true;
134
+ await wrapper.vm.$nextTick();
135
+ expect(input.props('iconRight')).toBe('keyboard_arrow_up');
136
+ });
137
+ });
138
+
139
+ describe('option selection', () => {
140
+ test('emits update:modelValue with array when option is selected', async () => {
141
+ wrapper.vm.openPopover = true;
142
+ await wrapper.vm.$nextTick();
143
+ const options = wrapper.findAllComponents({
144
+ name: 'UnnnicMultiSelectOption',
145
+ });
146
+
147
+ await options[0].vm.$emit('update:model-value', true);
148
+
149
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
150
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([['option1']]);
151
+ });
152
+
153
+ test('adds option to existing selection', async () => {
154
+ await wrapper.setProps({ modelValue: ['option1'] });
155
+ wrapper.vm.openPopover = true;
156
+ await wrapper.vm.$nextTick();
157
+ const options = wrapper.findAllComponents({
158
+ name: 'UnnnicMultiSelectOption',
159
+ });
160
+
161
+ await options[1].vm.$emit('update:model-value', true);
162
+
163
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
164
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([
165
+ ['option1', 'option2'],
166
+ ]);
167
+ });
168
+
169
+ test('removes option from selection when unselected', async () => {
170
+ await wrapper.setProps({ modelValue: ['option1', 'option2'] });
171
+ wrapper.vm.openPopover = true;
172
+ await wrapper.vm.$nextTick();
173
+ const options = wrapper.findAllComponents({
174
+ name: 'UnnnicMultiSelectOption',
175
+ });
176
+
177
+ await options[0].vm.$emit('update:model-value', false);
178
+
179
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
180
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([['option2']]);
181
+ });
182
+
183
+ test('emits object array when returnObject is true', async () => {
184
+ await wrapper.setProps({ returnObject: true, modelValue: [] });
185
+ wrapper.vm.openPopover = true;
186
+ await wrapper.vm.$nextTick();
187
+ const options = wrapper.findAllComponents({
188
+ name: 'UnnnicMultiSelectOption',
189
+ });
190
+
191
+ await options[0].vm.$emit('update:model-value', true);
192
+
193
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([
194
+ [{ label: 'Option 1', value: 'option1' }],
195
+ ]);
196
+ });
197
+
198
+ test('removes object from selection when returnObject is true', async () => {
199
+ await wrapper.setProps({
200
+ returnObject: true,
201
+ modelValue: [{ label: 'Option 1', value: 'option1' }],
202
+ });
203
+ wrapper.vm.openPopover = true;
204
+ await wrapper.vm.$nextTick();
205
+ const options = wrapper.findAllComponents({
206
+ name: 'UnnnicMultiSelectOption',
207
+ });
208
+
209
+ await options[0].vm.$emit('update:model-value', false);
210
+
211
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
212
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([[]]);
213
+ });
214
+
215
+ test('does not emit when disabled option is clicked', async () => {
216
+ const disabledOptions = [
217
+ { label: 'Option 1', value: 'option1' },
218
+ { label: 'Disabled Option', value: 'disabled', disabled: true },
219
+ ];
220
+
221
+ await wrapper.setProps({ options: disabledOptions });
222
+ wrapper.vm.openPopover = true;
223
+ await wrapper.vm.$nextTick();
224
+ const options = wrapper.findAllComponents({
225
+ name: 'UnnnicMultiSelectOption',
226
+ });
227
+
228
+ expect(options[1].props('disabled')).toBe(true);
229
+ });
230
+ });
231
+
232
+ describe('search functionality', () => {
233
+ test('renders search input when enableSearch is true', async () => {
234
+ await wrapper.setProps({ enableSearch: true });
235
+ wrapper.vm.openPopover = true;
236
+ await wrapper.vm.$nextTick();
237
+ const searchInputs = wrapper.findAllComponents({ name: 'UnnnicInput' });
238
+ expect(searchInputs.length).toBe(2);
239
+ });
240
+
241
+ test('does not render search input when enableSearch is false', async () => {
242
+ await wrapper.setProps({ enableSearch: false });
243
+ const searchInputs = wrapper.findAllComponents({ name: 'UnnnicInput' });
244
+ expect(searchInputs.length).toBe(1);
245
+ });
246
+
247
+ test('emits update:search when search input changes', async () => {
248
+ await wrapper.setProps({ enableSearch: true });
249
+ wrapper.vm.openPopover = true;
250
+ await wrapper.vm.$nextTick();
251
+ const searchInput = wrapper.findAllComponents({ name: 'UnnnicInput' })[1];
252
+
253
+ await searchInput.vm.$emit('update:modelValue', 'test search');
254
+
255
+ expect(wrapper.emitted('update:search')).toBeTruthy();
256
+ expect(wrapper.emitted('update:search')[0]).toEqual(['test search']);
257
+ });
258
+
259
+ test('filters options based on search term', async () => {
260
+ await wrapper.setProps({ enableSearch: true, search: 'Option 1' });
261
+
262
+ const filteredOptions = wrapper.vm.filteredOptions;
263
+ expect(filteredOptions.length).toBe(1);
264
+ expect(filteredOptions[0].label).toBe('Option 1');
265
+ });
266
+
267
+ test('filters options by both label and value', async () => {
268
+ await wrapper.setProps({ enableSearch: true, search: 'option1' });
269
+
270
+ const filteredOptions = wrapper.vm.filteredOptions;
271
+ expect(filteredOptions.length).toBe(1);
272
+ expect(filteredOptions[0].value).toBe('option1');
273
+ });
274
+
275
+ test('shows all options when search is empty', async () => {
276
+ await wrapper.setProps({ enableSearch: true, search: '' });
277
+
278
+ const filteredOptions = wrapper.vm.filteredOptions;
279
+ expect(filteredOptions.length).toBe(3);
280
+ });
281
+ });
282
+
283
+ describe('computed properties', () => {
284
+ test('calculatedMaxHeight returns correct value', () => {
285
+ const maxHeight = wrapper.vm.calculatedMaxHeight;
286
+ expect(maxHeight).toBe('228px');
287
+ });
288
+
289
+ test('calculatedMaxHeight includes search height when enabled', async () => {
290
+ await wrapper.setProps({ enableSearch: true });
291
+ const maxHeight = wrapper.vm.calculatedMaxHeight;
292
+ expect(maxHeight).toBe('295px');
293
+ });
294
+
295
+ test('calculatedMaxHeight returns unset when no options', async () => {
296
+ await wrapper.setProps({ options: [] });
297
+ const maxHeight = wrapper.vm.calculatedMaxHeight;
298
+ expect(maxHeight).toBe('unset');
299
+ });
300
+
301
+ test('selectedItems returns correct items for returnObject false', async () => {
302
+ await wrapper.setProps({ modelValue: ['option2'] });
303
+ const selectedItems = wrapper.vm.selectedItems;
304
+ expect(selectedItems.length).toBe(1);
305
+ expect(selectedItems[0].label).toBe('Option 2');
306
+ expect(selectedItems[0].value).toBe('option2');
307
+ });
308
+
309
+ test('selectedItems returns multiple items', async () => {
310
+ await wrapper.setProps({ modelValue: ['option1', 'option2'] });
311
+ const selectedItems = wrapper.vm.selectedItems;
312
+ expect(selectedItems.length).toBe(2);
313
+ expect(selectedItems[0].label).toBe('Option 1');
314
+ expect(selectedItems[1].label).toBe('Option 2');
315
+ });
316
+
317
+ test('selectedItems returns modelValue for returnObject true', async () => {
318
+ const selectedOptions = [
319
+ { label: 'Option 2', value: 'option2' },
320
+ { label: 'Option 3', value: 'option3' },
321
+ ];
322
+ await wrapper.setProps({
323
+ returnObject: true,
324
+ modelValue: selectedOptions,
325
+ });
326
+ const selectedItems = wrapper.vm.selectedItems;
327
+ expect(selectedItems).toStrictEqual(selectedOptions);
328
+ });
329
+
330
+ test('selectedItems handles returnObject false with array of values', async () => {
331
+ await wrapper.setProps({
332
+ returnObject: false,
333
+ modelValue: ['option1', 'option2'],
334
+ });
335
+ const selectedItems = wrapper.vm.selectedItems;
336
+ expect(selectedItems.length).toBe(2);
337
+ expect(selectedItems[0].value).toBe('option1');
338
+ expect(selectedItems[1].value).toBe('option2');
339
+ });
340
+
341
+ test('selectedItems with returnObject true and array of objects in modelValue', async () => {
342
+ const modelValueObjects = [
343
+ { label: 'Option 1', value: 'option1' },
344
+ { label: 'Option 2', value: 'option2' },
345
+ ];
346
+ await wrapper.setProps({
347
+ returnObject: true,
348
+ modelValue: modelValueObjects,
349
+ });
350
+ const selectedItems = wrapper.vm.selectedItems;
351
+
352
+ expect(selectedItems).toStrictEqual(modelValueObjects);
353
+ });
354
+
355
+ test('selectedItems handles modelValue as array of objects when returnObject is false', async () => {
356
+ const optionsWithCustomKeys = [
357
+ { name: 'Custom 1', id: 'custom1' },
358
+ { name: 'Custom 2', id: 'custom2' },
359
+ ];
360
+ await wrapper.setProps({
361
+ returnObject: false,
362
+ options: optionsWithCustomKeys,
363
+ itemLabel: 'name',
364
+ itemValue: 'id',
365
+ modelValue: ['custom1'],
366
+ });
367
+ const selectedItems = wrapper.vm.selectedItems;
368
+
369
+ expect(selectedItems.length).toBe(1);
370
+ expect(selectedItems[0].id).toBe('custom1');
371
+ expect(selectedItems[0].name).toBe('Custom 1');
372
+ });
373
+
374
+ test('inputValue returns correct labels separated by comma', async () => {
375
+ await wrapper.setProps({ modelValue: ['option3'] });
376
+ const inputValue = wrapper.vm.inputValue;
377
+ expect(inputValue).toBe('Option 3');
378
+ });
379
+
380
+ test('inputValue returns multiple labels separated by comma', async () => {
381
+ await wrapper.setProps({ modelValue: ['option1', 'option2', 'option3'] });
382
+ const inputValue = wrapper.vm.inputValue;
383
+ expect(inputValue).toBe('Option 1, Option 2, Option 3');
384
+ });
385
+
386
+ test('inputValue returns empty string when no selection', () => {
387
+ const inputValue = wrapper.vm.inputValue;
388
+ expect(inputValue).toBe('');
389
+ });
390
+ });
391
+
392
+ describe('disabled state', () => {
393
+ test('passes disabled prop to input', async () => {
394
+ await wrapper.setProps({ disabled: true });
395
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
396
+ expect(input.props('disabled')).toBe(true);
397
+ });
398
+
399
+ test('input is not disabled by default', () => {
400
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
401
+ expect(input.props('disabled')).toBe(false);
402
+ });
403
+ });
404
+
405
+ describe('size prop', () => {
406
+ test('passes size prop to input', async () => {
407
+ await wrapper.setProps({ size: 'sm' });
408
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
409
+ expect(input.props('size')).toBe('sm');
410
+ });
411
+
412
+ test('defaults to md size', () => {
413
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
414
+ expect(input.props('size')).toBe('md');
415
+ });
416
+ });
417
+
418
+ describe('label and message props', () => {
419
+ test('passes label prop to input', async () => {
420
+ await wrapper.setProps({ label: 'Select Label' });
421
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
422
+ expect(input.props('label')).toBe('Select Label');
423
+ });
424
+
425
+ test('passes message prop to input', async () => {
426
+ await wrapper.setProps({ message: 'Select Message' });
427
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
428
+ expect(input.props('message')).toBe('Select Message');
429
+ });
430
+
431
+ test('passes errors prop to input', async () => {
432
+ await wrapper.setProps({ errors: 'Error message' });
433
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
434
+ expect(input.props('errors')).toBe('Error message');
435
+ });
436
+ });
437
+
438
+ describe('custom item keys', () => {
439
+ test('uses custom itemLabel and itemValue', async () => {
440
+ const customOptions = [
441
+ { name: 'Custom 1', id: 'custom1' },
442
+ { name: 'Custom 2', id: 'custom2' },
443
+ ];
444
+
445
+ await wrapper.setProps({
446
+ options: customOptions,
447
+ itemLabel: 'name',
448
+ itemValue: 'id',
449
+ modelValue: 'custom1',
450
+ });
451
+
452
+ const inputValue = wrapper.vm.inputValue;
453
+ expect(inputValue).toBe('Custom 1');
454
+ });
455
+ });
456
+
457
+ describe('edge cases', () => {
458
+ test('handles empty options array', async () => {
459
+ await wrapper.setProps({ options: [] });
460
+ expect(wrapper.vm.filteredOptions).toEqual([]);
461
+ expect(wrapper.vm.calculatedMaxHeight).toBe('unset');
462
+ });
463
+
464
+ test('handles empty modelValue array', async () => {
465
+ await wrapper.setProps({ modelValue: [] });
466
+ expect(wrapper.vm.inputValue).toBe('');
467
+ expect(wrapper.vm.selectedItems).toEqual([]);
468
+ });
469
+
470
+ test('handles case insensitive search', async () => {
471
+ await wrapper.setProps({ enableSearch: true, search: 'OPTION 1' });
472
+ const filteredOptions = wrapper.vm.filteredOptions;
473
+ expect(filteredOptions.length).toBe(1);
474
+ expect(filteredOptions[0].label).toBe('Option 1');
475
+ });
476
+
477
+ test('handles partial search matches', async () => {
478
+ await wrapper.setProps({ enableSearch: true, search: 'tion' });
479
+ const filteredOptions = wrapper.vm.filteredOptions;
480
+ expect(filteredOptions.length).toBe(3);
481
+ });
482
+ });
483
+
484
+ describe('component structure', () => {
485
+ test('has correct component name', () => {
486
+ expect(wrapper.vm.$options.name).toBe('UnnnicMultiSelect');
487
+ });
488
+ });
489
+
490
+ describe('clear functionality', () => {
491
+ test('emits empty array when clear button is clicked', async () => {
492
+ await wrapper.setProps({ modelValue: ['option1', 'option2'] });
493
+ const input = wrapper.findComponent({ name: 'UnnnicInput' });
494
+
495
+ await input.vm.$emit('clear');
496
+
497
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy();
498
+ expect(wrapper.emitted('update:modelValue')[0]).toEqual([[]]);
499
+ });
500
+ });
501
+
502
+ describe('getActivatedOptionStatus', () => {
503
+ test('returns true when option is in modelValue array', async () => {
504
+ await wrapper.setProps({ modelValue: ['option1'] });
505
+ wrapper.vm.openPopover = true;
506
+ await wrapper.vm.$nextTick();
507
+ const options = wrapper.findAllComponents({
508
+ name: 'UnnnicMultiSelectOption',
509
+ });
510
+
511
+ expect(options[0].props('active')).toBe(true);
512
+ expect(options[1].props('active')).toBe(false);
513
+ });
514
+
515
+ test('returns true when option object is in modelValue array with returnObject true', async () => {
516
+ await wrapper.setProps({
517
+ returnObject: true,
518
+ modelValue: [{ label: 'Option 1', value: 'option1' }],
519
+ });
520
+ wrapper.vm.openPopover = true;
521
+ await wrapper.vm.$nextTick();
522
+ const options = wrapper.findAllComponents({
523
+ name: 'UnnnicMultiSelectOption',
524
+ });
525
+
526
+ expect(options[0].props('active')).toBeTruthy();
527
+ expect(options[1].props('active')).toBeFalsy();
528
+ });
529
+ });
530
+
531
+ describe('snapshot testing', () => {
532
+ test('matches snapshot with default props', () => {
533
+ expect(wrapper.html()).toMatchSnapshot();
534
+ });
535
+
536
+ test('matches snapshot with selected values', async () => {
537
+ await wrapper.setProps({ modelValue: ['option1'] });
538
+ expect(wrapper.html()).toMatchSnapshot();
539
+ });
540
+
541
+ test('matches snapshot with multiple selected values', async () => {
542
+ await wrapper.setProps({ modelValue: ['option1', 'option2'] });
543
+ expect(wrapper.html()).toMatchSnapshot();
544
+ });
545
+
546
+ test('matches snapshot with search enabled', async () => {
547
+ await wrapper.setProps({ enableSearch: true });
548
+ expect(wrapper.html()).toMatchSnapshot();
549
+ });
550
+
551
+ test('matches snapshot with disabled state', async () => {
552
+ await wrapper.setProps({ disabled: true });
553
+ expect(wrapper.html()).toMatchSnapshot();
554
+ });
555
+ });
556
+ });