@datametria/vue-components 1.2.0 → 2.0.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/README.md +548 -657
  2. package/dist/index.es.js +2353 -1364
  3. package/dist/index.umd.js +10 -10
  4. package/dist/vue-components.css +1 -1
  5. package/package.json +102 -98
  6. package/src/components/DatametriaAlert.vue +137 -137
  7. package/src/components/DatametriaAutocomplete.vue +184 -138
  8. package/src/components/DatametriaAvatar.vue +177 -33
  9. package/src/components/DatametriaBadge.vue +98 -98
  10. package/src/components/DatametriaBreadcrumb.vue +21 -21
  11. package/src/components/DatametriaButton.vue +177 -165
  12. package/src/components/DatametriaCard.vue +12 -12
  13. package/src/components/DatametriaCheckbox.vue +8 -8
  14. package/src/components/DatametriaChip.vue +145 -149
  15. package/src/components/DatametriaContainer.vue +4 -4
  16. package/src/components/DatametriaDatePicker.vue +686 -68
  17. package/src/components/DatametriaDivider.vue +13 -13
  18. package/src/components/DatametriaFileUpload.vue +272 -140
  19. package/src/components/DatametriaGrid.vue +3 -3
  20. package/src/components/DatametriaInput.vue +15 -15
  21. package/src/components/DatametriaMenu.vue +604 -619
  22. package/src/components/DatametriaModal.vue +16 -16
  23. package/src/components/DatametriaNavbar.vue +230 -252
  24. package/src/components/DatametriaPasswordInput.vue +430 -0
  25. package/src/components/DatametriaProgress.vue +18 -18
  26. package/src/components/DatametriaRadio.vue +20 -20
  27. package/src/components/DatametriaSelect.vue +15 -15
  28. package/src/components/DatametriaSkeleton.vue +243 -239
  29. package/src/components/DatametriaSlider.vue +395 -407
  30. package/src/components/DatametriaSortableTable.vue +585 -0
  31. package/src/components/DatametriaSpinner.vue +7 -7
  32. package/src/components/DatametriaSwitch.vue +16 -16
  33. package/src/components/DatametriaTable.vue +14 -14
  34. package/src/components/DatametriaTextarea.vue +28 -28
  35. package/src/components/DatametriaTimePicker.vue +285 -285
  36. package/src/components/DatametriaToast.vue +176 -176
  37. package/src/components/DatametriaTooltip.vue +408 -408
  38. package/src/components/__tests__/DatametriaAlert.test.js +35 -35
  39. package/src/components/__tests__/DatametriaAlert.test.ts +190 -0
  40. package/src/components/__tests__/DatametriaAutocomplete.test.ts +180 -0
  41. package/src/components/__tests__/DatametriaAvatar.test.ts +152 -0
  42. package/src/components/__tests__/DatametriaBadge.test.js +29 -29
  43. package/src/components/__tests__/DatametriaBadge.test.ts +167 -0
  44. package/src/components/__tests__/DatametriaBreadcrumb.test.ts +75 -0
  45. package/src/components/__tests__/DatametriaButton.test.js +30 -30
  46. package/src/components/__tests__/DatametriaButton.test.ts +283 -0
  47. package/src/components/__tests__/DatametriaCard.test.ts +201 -0
  48. package/src/components/__tests__/DatametriaCheckbox.test.ts +47 -0
  49. package/src/components/__tests__/DatametriaChip.test.js +38 -38
  50. package/src/components/__tests__/DatametriaContainer.test.ts +52 -0
  51. package/src/components/__tests__/DatametriaDatePicker.test.ts +234 -0
  52. package/src/components/__tests__/DatametriaDivider.test.ts +54 -0
  53. package/src/components/__tests__/DatametriaFileUpload.test.ts +291 -0
  54. package/src/components/__tests__/DatametriaGrid.test.ts +31 -0
  55. package/src/components/__tests__/DatametriaInput.test.ts +72 -0
  56. package/src/components/__tests__/DatametriaMenu.test.ts +366 -0
  57. package/src/components/__tests__/DatametriaModal.test.ts +86 -0
  58. package/src/components/__tests__/DatametriaNavbar.test.js +48 -48
  59. package/src/components/__tests__/DatametriaNavbar.test.ts +203 -0
  60. package/src/components/__tests__/DatametriaPasswordInput.test.js +305 -0
  61. package/src/components/__tests__/DatametriaProgress.test.ts +90 -0
  62. package/src/components/__tests__/DatametriaRadio.test.ts +77 -0
  63. package/src/components/__tests__/DatametriaSelect.test.ts +77 -0
  64. package/src/components/__tests__/DatametriaSlider.test.ts +261 -0
  65. package/src/components/__tests__/DatametriaSortableTable.test.js +168 -0
  66. package/src/components/__tests__/DatametriaSpinner.test.ts +156 -0
  67. package/src/components/__tests__/DatametriaSwitch.test.ts +64 -0
  68. package/src/components/__tests__/DatametriaTable.test.ts +97 -0
  69. package/src/components/__tests__/DatametriaTextarea.test.ts +66 -0
  70. package/src/components/__tests__/DatametriaToast.test.js +48 -48
  71. package/src/components/__tests__/DatametriaToast.test.ts +99 -0
  72. package/src/composables/useAccessibilityScale.ts +94 -94
  73. package/src/composables/useBreakpoints.ts +82 -82
  74. package/src/composables/useHapticFeedback.ts +439 -439
  75. package/src/composables/useRipple.ts +218 -218
  76. package/src/index.ts +68 -61
  77. package/src/stories/Variants.stories.js +95 -95
  78. package/src/styles/design-tokens.css +623 -623
  79. package/src/theme/ThemeProvider.vue +96 -0
  80. package/src/theme/__tests__/ThemeProvider.test.ts +208 -0
  81. package/src/theme/__tests__/constants.test.ts +31 -0
  82. package/src/theme/__tests__/presets.test.ts +166 -0
  83. package/src/theme/__tests__/tokens.test.ts +155 -0
  84. package/src/theme/__tests__/types.test.ts +153 -0
  85. package/src/theme/__tests__/useTheme.test.ts +146 -0
  86. package/src/theme/constants.ts +14 -0
  87. package/src/theme/index.ts +12 -0
  88. package/src/theme/presets/datametria.ts +94 -0
  89. package/src/theme/presets/default.ts +94 -0
  90. package/src/theme/presets/index.ts +8 -0
  91. package/src/theme/tokens/colors.ts +28 -0
  92. package/src/theme/tokens/index.ts +47 -0
  93. package/src/theme/tokens/spacing.ts +21 -0
  94. package/src/theme/tokens/typography.ts +35 -0
  95. package/src/theme/types.ts +111 -0
  96. package/src/theme/useTheme.ts +28 -0
  97. package/src/types/index.ts +19 -0
@@ -0,0 +1,203 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaNavbar from '../DatametriaNavbar.vue'
4
+
5
+ describe('DatametriaNavbar', () => {
6
+ describe('Renderização', () => {
7
+ it('renderiza corretamente', () => {
8
+ const wrapper = mount(DatametriaNavbar)
9
+ expect(wrapper.find('.dm-navbar').exists()).toBe(true)
10
+ })
11
+
12
+ it('renderiza brand text', () => {
13
+ const wrapper = mount(DatametriaNavbar, {
14
+ props: { brand: 'DATAMETRIA' }
15
+ })
16
+ expect(wrapper.text()).toContain('DATAMETRIA')
17
+ })
18
+
19
+ it('renderiza slot brand customizado', () => {
20
+ const wrapper = mount(DatametriaNavbar, {
21
+ slots: { brand: '<div class="custom-brand">Custom</div>' }
22
+ })
23
+ expect(wrapper.find('.custom-brand').exists()).toBe(true)
24
+ })
25
+
26
+ it('renderiza slot menu', () => {
27
+ const wrapper = mount(DatametriaNavbar, {
28
+ slots: { menu: '<div class="menu-items">Items</div>' }
29
+ })
30
+ expect(wrapper.find('.menu-items').exists()).toBe(true)
31
+ })
32
+
33
+ it('renderiza slot actions', () => {
34
+ const wrapper = mount(DatametriaNavbar, {
35
+ slots: { actions: '<button>Login</button>' }
36
+ })
37
+ expect(wrapper.find('.dm-navbar__actions').exists()).toBe(true)
38
+ })
39
+ })
40
+
41
+ describe('Variants', () => {
42
+ it('aplica variant light por padrão', () => {
43
+ const wrapper = mount(DatametriaNavbar)
44
+ expect(wrapper.find('.dm-navbar').classes()).toContain('dm-navbar--light')
45
+ })
46
+
47
+ it('aplica variant dark', () => {
48
+ const wrapper = mount(DatametriaNavbar, {
49
+ props: { variant: 'dark' }
50
+ })
51
+ expect(wrapper.find('.dm-navbar').classes()).toContain('dm-navbar--dark')
52
+ })
53
+
54
+ it('aplica variant primary', () => {
55
+ const wrapper = mount(DatametriaNavbar, {
56
+ props: { variant: 'primary' }
57
+ })
58
+ expect(wrapper.find('.dm-navbar').classes()).toContain('dm-navbar--primary')
59
+ })
60
+
61
+ it('aplica variant transparent', () => {
62
+ const wrapper = mount(DatametriaNavbar, {
63
+ props: { variant: 'transparent' }
64
+ })
65
+ expect(wrapper.find('.dm-navbar').classes()).toContain('dm-navbar--transparent')
66
+ })
67
+ })
68
+
69
+ describe('Props', () => {
70
+ it('aceita sticky', () => {
71
+ const wrapper = mount(DatametriaNavbar, {
72
+ props: { sticky: true }
73
+ })
74
+ expect(wrapper.find('.dm-navbar').classes()).toContain('dm-navbar--sticky')
75
+ })
76
+
77
+ it('aceita transparent', () => {
78
+ const wrapper = mount(DatametriaNavbar, {
79
+ props: { transparent: true }
80
+ })
81
+ expect(wrapper.find('.dm-navbar').classes()).toContain('dm-navbar--transparent')
82
+ })
83
+
84
+ it('aceita bordered', () => {
85
+ const wrapper = mount(DatametriaNavbar, {
86
+ props: { bordered: true }
87
+ })
88
+ expect(wrapper.find('.dm-navbar').classes()).toContain('dm-navbar--bordered')
89
+ })
90
+
91
+ it('aceita ariaLabel customizado', () => {
92
+ const wrapper = mount(DatametriaNavbar, {
93
+ props: { ariaLabel: 'Custom navigation' }
94
+ })
95
+ expect(wrapper.find('nav').attributes('aria-label')).toBe('Custom navigation')
96
+ })
97
+ })
98
+
99
+ describe('Mobile Menu', () => {
100
+ it('mostra toggle quando há slot menu', () => {
101
+ const wrapper = mount(DatametriaNavbar, {
102
+ slots: { menu: '<div>Menu</div>' }
103
+ })
104
+ expect(wrapper.find('.dm-navbar__toggle').exists()).toBe(true)
105
+ })
106
+
107
+ it('não mostra toggle quando não há slot menu', () => {
108
+ const wrapper = mount(DatametriaNavbar)
109
+ expect(wrapper.find('.dm-navbar__toggle').exists()).toBe(false)
110
+ })
111
+
112
+ it('abre menu ao clicar no toggle', async () => {
113
+ const wrapper = mount(DatametriaNavbar, {
114
+ slots: { menu: '<div>Menu</div>' }
115
+ })
116
+
117
+ const toggle = wrapper.find('.dm-navbar__toggle')
118
+ await toggle.trigger('click')
119
+
120
+ expect(wrapper.find('.dm-navbar__menu').classes()).toContain('dm-navbar__menu--open')
121
+ })
122
+
123
+ it('fecha menu ao clicar novamente no toggle', async () => {
124
+ const wrapper = mount(DatametriaNavbar, {
125
+ slots: { menu: '<div>Menu</div>' }
126
+ })
127
+
128
+ const toggle = wrapper.find('.dm-navbar__toggle')
129
+ await toggle.trigger('click')
130
+ await toggle.trigger('click')
131
+
132
+ expect(wrapper.find('.dm-navbar__menu').classes()).not.toContain('dm-navbar__menu--open')
133
+ })
134
+ })
135
+
136
+ describe('Acessibilidade', () => {
137
+ it('tem role navigation', () => {
138
+ const wrapper = mount(DatametriaNavbar)
139
+ expect(wrapper.find('nav').attributes('role')).toBe('navigation')
140
+ })
141
+
142
+ it('tem aria-label padrão', () => {
143
+ const wrapper = mount(DatametriaNavbar)
144
+ expect(wrapper.find('nav').attributes('aria-label')).toBe('Main navigation')
145
+ })
146
+
147
+ it('toggle tem aria-expanded', () => {
148
+ const wrapper = mount(DatametriaNavbar, {
149
+ slots: { menu: '<div>Menu</div>' }
150
+ })
151
+ expect(wrapper.find('.dm-navbar__toggle').attributes('aria-expanded')).toBe('false')
152
+ })
153
+
154
+ it('toggle atualiza aria-expanded quando aberto', async () => {
155
+ const wrapper = mount(DatametriaNavbar, {
156
+ slots: { menu: '<div>Menu</div>' }
157
+ })
158
+
159
+ await wrapper.find('.dm-navbar__toggle').trigger('click')
160
+ expect(wrapper.find('.dm-navbar__toggle').attributes('aria-expanded')).toBe('true')
161
+ })
162
+
163
+ it('toggle tem aria-controls', () => {
164
+ const wrapper = mount(DatametriaNavbar, {
165
+ slots: { menu: '<div>Menu</div>' }
166
+ })
167
+ expect(wrapper.find('.dm-navbar__toggle').attributes('aria-controls')).toBe('navbar-menu')
168
+ })
169
+
170
+ it('menu tem id correto', () => {
171
+ const wrapper = mount(DatametriaNavbar, {
172
+ slots: { menu: '<div>Menu</div>' }
173
+ })
174
+ expect(wrapper.find('.dm-navbar__menu').attributes('id')).toBe('navbar-menu')
175
+ })
176
+ })
177
+
178
+ describe('Validação', () => {
179
+ it('valida variant inválido em desenvolvimento', () => {
180
+ const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {})
181
+ const originalEnv = process.env.NODE_ENV
182
+ process.env.NODE_ENV = 'development'
183
+
184
+ mount(DatametriaNavbar, {
185
+ props: { variant: 'invalid' as any }
186
+ })
187
+
188
+ expect(consoleSpy).toHaveBeenCalledWith(
189
+ expect.stringContaining('[DatametriaNavbar] Invalid variant "invalid"')
190
+ )
191
+
192
+ process.env.NODE_ENV = originalEnv
193
+ consoleSpy.mockRestore()
194
+ })
195
+ })
196
+
197
+ describe('CSS Variables', () => {
198
+ it('usa CSS Variables padronizadas', () => {
199
+ const wrapper = mount(DatametriaNavbar)
200
+ expect(wrapper.find('.dm-navbar').exists()).toBe(true)
201
+ })
202
+ })
203
+ })
@@ -0,0 +1,305 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaPasswordInput from '../DatametriaPasswordInput.vue'
4
+
5
+ describe('DatametriaPasswordInput', () => {
6
+ it('renders correctly', () => {
7
+ const wrapper = mount(DatametriaPasswordInput, {
8
+ props: {
9
+ label: 'Senha'
10
+ }
11
+ })
12
+ expect(wrapper.find('.datametria-password-input').exists()).toBe(true)
13
+ expect(wrapper.find('label').text()).toContain('Senha')
14
+ })
15
+
16
+ it('toggles password visibility', async () => {
17
+ const wrapper = mount(DatametriaPasswordInput)
18
+ const input = wrapper.find('input')
19
+ const toggleButton = wrapper.find('.datametria-password-input__toggle')
20
+
21
+ expect(input.attributes('type')).toBe('password')
22
+
23
+ await toggleButton.trigger('click')
24
+ expect(input.attributes('type')).toBe('text')
25
+
26
+ await toggleButton.trigger('click')
27
+ expect(input.attributes('type')).toBe('password')
28
+ })
29
+
30
+ it('validates password strength', async () => {
31
+ const wrapper = mount(DatametriaPasswordInput, {
32
+ props: {
33
+ modelValue: '',
34
+ showStrength: true
35
+ }
36
+ })
37
+
38
+ // Weak password
39
+ await wrapper.setProps({ modelValue: 'abc' })
40
+ expect(wrapper.find('.datametria-password-input__strength-text--weak').exists()).toBe(true)
41
+
42
+ // Strong password
43
+ await wrapper.setProps({ modelValue: 'Abc123!@#' })
44
+ expect(wrapper.find('.datametria-password-input__strength-text--strong').exists()).toBe(true)
45
+ })
46
+
47
+ it('shows requirements when focused', async () => {
48
+ const wrapper = mount(DatametriaPasswordInput, {
49
+ props: {
50
+ showRequirements: true
51
+ }
52
+ })
53
+
54
+ const input = wrapper.find('input')
55
+
56
+ expect(wrapper.find('.datametria-password-input__requirements').exists()).toBe(false)
57
+
58
+ await input.trigger('focus')
59
+ expect(wrapper.find('.datametria-password-input__requirements').exists()).toBe(true)
60
+
61
+ await input.trigger('blur')
62
+ expect(wrapper.find('.datametria-password-input__requirements').exists()).toBe(false)
63
+ })
64
+
65
+ it('validates all requirements', async () => {
66
+ const wrapper = mount(DatametriaPasswordInput, {
67
+ props: {
68
+ modelValue: 'Abc123!@#',
69
+ showRequirements: true
70
+ }
71
+ })
72
+
73
+ await wrapper.find('input').trigger('focus')
74
+
75
+ const requirements = wrapper.findAll('.datametria-password-input__requirements-list li')
76
+ expect(requirements).toHaveLength(5)
77
+
78
+ // All requirements should be valid
79
+ requirements.forEach(req => {
80
+ expect(req.classes()).toContain('valid')
81
+ })
82
+ })
83
+
84
+ it('emits update:modelValue on input', async () => {
85
+ const wrapper = mount(DatametriaPasswordInput)
86
+ const input = wrapper.find('input')
87
+
88
+ await input.setValue('test123')
89
+ expect(wrapper.emitted('update:modelValue')).toBeTruthy()
90
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['test123'])
91
+ })
92
+
93
+ it('emits strength-change event', async () => {
94
+ const wrapper = mount(DatametriaPasswordInput, {
95
+ props: {
96
+ modelValue: ''
97
+ }
98
+ })
99
+
100
+ await wrapper.setProps({ modelValue: 'Abc123!@#' })
101
+ await wrapper.vm.$nextTick()
102
+
103
+ expect(wrapper.emitted('strength-change')).toBeTruthy()
104
+ expect(wrapper.emitted('strength-change')?.[0]?.[0]).toBe(100)
105
+ })
106
+
107
+ it('shows error message', () => {
108
+ const wrapper = mount(DatametriaPasswordInput, {
109
+ props: {
110
+ errorMessage: 'Senha inválida'
111
+ }
112
+ })
113
+
114
+ expect(wrapper.find('.datametria-password-input__error').text()).toBe('Senha inválida')
115
+ expect(wrapper.find('input').classes()).toContain('datametria-password-input__field--error')
116
+ })
117
+
118
+ it('shows help text', () => {
119
+ const wrapper = mount(DatametriaPasswordInput, {
120
+ props: {
121
+ helpText: 'Use uma senha forte'
122
+ }
123
+ })
124
+
125
+ expect(wrapper.find('.datametria-password-input__help').text()).toBe('Use uma senha forte')
126
+ })
127
+
128
+ it('marks required field', () => {
129
+ const wrapper = mount(DatametriaPasswordInput, {
130
+ props: {
131
+ label: 'Senha',
132
+ required: true
133
+ }
134
+ })
135
+
136
+ expect(wrapper.find('.datametria-password-input__required').exists()).toBe(true)
137
+ expect(wrapper.find('input').attributes('required')).toBeDefined()
138
+ })
139
+
140
+ it('disables input when disabled prop is true', () => {
141
+ const wrapper = mount(DatametriaPasswordInput, {
142
+ props: {
143
+ disabled: true
144
+ }
145
+ })
146
+
147
+ const input = wrapper.find('input')
148
+ expect(input.attributes('disabled')).toBeDefined()
149
+ expect(input.classes()).toContain('datametria-password-input__field--disabled')
150
+ })
151
+
152
+ it('sets correct autocomplete attribute', () => {
153
+ const wrapper = mount(DatametriaPasswordInput, {
154
+ props: {
155
+ autocomplete: 'new-password'
156
+ }
157
+ })
158
+
159
+ expect(wrapper.find('input').attributes('autocomplete')).toBe('new-password')
160
+ })
161
+
162
+ it('has proper ARIA attributes', async () => {
163
+ const wrapper = mount(DatametriaPasswordInput, {
164
+ props: {
165
+ errorMessage: 'Senha inválida',
166
+ showRequirements: true
167
+ }
168
+ })
169
+
170
+ const input = wrapper.find('input')
171
+
172
+ expect(input.attributes('aria-invalid')).toBe('true')
173
+
174
+ await input.trigger('focus')
175
+ expect(input.attributes('aria-describedby')).toBeTruthy()
176
+ })
177
+
178
+ it('toggle button has proper accessibility', () => {
179
+ const wrapper = mount(DatametriaPasswordInput)
180
+ const toggleButton = wrapper.find('.datametria-password-input__toggle')
181
+
182
+ expect(toggleButton.attributes('aria-label')).toBe('Mostrar senha')
183
+ expect(toggleButton.attributes('type')).toBe('button')
184
+ expect(toggleButton.attributes('tabindex')).toBe('-1')
185
+ })
186
+
187
+ describe('CSS Variables Integration', () => {
188
+ it('uses semantic color tokens for strength indicators', async () => {
189
+ const wrapper = mount(DatametriaPasswordInput, {
190
+ props: {
191
+ modelValue: 'Abc123!@#',
192
+ showStrength: true
193
+ }
194
+ })
195
+
196
+ expect(wrapper.find('.datametria-password-input__strength-fill--strong').exists()).toBe(true)
197
+ expect(wrapper.find('.datametria-password-input__strength-text--strong').exists()).toBe(true)
198
+ })
199
+
200
+ it('uses error token for error states', () => {
201
+ const wrapper = mount(DatametriaPasswordInput, {
202
+ props: {
203
+ errorMessage: 'Error'
204
+ }
205
+ })
206
+
207
+ expect(wrapper.find('.datametria-password-input__field--error').exists()).toBe(true)
208
+ expect(wrapper.find('.datametria-password-input__error').exists()).toBe(true)
209
+ })
210
+
211
+ it('uses warning token for caps lock warning', async () => {
212
+ const wrapper = mount(DatametriaPasswordInput)
213
+ const input = wrapper.find('input')
214
+
215
+ const event = new KeyboardEvent('keyup', { key: 'A' })
216
+ Object.defineProperty(event, 'getModifierState', {
217
+ value: vi.fn(() => true)
218
+ })
219
+
220
+ await input.element.dispatchEvent(event)
221
+ await wrapper.vm.$nextTick()
222
+
223
+ expect(wrapper.find('.datametria-password-input__warning').exists()).toBe(true)
224
+ })
225
+
226
+ it('uses success token for valid requirements', async () => {
227
+ const wrapper = mount(DatametriaPasswordInput, {
228
+ props: {
229
+ modelValue: 'Abc123!@#',
230
+ showRequirements: true
231
+ }
232
+ })
233
+
234
+ await wrapper.find('input').trigger('focus')
235
+ const validItems = wrapper.findAll('.datametria-password-input__requirements-list li.valid')
236
+ expect(validItems.length).toBe(5)
237
+ })
238
+
239
+ it('uses spacing tokens consistently', () => {
240
+ const wrapper = mount(DatametriaPasswordInput, {
241
+ props: {
242
+ label: 'Password',
243
+ showRequirements: true
244
+ }
245
+ })
246
+
247
+ expect(wrapper.find('.datametria-password-input').exists()).toBe(true)
248
+ expect(wrapper.find('.datametria-password-input__field').exists()).toBe(true)
249
+ })
250
+
251
+ it('uses typography tokens for text elements', () => {
252
+ const wrapper = mount(DatametriaPasswordInput, {
253
+ props: {
254
+ label: 'Password',
255
+ helpText: 'Help text'
256
+ }
257
+ })
258
+
259
+ expect(wrapper.find('.datametria-password-input__label').exists()).toBe(true)
260
+ expect(wrapper.find('.datametria-password-input__help').exists()).toBe(true)
261
+ })
262
+
263
+ it('uses transition tokens for animations', async () => {
264
+ const wrapper = mount(DatametriaPasswordInput, {
265
+ props: {
266
+ modelValue: '',
267
+ showStrength: true
268
+ }
269
+ })
270
+
271
+ await wrapper.setProps({ modelValue: 'test' })
272
+ expect(wrapper.find('.datametria-password-input__strength-fill').exists()).toBe(true)
273
+ })
274
+ })
275
+
276
+ describe('Backward Compatibility', () => {
277
+ it('works without ThemeProvider', () => {
278
+ const wrapper = mount(DatametriaPasswordInput, {
279
+ props: {
280
+ label: 'Password',
281
+ modelValue: 'Abc123!@#',
282
+ showStrength: true,
283
+ showRequirements: true
284
+ }
285
+ })
286
+
287
+ expect(wrapper.find('.datametria-password-input').exists()).toBe(true)
288
+ expect(wrapper.find('.datametria-password-input__strength').exists()).toBe(true)
289
+ })
290
+
291
+ it('maintains original visual appearance with fallback values', async () => {
292
+ const wrapper = mount(DatametriaPasswordInput, {
293
+ props: {
294
+ modelValue: 'weak',
295
+ showStrength: true
296
+ }
297
+ })
298
+
299
+ expect(wrapper.find('.datametria-password-input__strength-fill--weak').exists()).toBe(true)
300
+
301
+ await wrapper.setProps({ modelValue: 'Abc123!@#' })
302
+ expect(wrapper.find('.datametria-password-input__strength-fill--strong').exists()).toBe(true)
303
+ })
304
+ })
305
+ })
@@ -0,0 +1,90 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaProgress from '../DatametriaProgress.vue'
4
+
5
+ describe('DatametriaProgress', () => {
6
+ it('renders with value', () => {
7
+ const wrapper = mount(DatametriaProgress, {
8
+ props: { value: 50 }
9
+ })
10
+ expect(wrapper.find('.dm-progress').exists()).toBe(true)
11
+ expect(wrapper.find('.dm-progress__bar').attributes('style')).toContain('width: 50%')
12
+ })
13
+
14
+ it('renders with label', () => {
15
+ const wrapper = mount(DatametriaProgress, {
16
+ props: { value: 75, label: 'Loading' }
17
+ })
18
+ expect(wrapper.find('.dm-progress__label').text()).toBe('Loading')
19
+ })
20
+
21
+ it('shows value by default', () => {
22
+ const wrapper = mount(DatametriaProgress, {
23
+ props: { value: 60, label: 'Test' }
24
+ })
25
+ expect(wrapper.find('.dm-progress__value').text()).toBe('60%')
26
+ })
27
+
28
+ it('hides value when showValue is false', () => {
29
+ const wrapper = mount(DatametriaProgress, {
30
+ props: { value: 60, label: 'Test', showValue: false }
31
+ })
32
+ expect(wrapper.find('.dm-progress__value').exists()).toBe(false)
33
+ })
34
+
35
+ it('clamps value to 0-100 range', () => {
36
+ const wrapper1 = mount(DatametriaProgress, { props: { value: -10 } })
37
+ expect(wrapper1.find('.dm-progress__bar').attributes('style')).toContain('width: 0%')
38
+
39
+ const wrapper2 = mount(DatametriaProgress, { props: { value: 150 } })
40
+ expect(wrapper2.find('.dm-progress__bar').attributes('style')).toContain('width: 100%')
41
+ })
42
+
43
+ it('renders primary variant', () => {
44
+ const wrapper = mount(DatametriaProgress, {
45
+ props: { value: 50, variant: 'primary' }
46
+ })
47
+ expect(wrapper.find('.dm-progress__bar--primary').exists()).toBe(true)
48
+ })
49
+
50
+ it('renders success variant', () => {
51
+ const wrapper = mount(DatametriaProgress, {
52
+ props: { value: 50, variant: 'success' }
53
+ })
54
+ expect(wrapper.find('.dm-progress__bar--success').exists()).toBe(true)
55
+ })
56
+
57
+ it('renders warning variant', () => {
58
+ const wrapper = mount(DatametriaProgress, {
59
+ props: { value: 50, variant: 'warning' }
60
+ })
61
+ expect(wrapper.find('.dm-progress__bar--warning').exists()).toBe(true)
62
+ })
63
+
64
+ it('renders error variant', () => {
65
+ const wrapper = mount(DatametriaProgress, {
66
+ props: { value: 50, variant: 'error' }
67
+ })
68
+ expect(wrapper.find('.dm-progress__bar--error').exists()).toBe(true)
69
+ })
70
+
71
+ it('has correct ARIA attributes', () => {
72
+ const wrapper = mount(DatametriaProgress, {
73
+ props: { value: 75, label: 'Progress' }
74
+ })
75
+ const track = wrapper.find('.dm-progress__track')
76
+ expect(track.attributes('role')).toBe('progressbar')
77
+ expect(track.attributes('aria-valuenow')).toBe('75')
78
+ expect(track.attributes('aria-valuemin')).toBe('0')
79
+ expect(track.attributes('aria-valuemax')).toBe('100')
80
+ expect(track.attributes('aria-label')).toBe('Progress')
81
+ })
82
+
83
+ it('works without ThemeProvider', () => {
84
+ const wrapper = mount(DatametriaProgress, {
85
+ props: { value: 80, label: 'Test', variant: 'success' }
86
+ })
87
+ expect(wrapper.find('.dm-progress__bar--success').exists()).toBe(true)
88
+ expect(wrapper.find('.dm-progress__bar').attributes('style')).toContain('width: 80%')
89
+ })
90
+ })
@@ -0,0 +1,77 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import DatametriaRadio from '../DatametriaRadio.vue'
4
+
5
+ describe('DatametriaRadio', () => {
6
+ it('renders correctly', () => {
7
+ const wrapper = mount(DatametriaRadio, {
8
+ props: { value: 'option1' }
9
+ })
10
+ expect(wrapper.find('.dm-radio').exists()).toBe(true)
11
+ })
12
+
13
+ it('renders label when provided', () => {
14
+ const wrapper = mount(DatametriaRadio, {
15
+ props: { value: 'option1', label: 'Option 1' }
16
+ })
17
+ expect(wrapper.find('.dm-radio__text').text()).toBe('Option 1')
18
+ })
19
+
20
+ it('checks radio when modelValue matches value', () => {
21
+ const wrapper = mount(DatametriaRadio, {
22
+ props: { value: 'option1', modelValue: 'option1' }
23
+ })
24
+ expect((wrapper.find('input').element as HTMLInputElement).checked).toBe(true)
25
+ })
26
+
27
+ it('emits update:modelValue on change', async () => {
28
+ const wrapper = mount(DatametriaRadio, {
29
+ props: { value: 'option1' }
30
+ })
31
+ await wrapper.find('input').trigger('change')
32
+ expect(wrapper.emitted('update:modelValue')?.[0]).toEqual(['option1'])
33
+ })
34
+
35
+ it('applies disabled state', () => {
36
+ const wrapper = mount(DatametriaRadio, {
37
+ props: { value: 'option1', disabled: true }
38
+ })
39
+ expect(wrapper.find('input').attributes('disabled')).toBeDefined()
40
+ })
41
+
42
+ it('does not emit when disabled', async () => {
43
+ const wrapper = mount(DatametriaRadio, {
44
+ props: { value: 'option1', disabled: true }
45
+ })
46
+ await wrapper.find('input').trigger('change')
47
+ expect(wrapper.emitted('update:modelValue')).toBeUndefined()
48
+ })
49
+
50
+ it('shows error message when provided', () => {
51
+ const wrapper = mount(DatametriaRadio, {
52
+ props: { value: 'option1', name: 'test', error: 'Error text' }
53
+ })
54
+ expect(wrapper.find('.dm-radio__error').text()).toBe('Error text')
55
+ })
56
+
57
+ it('applies name attribute', () => {
58
+ const wrapper = mount(DatametriaRadio, {
59
+ props: { value: 'option1', name: 'radio-group' }
60
+ })
61
+ expect(wrapper.find('input').attributes('name')).toBe('radio-group')
62
+ })
63
+
64
+ it('applies aria-label', () => {
65
+ const wrapper = mount(DatametriaRadio, {
66
+ props: { value: 'option1', ariaLabel: 'Select option' }
67
+ })
68
+ expect(wrapper.find('input').attributes('aria-label')).toBe('Select option')
69
+ })
70
+
71
+ it('uses CSS variables with fallbacks', () => {
72
+ const wrapper = mount(DatametriaRadio, {
73
+ props: { value: 'option1' }
74
+ })
75
+ expect(wrapper.html()).toContain('dm-radio')
76
+ })
77
+ })