@weni/unnnic-system 3.28.2-alpha.2 → 3.29.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/index.d.ts +4 -0
- package/dist/style.css +1 -1
- package/dist/unnnic.mjs +3005 -3001
- package/dist/unnnic.umd.js +22 -22
- package/package.json +2 -2
- package/src/components/Button/__tests__/Button.spec.js +81 -0
- package/src/components/Button/__tests__/ButtonIcon.spec.js +56 -0
- package/src/components/Carousel/TagCarousel.vue +8 -2
- package/src/components/Carousel/__tests__/TagCarousel.spec.js +258 -0
- package/src/components/ChartLine/__tests__/ChartLine.spec.js +170 -0
- package/src/components/Checkbox/__tests__/Checkbox.spec.js +14 -0
- package/src/components/CheckboxGroup/__tests__/CheckboxGroup.spec.js +62 -0
- package/src/components/DatePicker/DatePicker.vue +1 -0
- package/src/components/DatePicker/__tests__/DatePicker.spec.js +364 -0
- package/src/components/Disclaimer/__tests__/Disclaimer.spec.js +16 -0
- package/src/components/DropArea/DropArea.vue +1 -2
- package/src/components/DropArea/__tests__/DropArea.spec.js +333 -0
- package/src/components/MultiSelect/__tests__/MultiSelect.spec.js +109 -2
- package/src/components/MultiSelect/index.vue +1 -1
- package/src/components/PageHeader/__tests__/PageHeader.spec.js +97 -0
- package/src/components/Pagination/__tests__/Pagination.spec.js +99 -0
- package/src/components/Radio/__test__/Radio.spec.js +67 -0
- package/src/components/RadioGroup/__tests__/RadioGroup.spec.js +100 -0
- package/src/components/Slider/__tests__/Slider.spec.js +321 -0
- package/src/components/Tag/DefaultTag.vue +4 -3
- package/src/components/Tag/__tests__/DefaultTag.spec.js +51 -0
- package/src/components/Tag/__tests__/Tag.spec.js +82 -0
- package/src/components/TextArea/__test__/TextArea.spec.js +19 -0
- package/src/components/ToolTip/__tests__/ToolTip.spec.js +82 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { mount } from '@vue/test-utils';
|
|
2
2
|
import { describe, test, expect, beforeEach, afterEach } from 'vitest';
|
|
3
3
|
import Radio from '@/components/Radio/Radio.vue';
|
|
4
|
+
import RadioGroup from '@/components/RadioGroup/RadioGroup.vue';
|
|
4
5
|
import UnnnicIcon from '@/components/Icon.vue';
|
|
5
6
|
|
|
6
7
|
describe('Radio.vue', () => {
|
|
@@ -75,4 +76,70 @@ describe('Radio.vue', () => {
|
|
|
75
76
|
test('matches the snapshot', () => {
|
|
76
77
|
expect(wrapper.html()).toMatchSnapshot();
|
|
77
78
|
});
|
|
79
|
+
|
|
80
|
+
test('renders helper text when provided', async () => {
|
|
81
|
+
await wrapper.setProps({ helper: 'Radio helper' });
|
|
82
|
+
expect(wrapper.find('.unnnic-radio__helper').text()).toBe('Radio helper');
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test('renders label prop', async () => {
|
|
86
|
+
await wrapper.setProps({ label: 'Radio label' });
|
|
87
|
+
expect(wrapper.find('.unnnic-radio__label').text()).toContain(
|
|
88
|
+
'Radio label',
|
|
89
|
+
);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test('uses name prop when radio is standalone', () => {
|
|
93
|
+
const standalone = mount(Radio, {
|
|
94
|
+
props: {
|
|
95
|
+
modelValue: '',
|
|
96
|
+
value: 'option1',
|
|
97
|
+
name: 'standalone-radio',
|
|
98
|
+
},
|
|
99
|
+
global: { components: { UnnnicIcon } },
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
expect(standalone.find('input').attributes('name')).toBe(
|
|
103
|
+
'standalone-radio',
|
|
104
|
+
);
|
|
105
|
+
standalone.unmount();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('uses injected name when inside RadioGroup without name prop', () => {
|
|
109
|
+
const groupWrapper = mount(RadioGroup, {
|
|
110
|
+
props: { modelValue: '', name: 'group-name' },
|
|
111
|
+
slots: {
|
|
112
|
+
default: '<Radio value="option1" label="Option 1" />',
|
|
113
|
+
},
|
|
114
|
+
global: {
|
|
115
|
+
components: { Radio },
|
|
116
|
+
stubs: { UnnnicLabel: true },
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
expect(groupWrapper.find('input[type="radio"]').attributes('name')).toBe(
|
|
121
|
+
'group-name',
|
|
122
|
+
);
|
|
123
|
+
groupWrapper.unmount();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
test('updates group context value when used inside RadioGroup', async () => {
|
|
127
|
+
const groupWrapper = mount(RadioGroup, {
|
|
128
|
+
props: { modelValue: '' },
|
|
129
|
+
slots: {
|
|
130
|
+
default: '<Radio value="option1" label="Option 1" />',
|
|
131
|
+
},
|
|
132
|
+
global: {
|
|
133
|
+
components: { Radio },
|
|
134
|
+
stubs: {
|
|
135
|
+
UnnnicLabel: true,
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
await groupWrapper.find('input[type="radio"]').trigger('change');
|
|
141
|
+
|
|
142
|
+
expect(groupWrapper.emitted('update:modelValue')[0]).toEqual(['option1']);
|
|
143
|
+
groupWrapper.unmount();
|
|
144
|
+
});
|
|
78
145
|
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
|
|
4
|
+
import RadioGroup from '../RadioGroup.vue';
|
|
5
|
+
import Radio from '../../Radio/Radio.vue';
|
|
6
|
+
|
|
7
|
+
const mountRadioGroup = (props = {}, slots = {}) =>
|
|
8
|
+
mount(RadioGroup, {
|
|
9
|
+
props: {
|
|
10
|
+
modelValue: '',
|
|
11
|
+
...props,
|
|
12
|
+
},
|
|
13
|
+
slots: {
|
|
14
|
+
default: `
|
|
15
|
+
<Radio value="a" label="Option A" />
|
|
16
|
+
<Radio value="b" label="Option B" />
|
|
17
|
+
`,
|
|
18
|
+
...slots,
|
|
19
|
+
},
|
|
20
|
+
global: {
|
|
21
|
+
components: { Radio },
|
|
22
|
+
stubs: {
|
|
23
|
+
UnnnicLabel: {
|
|
24
|
+
props: ['label', 'tooltip'],
|
|
25
|
+
template:
|
|
26
|
+
'<label data-testid="radio-group-label">{{ label }}</label>',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('RadioGroup', () => {
|
|
33
|
+
it('renders radios in slot', () => {
|
|
34
|
+
const wrapper = mountRadioGroup();
|
|
35
|
+
|
|
36
|
+
expect(wrapper.findAllComponents(Radio).length).toBe(2);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('renders label and helper when provided', () => {
|
|
40
|
+
const wrapper = mountRadioGroup({
|
|
41
|
+
label: 'Choose one',
|
|
42
|
+
helper: 'Select an option',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
expect(wrapper.find('[data-testid="radio-group-label"]').text()).toBe(
|
|
46
|
+
'Choose one',
|
|
47
|
+
);
|
|
48
|
+
expect(wrapper.find('.unnnic-radio-group__helper').text()).toBe(
|
|
49
|
+
'Select an option',
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it('emits update:modelValue when a radio is selected', async () => {
|
|
54
|
+
const wrapper = mountRadioGroup();
|
|
55
|
+
|
|
56
|
+
await wrapper.findAll('input[type="radio"]')[0].trigger('change');
|
|
57
|
+
|
|
58
|
+
expect(wrapper.emitted('update:modelValue')).toBeTruthy();
|
|
59
|
+
expect(wrapper.emitted('update:modelValue')[0]).toEqual(['a']);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('syncs modelValue from parent', async () => {
|
|
63
|
+
const wrapper = mountRadioGroup({ modelValue: 'b' });
|
|
64
|
+
|
|
65
|
+
await wrapper.setProps({ modelValue: 'a' });
|
|
66
|
+
|
|
67
|
+
const inputs = wrapper.findAll('input[type="radio"]');
|
|
68
|
+
expect(inputs[0].element.checked).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('does not emit when modelValue is already in sync', async () => {
|
|
72
|
+
const wrapper = mountRadioGroup({ modelValue: 'a' });
|
|
73
|
+
|
|
74
|
+
await wrapper.setProps({ modelValue: 'a' });
|
|
75
|
+
|
|
76
|
+
expect(wrapper.emitted('update:modelValue') ?? []).toHaveLength(0);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('uses custom name when provided', () => {
|
|
80
|
+
const wrapper = mountRadioGroup({ name: 'custom-group' });
|
|
81
|
+
|
|
82
|
+
expect(wrapper.find('input[type="radio"]').attributes('name')).toBe(
|
|
83
|
+
'custom-group',
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('generates a random group name when name is not provided', () => {
|
|
88
|
+
const wrapper = mountRadioGroup();
|
|
89
|
+
|
|
90
|
+
expect(wrapper.find('input[type="radio"]').attributes('name')).toMatch(
|
|
91
|
+
/^unnnic-radio-group-/,
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it('applies vertical state class', () => {
|
|
96
|
+
const wrapper = mountRadioGroup({ state: 'vertical' });
|
|
97
|
+
|
|
98
|
+
expect(wrapper.classes()).toContain('unnnic-radio-group--state-vertical');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
|
|
4
|
+
import Slider from '../Slider.vue';
|
|
5
|
+
|
|
6
|
+
const tooltipStub = {
|
|
7
|
+
name: 'UnnnicTooltip',
|
|
8
|
+
template: '<div><div ref="label" class="tooltip-label"><slot /></div></div>',
|
|
9
|
+
mounted() {
|
|
10
|
+
Object.defineProperty(this.$refs.label, 'clientWidth', {
|
|
11
|
+
configurable: true,
|
|
12
|
+
value: 32,
|
|
13
|
+
});
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const noLabelTooltipStub = {
|
|
18
|
+
name: 'UnnnicTooltip',
|
|
19
|
+
template: '<div><slot /></div>',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const mockInputDimensions = (wrapper, width = 200) => {
|
|
23
|
+
const input = wrapper.find('input[type="range"]').element;
|
|
24
|
+
|
|
25
|
+
Object.defineProperty(input, 'clientWidth', {
|
|
26
|
+
configurable: true,
|
|
27
|
+
value: width,
|
|
28
|
+
});
|
|
29
|
+
Object.defineProperty(input, 'offsetWidth', {
|
|
30
|
+
configurable: true,
|
|
31
|
+
value: width,
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const createWrapper = async (props = {}, options = {}) => {
|
|
36
|
+
const wrapper = mount(Slider, {
|
|
37
|
+
props: {
|
|
38
|
+
initialValue: 2,
|
|
39
|
+
minValue: 0,
|
|
40
|
+
maxValue: 10,
|
|
41
|
+
...props,
|
|
42
|
+
},
|
|
43
|
+
attachTo: document.body,
|
|
44
|
+
global: {
|
|
45
|
+
stubs: {
|
|
46
|
+
UnnnicTooltip: tooltipStub,
|
|
47
|
+
UnnnicIcon: true,
|
|
48
|
+
...options.stubs,
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
mockInputDimensions(wrapper);
|
|
54
|
+
await wrapper.vm.$nextTick();
|
|
55
|
+
|
|
56
|
+
return wrapper;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
describe('Slider', () => {
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
vi.useFakeTimers();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
afterEach(() => {
|
|
65
|
+
vi.useRealTimers();
|
|
66
|
+
document.body.innerHTML = '';
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('renders label, info tooltip, and min/max labels', async () => {
|
|
70
|
+
const wrapper = await createWrapper({
|
|
71
|
+
label: 'Volume',
|
|
72
|
+
labelInfo: 'Adjust volume',
|
|
73
|
+
minLabel: 'Low',
|
|
74
|
+
maxLabel: 'High',
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
expect(wrapper.find('.unnnic-slider__label').text()).toBe('Volume');
|
|
78
|
+
expect(wrapper.find('.unnnic-label__tooltip').exists()).toBe(true);
|
|
79
|
+
expect(wrapper.find('.unnnic-slider__content__labels__min').text()).toBe(
|
|
80
|
+
'Low',
|
|
81
|
+
);
|
|
82
|
+
expect(wrapper.find('.unnnic-slider__content__labels__max').text()).toBe(
|
|
83
|
+
'High',
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('hides value input when showInputValue is false', async () => {
|
|
88
|
+
const wrapper = await createWrapper({ showInputValue: false });
|
|
89
|
+
|
|
90
|
+
expect(wrapper.find('.value-input').exists()).toBe(false);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('emits valueChange when range input changes', async () => {
|
|
94
|
+
const wrapper = await createWrapper();
|
|
95
|
+
|
|
96
|
+
const rangeInput = wrapper.find('input[type="range"]');
|
|
97
|
+
await rangeInput.setValue(5);
|
|
98
|
+
await rangeInput.trigger('change');
|
|
99
|
+
|
|
100
|
+
expect(wrapper.emitted('valueChange')).toBeTruthy();
|
|
101
|
+
expect(wrapper.emitted('valueChange').at(-1)).toEqual(['5']);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('updates value from the text input', async () => {
|
|
105
|
+
const wrapper = await createWrapper();
|
|
106
|
+
|
|
107
|
+
const valueInput = wrapper.find('.value-input');
|
|
108
|
+
await valueInput.setValue('7');
|
|
109
|
+
|
|
110
|
+
expect(wrapper.vm.sliderVal).toBe('7');
|
|
111
|
+
expect(wrapper.emitted('valueChange')).toBeTruthy();
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('shows tooltip on mouseover and hides on mouseleave', async () => {
|
|
115
|
+
const wrapper = await createWrapper();
|
|
116
|
+
|
|
117
|
+
const rangeInput = wrapper.find('input[type="range"]');
|
|
118
|
+
await rangeInput.trigger('mouseover');
|
|
119
|
+
expect(wrapper.vm.showTooltip).toBe(true);
|
|
120
|
+
|
|
121
|
+
await rangeInput.trigger('mouseleave');
|
|
122
|
+
expect(wrapper.vm.showTooltip).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('computes css variables from current state', async () => {
|
|
126
|
+
const wrapper = await createWrapper({
|
|
127
|
+
initialValue: 4,
|
|
128
|
+
minValue: 0,
|
|
129
|
+
maxValue: 8,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(wrapper.vm.cssVars).toEqual({
|
|
133
|
+
'--val': 4,
|
|
134
|
+
'--tooltip-offset': expect.any(Number),
|
|
135
|
+
'--min': 0,
|
|
136
|
+
'--max': 8,
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it('returns zero tooltip offset when dimensions are unavailable', async () => {
|
|
141
|
+
const wrapper = await createWrapper();
|
|
142
|
+
wrapper.vm.sliderWidth = 0;
|
|
143
|
+
wrapper.vm.labelWidth = 0;
|
|
144
|
+
|
|
145
|
+
expect(wrapper.vm.getNewTooltipPosition()).toBe(0);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('handles NaN range when min and max are equal', async () => {
|
|
149
|
+
const wrapper = await createWrapper({
|
|
150
|
+
initialValue: 5,
|
|
151
|
+
minValue: 5,
|
|
152
|
+
maxValue: 5,
|
|
153
|
+
});
|
|
154
|
+
wrapper.vm.sliderWidth = 200;
|
|
155
|
+
wrapper.vm.labelWidth = 32;
|
|
156
|
+
|
|
157
|
+
expect(wrapper.vm.getNewTooltipPosition()).toBeTypeOf('number');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('uses fallback label width when label width is zero', async () => {
|
|
161
|
+
const wrapper = await createWrapper();
|
|
162
|
+
wrapper.vm.sliderWidth = 200;
|
|
163
|
+
wrapper.vm.labelWidth = 0;
|
|
164
|
+
|
|
165
|
+
expect(wrapper.vm.getNewTooltipPosition()).toBe(0);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('reconfigures tooltip on window resize', async () => {
|
|
169
|
+
const wrapper = await createWrapper();
|
|
170
|
+
const configureSpy = vi.spyOn(wrapper.vm, 'configureTooltip');
|
|
171
|
+
|
|
172
|
+
window.dispatchEvent(new Event('resize'));
|
|
173
|
+
|
|
174
|
+
expect(configureSpy).toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('removes resize listener on unmount', async () => {
|
|
178
|
+
const wrapper = await createWrapper();
|
|
179
|
+
const removeSpy = vi.spyOn(window, 'removeEventListener');
|
|
180
|
+
|
|
181
|
+
wrapper.unmount();
|
|
182
|
+
|
|
183
|
+
expect(removeSpy).toHaveBeenCalledWith('resize', wrapper.vm.handleResize);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('initializes dimensions from polling intervals', async () => {
|
|
187
|
+
const wrapper = await createWrapper();
|
|
188
|
+
mockInputDimensions(wrapper, 0);
|
|
189
|
+
|
|
190
|
+
vi.advanceTimersByTime(100);
|
|
191
|
+
await wrapper.vm.$nextTick();
|
|
192
|
+
|
|
193
|
+
mockInputDimensions(wrapper, 200);
|
|
194
|
+
vi.advanceTimersByTime(100);
|
|
195
|
+
await wrapper.vm.$nextTick();
|
|
196
|
+
|
|
197
|
+
expect(wrapper.vm.sliderWidth).toBe(200);
|
|
198
|
+
expect(wrapper.vm.labelWidth).toBe(32);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('skips tooltip label polling when label ref is missing', async () => {
|
|
202
|
+
vi.spyOn(Slider.methods, 'configureTooltip').mockImplementation(() => {});
|
|
203
|
+
|
|
204
|
+
const wrapper = await createWrapper(
|
|
205
|
+
{},
|
|
206
|
+
{ stubs: { UnnnicTooltip: noLabelTooltipStub } },
|
|
207
|
+
);
|
|
208
|
+
mockInputDimensions(wrapper);
|
|
209
|
+
|
|
210
|
+
vi.advanceTimersByTime(100);
|
|
211
|
+
await wrapper.vm.$nextTick();
|
|
212
|
+
|
|
213
|
+
expect(wrapper.vm.labelWidth).toBe(0);
|
|
214
|
+
|
|
215
|
+
Slider.methods.configureTooltip.mockRestore();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('syncs value input text when slider value changes', async () => {
|
|
219
|
+
const wrapper = await createWrapper({ initialValue: 1 });
|
|
220
|
+
|
|
221
|
+
const valueInput = wrapper.find('.value-input').element;
|
|
222
|
+
valueInput.textContent = 'old';
|
|
223
|
+
|
|
224
|
+
wrapper.vm.sliderVal = 9;
|
|
225
|
+
await wrapper.vm.$nextTick();
|
|
226
|
+
await wrapper.vm.$nextTick();
|
|
227
|
+
|
|
228
|
+
expect(valueInput.textContent).toBe('9');
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('skips value input sync when text already matches slider value', async () => {
|
|
232
|
+
const wrapper = await createWrapper({ initialValue: 4 });
|
|
233
|
+
const valueInput = wrapper.find('.value-input').element;
|
|
234
|
+
valueInput.textContent = '4';
|
|
235
|
+
|
|
236
|
+
wrapper.vm.sliderVal = 4;
|
|
237
|
+
await wrapper.vm.$nextTick();
|
|
238
|
+
await wrapper.vm.$nextTick();
|
|
239
|
+
|
|
240
|
+
expect(valueInput.textContent).toBe('4');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('computes tooltip position with non-zero dimensions', async () => {
|
|
244
|
+
const wrapper = await createWrapper({
|
|
245
|
+
initialValue: 5,
|
|
246
|
+
minValue: 0,
|
|
247
|
+
maxValue: 10,
|
|
248
|
+
});
|
|
249
|
+
wrapper.vm.sliderWidth = 200;
|
|
250
|
+
wrapper.vm.labelWidth = 32;
|
|
251
|
+
|
|
252
|
+
expect(wrapper.vm.getNewTooltipPosition()).toBeGreaterThan(0);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('uses fallback label width when label width is NaN', async () => {
|
|
256
|
+
const wrapper = await createWrapper();
|
|
257
|
+
wrapper.vm.sliderWidth = 200;
|
|
258
|
+
wrapper.vm.labelWidth = Number.NaN;
|
|
259
|
+
|
|
260
|
+
expect(wrapper.vm.getNewTooltipPosition()).toBeTypeOf('number');
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('ignores input polling after unmount', async () => {
|
|
264
|
+
const wrapper = await createWrapper();
|
|
265
|
+
mockInputDimensions(wrapper, 0);
|
|
266
|
+
|
|
267
|
+
wrapper.unmount();
|
|
268
|
+
vi.advanceTimersByTime(200);
|
|
269
|
+
|
|
270
|
+
expect(true).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('waits until tooltip label width is greater than zero', async () => {
|
|
274
|
+
const delayedLabelTooltipStub = {
|
|
275
|
+
name: 'UnnnicTooltip',
|
|
276
|
+
template:
|
|
277
|
+
'<div><div ref="label" class="tooltip-label"><slot /></div></div>',
|
|
278
|
+
mounted() {
|
|
279
|
+
Object.defineProperty(this.$refs.label, 'clientWidth', {
|
|
280
|
+
configurable: true,
|
|
281
|
+
value: 0,
|
|
282
|
+
});
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
vi.spyOn(Slider.methods, 'configureTooltip').mockImplementation(
|
|
287
|
+
function mockConfigure() {
|
|
288
|
+
if (!this.$refs.input || !this.$refs.tooltip?.$refs?.label) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
this.sliderWidth = this.$refs.input.clientWidth;
|
|
293
|
+
this.labelWidth = this.$refs.tooltip.$refs.label.clientWidth;
|
|
294
|
+
this.tooltipOffset = this.getNewTooltipPosition();
|
|
295
|
+
},
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
const wrapper = await createWrapper(
|
|
299
|
+
{},
|
|
300
|
+
{
|
|
301
|
+
stubs: { UnnnicTooltip: delayedLabelTooltipStub },
|
|
302
|
+
},
|
|
303
|
+
);
|
|
304
|
+
mockInputDimensions(wrapper);
|
|
305
|
+
|
|
306
|
+
vi.advanceTimersByTime(100);
|
|
307
|
+
await wrapper.vm.$nextTick();
|
|
308
|
+
expect(wrapper.vm.labelWidth).toBe(0);
|
|
309
|
+
|
|
310
|
+
Object.defineProperty(wrapper.vm.$refs.tooltip.$refs.label, 'clientWidth', {
|
|
311
|
+
configurable: true,
|
|
312
|
+
value: 32,
|
|
313
|
+
});
|
|
314
|
+
vi.advanceTimersByTime(100);
|
|
315
|
+
await wrapper.vm.$nextTick();
|
|
316
|
+
|
|
317
|
+
expect(wrapper.vm.labelWidth).toBe(32);
|
|
318
|
+
|
|
319
|
+
Slider.methods.configureTooltip.mockRestore();
|
|
320
|
+
});
|
|
321
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<section
|
|
2
|
+
<section
|
|
3
|
+
:class="`unnnic-tag unnnic-tag--${size}`"
|
|
4
|
+
:style="{ backgroundColor: color }"
|
|
5
|
+
>
|
|
3
6
|
<section
|
|
4
7
|
v-if="leftIcon"
|
|
5
8
|
class="unnnic-tag__icon"
|
|
@@ -62,8 +65,6 @@ const color = computed(() => {
|
|
|
62
65
|
padding: calc($unnnic-space-1 * 1.5) $unnnic-space-3;
|
|
63
66
|
width: fit-content;
|
|
64
67
|
|
|
65
|
-
background-color: v-bind(color);
|
|
66
|
-
|
|
67
68
|
&--small {
|
|
68
69
|
padding: calc($unnnic-space-1 * 0.5) $unnnic-space-3;
|
|
69
70
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
|
|
4
|
+
import DefaultTag from '../DefaultTag.vue';
|
|
5
|
+
|
|
6
|
+
const schemes = [
|
|
7
|
+
'aux-green',
|
|
8
|
+
'aux-blue',
|
|
9
|
+
'aux-purple',
|
|
10
|
+
'aux-red',
|
|
11
|
+
'aux-pink',
|
|
12
|
+
'aux-orange',
|
|
13
|
+
'aux-yellow',
|
|
14
|
+
'aux-gray',
|
|
15
|
+
'aux-teal',
|
|
16
|
+
'aux-weni',
|
|
17
|
+
'unknown-scheme',
|
|
18
|
+
];
|
|
19
|
+
|
|
20
|
+
describe('DefaultTag', () => {
|
|
21
|
+
schemes.forEach((scheme) => {
|
|
22
|
+
it(`renders with scheme ${scheme}`, () => {
|
|
23
|
+
const wrapper = mount(DefaultTag, {
|
|
24
|
+
props: { text: `Tag ${scheme}`, scheme },
|
|
25
|
+
global: { stubs: { UnnnicIcon: true } },
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
expect(wrapper.find('.unnnic-tag__label').text()).toBe(`Tag ${scheme}`);
|
|
29
|
+
expect(wrapper.element.style.backgroundColor).toBeTruthy();
|
|
30
|
+
wrapper.unmount();
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('renders left icon when provided', () => {
|
|
35
|
+
const wrapper = mount(DefaultTag, {
|
|
36
|
+
props: { text: 'Label', leftIcon: 'info' },
|
|
37
|
+
global: { stubs: { UnnnicIcon: true } },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(wrapper.find('.unnnic-tag__icon').exists()).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('applies small size class', () => {
|
|
44
|
+
const wrapper = mount(DefaultTag, {
|
|
45
|
+
props: { text: 'Label', size: 'small' },
|
|
46
|
+
global: { stubs: { UnnnicIcon: true } },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
expect(wrapper.classes()).toContain('unnnic-tag--small');
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { mount } from '@vue/test-utils';
|
|
3
|
+
|
|
4
|
+
import Tag from '../Tag.vue';
|
|
5
|
+
import DefaultTag from '../DefaultTag.vue';
|
|
6
|
+
import Chip from '../../Chip/Chip.vue';
|
|
7
|
+
|
|
8
|
+
const mountTag = (props = {}) =>
|
|
9
|
+
mount(Tag, {
|
|
10
|
+
props: {
|
|
11
|
+
text: 'Tag label',
|
|
12
|
+
...props,
|
|
13
|
+
},
|
|
14
|
+
global: {
|
|
15
|
+
stubs: {
|
|
16
|
+
UnnnicIcon: true,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
describe('Tag', () => {
|
|
22
|
+
it('renders DefaultTag for default type', () => {
|
|
23
|
+
const wrapper = mountTag({ type: 'default' });
|
|
24
|
+
|
|
25
|
+
expect(wrapper.findComponent(DefaultTag).exists()).toBe(true);
|
|
26
|
+
expect(wrapper.text()).toContain('Tag label');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('renders Chip for brand type', () => {
|
|
30
|
+
const wrapper = mountTag({ type: 'brand' });
|
|
31
|
+
|
|
32
|
+
expect(wrapper.findComponent(Chip).exists()).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('renders Chip when hasCloseIcon is true', () => {
|
|
36
|
+
const wrapper = mountTag({ hasCloseIcon: true });
|
|
37
|
+
|
|
38
|
+
expect(wrapper.findComponent(Chip).exists()).toBe(true);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('emits close when chip with close icon is clicked', async () => {
|
|
42
|
+
const wrapper = mountTag({ hasCloseIcon: true });
|
|
43
|
+
|
|
44
|
+
await wrapper.find('.chip').trigger('click');
|
|
45
|
+
|
|
46
|
+
expect(wrapper.emitted('close')).toBeTruthy();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('emits click for brand type chip', async () => {
|
|
50
|
+
const wrapper = mountTag({ type: 'brand' });
|
|
51
|
+
|
|
52
|
+
await wrapper.find('.chip').trigger('click');
|
|
53
|
+
|
|
54
|
+
expect(wrapper.emitted('click')).toBeTruthy();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('passes small size for next type', () => {
|
|
58
|
+
const wrapper = mountTag({ type: 'next' });
|
|
59
|
+
|
|
60
|
+
expect(wrapper.findComponent(DefaultTag).props('size')).toBe('small');
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('DefaultTag', () => {
|
|
65
|
+
it('renders left icon when provided', () => {
|
|
66
|
+
const wrapper = mount(DefaultTag, {
|
|
67
|
+
props: { text: 'Label', leftIcon: 'info' },
|
|
68
|
+
global: { stubs: { UnnnicIcon: true } },
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
expect(wrapper.find('.unnnic-tag__icon').exists()).toBe(true);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('applies small size class', () => {
|
|
75
|
+
const wrapper = mount(DefaultTag, {
|
|
76
|
+
props: { text: 'Label', size: 'small' },
|
|
77
|
+
global: { stubs: { UnnnicIcon: true } },
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
expect(wrapper.classes()).toContain('unnnic-tag--small');
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -57,6 +57,18 @@ describe('TextArea.vue', () => {
|
|
|
57
57
|
expect(wrapper.emitted('update:modelValue')[0]).toEqual(['new text']);
|
|
58
58
|
});
|
|
59
59
|
|
|
60
|
+
test('applies resize none class binding', async () => {
|
|
61
|
+
await wrapper.setProps({ resize: 'none' });
|
|
62
|
+
expect(wrapper.find('textarea').exists()).toBe(true);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('returns empty error string for non-string non-array errors', async () => {
|
|
66
|
+
await wrapper.setProps({ type: 'error', errors: null });
|
|
67
|
+
expect(
|
|
68
|
+
wrapper.findComponent({ name: 'UnnnicFormElement' }).props('error'),
|
|
69
|
+
).toBe('');
|
|
70
|
+
});
|
|
71
|
+
|
|
60
72
|
test('displays errors when type is error', async () => {
|
|
61
73
|
await wrapper.setProps({ type: 'error', errors: ['Error 1', 'Error 2'] });
|
|
62
74
|
expect(
|
|
@@ -64,6 +76,13 @@ describe('TextArea.vue', () => {
|
|
|
64
76
|
).toBe('Error 1, Error 2');
|
|
65
77
|
});
|
|
66
78
|
|
|
79
|
+
test('displays string error when errors is a string', async () => {
|
|
80
|
+
await wrapper.setProps({ type: 'error', errors: 'Single error' });
|
|
81
|
+
expect(
|
|
82
|
+
wrapper.findComponent({ name: 'UnnnicFormElement' }).props('error'),
|
|
83
|
+
).toBe('Single error');
|
|
84
|
+
});
|
|
85
|
+
|
|
67
86
|
test('applies disabled class and disables textarea when disabled is true', async () => {
|
|
68
87
|
await wrapper.setProps({ disabled: true });
|
|
69
88
|
expect(
|