@davidbirchall/core 1.0.6 → 1.0.8

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 (95) hide show
  1. package/.storybook/main.ts +18 -0
  2. package/.storybook/preview.ts +14 -0
  3. package/package.json +1 -4
  4. package/src/components/Badge/Badge.stories.ts +147 -0
  5. package/src/components/Badge/Badge.test.ts +57 -0
  6. package/src/components/Badge/Badge.vue +79 -0
  7. package/src/components/Button/Button.stories.ts +80 -0
  8. package/src/components/Button/Button.test.ts +145 -0
  9. package/src/components/Button/Button.vue +108 -0
  10. package/src/components/Button/types.ts +4 -0
  11. package/src/components/Calendar/Calendar.stories.ts +261 -0
  12. package/src/components/Calendar/Calendar.test.ts +119 -0
  13. package/src/components/Calendar/Calendar.vue +528 -0
  14. package/src/components/Calendar/types.ts +20 -0
  15. package/src/components/Card/Card.stories.ts +88 -0
  16. package/src/components/Card/Card.test.ts +173 -0
  17. package/src/components/Card/Card.vue +59 -0
  18. package/{dist/Card/types.d.ts → src/components/Card/types.ts} +1 -1
  19. package/src/components/Checkbox/Checkbox.stories.ts +126 -0
  20. package/src/components/Checkbox/Checkbox.test.ts +155 -0
  21. package/src/components/Checkbox/Checkbox.vue +121 -0
  22. package/src/components/Checkbox/types.ts +7 -0
  23. package/src/components/DataTable/DataTable.stories.ts +156 -0
  24. package/src/components/DataTable/DataTable.test.ts +185 -0
  25. package/src/components/DataTable/DataTable.vue +177 -0
  26. package/src/components/DataTable/types.ts +12 -0
  27. package/src/components/DatePicker/DatePicker.stories.ts +172 -0
  28. package/src/components/DatePicker/DatePicker.test.ts +87 -0
  29. package/src/components/DatePicker/DatePicker.vue +302 -0
  30. package/src/components/Dropdown/Dropdown.stories.ts +231 -0
  31. package/src/components/Dropdown/Dropdown.vue +314 -0
  32. package/src/components/Dropdown/types.ts +14 -0
  33. package/src/components/EmptyState/EmptyState.stories.ts +189 -0
  34. package/src/components/EmptyState/EmptyState.vue +215 -0
  35. package/src/components/EmptyState/types.ts +8 -0
  36. package/src/components/ErrorSummary/ErrorSummary.vue +78 -0
  37. package/src/components/ErrorSummary/types.ts +4 -0
  38. package/src/components/FormGroup/FormGroup.stories.ts +264 -0
  39. package/src/components/FormGroup/FormGroup.test.ts +63 -0
  40. package/src/components/FormGroup/FormGroup.vue +58 -0
  41. package/src/components/Heading/Heading.stories.ts +121 -0
  42. package/src/components/Heading/Heading.test.ts +184 -0
  43. package/src/components/Heading/Heading.vue +95 -0
  44. package/src/components/Heading/types.ts +6 -0
  45. package/src/components/Input/Input.stories.ts +172 -0
  46. package/src/components/Input/Input.test.ts +213 -0
  47. package/src/components/Input/Input.vue +121 -0
  48. package/src/components/Input/types.ts +11 -0
  49. package/src/components/Modal/Modal.stories.ts +341 -0
  50. package/src/components/Modal/Modal.test.ts +99 -0
  51. package/src/components/Modal/Modal.vue +278 -0
  52. package/src/components/ProgressBar/ProgressBar.stories.ts +313 -0
  53. package/src/components/ProgressBar/ProgressBar.test.ts +98 -0
  54. package/src/components/ProgressBar/ProgressBar.vue +117 -0
  55. package/src/components/Select/Select.stories.ts +177 -0
  56. package/src/components/Select/Select.test.ts +225 -0
  57. package/src/components/Select/Select.vue +147 -0
  58. package/src/components/Select/types.ts +16 -0
  59. package/src/components/StatCard/StatCard.stories.ts +274 -0
  60. package/src/components/StatCard/StatCard.vue +226 -0
  61. package/src/components/StatCard/types.ts +12 -0
  62. package/src/components/Tag/Tag.stories.ts +78 -0
  63. package/src/components/Tag/Tag.test.ts +50 -0
  64. package/src/components/Tag/Tag.vue +71 -0
  65. package/src/components/Tag/types.ts +4 -0
  66. package/src/components/TextArea/TextArea.stories.ts +171 -0
  67. package/src/components/TextArea/TextArea.test.ts +202 -0
  68. package/src/components/TextArea/TextArea.vue +122 -0
  69. package/src/components/TextArea/types.ts +11 -0
  70. package/src/components/index.ts +5 -0
  71. package/src/test/setup.ts +1 -0
  72. package/src/vite-env.d.ts +6 -0
  73. package/tsconfig.json +29 -0
  74. package/vite.config.ts +33 -0
  75. package/vitest.config.ts +28 -0
  76. package/dist/Button/types.d.ts +0 -4
  77. package/dist/Calendar/types.d.ts +0 -22
  78. package/dist/Checkbox/types.d.ts +0 -7
  79. package/dist/DataTable/types.d.ts +0 -11
  80. package/dist/Dropdown/types.d.ts +0 -13
  81. package/dist/EmptyState/types.d.ts +0 -8
  82. package/dist/ErrorSummary/types.d.ts +0 -4
  83. package/dist/Heading/types.d.ts +0 -6
  84. package/dist/Input/types.d.ts +0 -11
  85. package/dist/Select/types.d.ts +0 -15
  86. package/dist/StatCard/types.d.ts +0 -12
  87. package/dist/Tag/types.d.ts +0 -4
  88. package/dist/TextArea/types.d.ts +0 -11
  89. package/dist/core.css +0 -1
  90. package/dist/core.js +0 -24
  91. package/dist/core.js.map +0 -1
  92. package/dist/core.umd.cjs +0 -2
  93. package/dist/core.umd.cjs.map +0 -1
  94. package/dist/index.d.ts +0 -2
  95. package/dist/package.json +0 -27
@@ -0,0 +1,98 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { mount } from '@vue/test-utils'
3
+ import ProgressBar from './ProgressBar.vue'
4
+
5
+ describe('ProgressBar', () => {
6
+ it('renders with default props', () => {
7
+ const wrapper = mount(ProgressBar, {
8
+ props: {
9
+ percentage: 50
10
+ }
11
+ })
12
+ expect(wrapper.find('.progress-bar').exists()).toBe(true)
13
+ })
14
+
15
+ it('displays correct percentage', () => {
16
+ const wrapper = mount(ProgressBar, {
17
+ props: {
18
+ percentage: 75
19
+ }
20
+ })
21
+ expect(wrapper.find('.progress-bar__percentage').text()).toBe('75%')
22
+ })
23
+
24
+ it('clamps percentage to 0-100 range', () => {
25
+ const wrapper = mount(ProgressBar, {
26
+ props: {
27
+ percentage: 150
28
+ }
29
+ })
30
+ const fill = wrapper.find('.progress-bar__fill')
31
+ expect(fill.attributes('style')).toContain('width: 100%')
32
+ })
33
+
34
+ it('applies correct variant class', () => {
35
+ const variants = ['primary', 'success', 'warning', 'danger'] as const
36
+
37
+ variants.forEach(variant => {
38
+ const wrapper = mount(ProgressBar, {
39
+ props: {
40
+ percentage: 50,
41
+ variant
42
+ }
43
+ })
44
+ expect(wrapper.find(`.progress-bar__fill--${variant}`).exists()).toBe(true)
45
+ })
46
+ })
47
+
48
+ it('shows label when provided', () => {
49
+ const wrapper = mount(ProgressBar, {
50
+ props: {
51
+ percentage: 50,
52
+ label: 'Progress'
53
+ }
54
+ })
55
+ expect(wrapper.find('.progress-bar__label').text()).toBe('Progress')
56
+ })
57
+
58
+ it('hides header when showLabel is false', () => {
59
+ const wrapper = mount(ProgressBar, {
60
+ props: {
61
+ percentage: 50,
62
+ showLabel: false
63
+ }
64
+ })
65
+ expect(wrapper.find('.progress-bar__header').exists()).toBe(false)
66
+ })
67
+
68
+ it('shows percentage in bar when showPercentageInBar is true', () => {
69
+ const wrapper = mount(ProgressBar, {
70
+ props: {
71
+ percentage: 50,
72
+ showPercentageInBar: true
73
+ }
74
+ })
75
+ expect(wrapper.find('.progress-bar__text').exists()).toBe(true)
76
+ expect(wrapper.find('.progress-bar__text').text()).toBe('50%')
77
+ })
78
+
79
+ it('applies custom height', () => {
80
+ const wrapper = mount(ProgressBar, {
81
+ props: {
82
+ percentage: 50,
83
+ height: 20
84
+ }
85
+ })
86
+ expect(wrapper.find('.progress-bar__track').attributes('style')).toContain('height: 20px')
87
+ })
88
+
89
+ it('adds animated class when animated prop is true', () => {
90
+ const wrapper = mount(ProgressBar, {
91
+ props: {
92
+ percentage: 50,
93
+ animated: true
94
+ }
95
+ })
96
+ expect(wrapper.find('.progress-bar__fill--animated').exists()).toBe(true)
97
+ })
98
+ })
@@ -0,0 +1,117 @@
1
+ <template>
2
+ <div class="progress-bar">
3
+ <div v-if="showLabel" class="progress-bar__header">
4
+ <span class="progress-bar__label">{{ label }}</span>
5
+ <span class="progress-bar__percentage">{{ displayPercentage }}%</span>
6
+ </div>
7
+ <div class="progress-bar__track" :style="{ height: `${height}px` }">
8
+ <div
9
+ class="progress-bar__fill"
10
+ :class="[
11
+ `progress-bar__fill--${variant}`,
12
+ { 'progress-bar__fill--animated': animated }
13
+ ]"
14
+ :style="{ width: `${clampedPercentage}%` }"
15
+ >
16
+ <span v-if="showPercentageInBar" class="progress-bar__text">
17
+ {{ displayPercentage }}%
18
+ </span>
19
+ </div>
20
+ </div>
21
+ </div>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ import { computed } from 'vue'
26
+
27
+ interface Props {
28
+ percentage: number
29
+ label?: string
30
+ showLabel?: boolean
31
+ showPercentageInBar?: boolean
32
+ height?: number
33
+ variant?: 'primary' | 'success' | 'warning' | 'danger'
34
+ animated?: boolean
35
+ }
36
+
37
+ const props = withDefaults(defineProps<Props>(), {
38
+ percentage: 0,
39
+ label: '',
40
+ showLabel: true,
41
+ showPercentageInBar: false,
42
+ height: 12,
43
+ variant: 'primary',
44
+ animated: true
45
+ })
46
+
47
+ const clampedPercentage = computed(() => {
48
+ return Math.min(Math.max(props.percentage, 0), 100)
49
+ })
50
+
51
+ const displayPercentage = computed(() => {
52
+ return Math.round(clampedPercentage.value)
53
+ })
54
+ </script>
55
+
56
+ <style scoped>
57
+ .progress-bar {
58
+ width: 100%;
59
+ }
60
+
61
+ .progress-bar__header {
62
+ display: flex;
63
+ justify-content: space-between;
64
+ margin-bottom: 0.75rem;
65
+ font-weight: 600;
66
+ color: #374151;
67
+ font-size: 0.9375rem;
68
+ }
69
+
70
+ .progress-bar__percentage {
71
+ color: #667eea;
72
+ }
73
+
74
+ .progress-bar__track {
75
+ width: 100%;
76
+ background: #e5e7eb;
77
+ border-radius: 0.5rem;
78
+ overflow: hidden;
79
+ position: relative;
80
+ }
81
+
82
+ .progress-bar__fill {
83
+ height: 100%;
84
+ border-radius: 0.5rem;
85
+ transition: width 0.3s ease;
86
+ display: flex;
87
+ align-items: center;
88
+ justify-content: center;
89
+ position: relative;
90
+ }
91
+
92
+ .progress-bar__fill--animated {
93
+ transition: width 0.6s ease;
94
+ }
95
+
96
+ .progress-bar__fill--primary {
97
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
98
+ }
99
+
100
+ .progress-bar__fill--success {
101
+ background: linear-gradient(90deg, #10b981 0%, #059669 100%);
102
+ }
103
+
104
+ .progress-bar__fill--warning {
105
+ background: linear-gradient(90deg, #f59e0b 0%, #d97706 100%);
106
+ }
107
+
108
+ .progress-bar__fill--danger {
109
+ background: linear-gradient(90deg, #ef4444 0%, #dc2626 100%);
110
+ }
111
+
112
+ .progress-bar__text {
113
+ color: white;
114
+ font-size: 0.75rem;
115
+ font-weight: 600;
116
+ }
117
+ </style>
@@ -0,0 +1,177 @@
1
+ import type { Meta, StoryObj } from '@storybook/vue3'
2
+ import { ref } from 'vue'
3
+ import Select from './Select.vue'
4
+
5
+ const meta = {
6
+ title: 'Form Fields/Select',
7
+ component: Select,
8
+ tags: ['autodocs'],
9
+ argTypes: {
10
+ label: {
11
+ control: 'text',
12
+ description: 'Label text'
13
+ },
14
+ placeholder: {
15
+ control: 'text',
16
+ description: 'Placeholder text'
17
+ },
18
+ disabled: {
19
+ control: 'boolean',
20
+ description: 'Disabled state'
21
+ },
22
+ required: {
23
+ control: 'boolean',
24
+ description: 'Required field'
25
+ },
26
+ error: {
27
+ control: 'text',
28
+ description: 'Error message'
29
+ },
30
+ hint: {
31
+ control: 'text',
32
+ description: 'Hint text'
33
+ }
34
+ }
35
+ } satisfies Meta<typeof Select>
36
+
37
+ export default meta
38
+ type Story = StoryObj<typeof meta>
39
+
40
+ const countryOptions = [
41
+ { value: 'us', label: 'United States' },
42
+ { value: 'uk', label: 'United Kingdom' },
43
+ { value: 'ca', label: 'Canada' },
44
+ { value: 'au', label: 'Australia' },
45
+ { value: 'de', label: 'Germany' }
46
+ ]
47
+
48
+ export const Default: Story = {
49
+ args: {
50
+ label: 'Country',
51
+ placeholder: 'Select a country',
52
+ options: countryOptions
53
+ },
54
+ render: (args: any) => ({
55
+ components: { Select },
56
+ setup() {
57
+ const value = ref('')
58
+ return { args, value }
59
+ },
60
+ template: '<Select v-bind="args" v-model="value" />'
61
+ })
62
+ }
63
+
64
+ export const WithValue: Story = {
65
+ args: {
66
+ label: 'Country',
67
+ modelValue: 'uk',
68
+ options: countryOptions
69
+ },
70
+ render: (args: any) => ({
71
+ components: { Select },
72
+ setup() {
73
+ const value = ref(args.modelValue ?? '')
74
+ return { args, value }
75
+ },
76
+ template: '<Select v-bind="args" v-model="value" />'
77
+ })
78
+ }
79
+
80
+ export const WithHint: Story = {
81
+ args: {
82
+ label: 'Shipping Method',
83
+ placeholder: 'Choose shipping method',
84
+ hint: 'Standard shipping takes 5-7 business days',
85
+ options: [
86
+ { value: 'standard', label: 'Standard Shipping' },
87
+ { value: 'express', label: 'Express Shipping' },
88
+ { value: 'overnight', label: 'Overnight Shipping' }
89
+ ]
90
+ },
91
+ render: (args: any) => ({
92
+ components: { Select },
93
+ setup() {
94
+ const value = ref('')
95
+ return { args, value }
96
+ },
97
+ template: '<Select v-bind="args" v-model="value" />'
98
+ })
99
+ }
100
+
101
+ export const WithError: Story = {
102
+ args: {
103
+ label: 'Country',
104
+ placeholder: 'Select a country',
105
+ error: 'Please select a country',
106
+ options: countryOptions
107
+ },
108
+ render: (args: any) => ({
109
+ components: { Select },
110
+ setup() {
111
+ const value = ref('')
112
+ return { args, value }
113
+ },
114
+ template: '<Select v-bind="args" v-model="value" />'
115
+ })
116
+ }
117
+
118
+ export const Required: Story = {
119
+ args: {
120
+ label: 'Country',
121
+ placeholder: 'Select a country',
122
+ required: true,
123
+ options: countryOptions
124
+ },
125
+ render: (args: any) => ({
126
+ components: { Select },
127
+ setup() {
128
+ const value = ref('')
129
+ return { args, value }
130
+ },
131
+ template: '<Select v-bind="args" v-model="value" />'
132
+ })
133
+ }
134
+
135
+ export const Disabled: Story = {
136
+ args: {
137
+ label: 'Country',
138
+ modelValue: 'us',
139
+ disabled: true,
140
+ options: countryOptions
141
+ },
142
+ render: (args: any) => ({
143
+ components: { Select },
144
+ setup() {
145
+ const value = ref(args.modelValue ?? '')
146
+ return { args, value }
147
+ },
148
+ template: '<Select v-bind="args" v-model="value" />'
149
+ })
150
+ }
151
+
152
+ export const ManyOptions: Story = {
153
+ args: {
154
+ label: 'State',
155
+ placeholder: 'Select a state',
156
+ options: [
157
+ { value: 'al', label: 'Alabama' },
158
+ { value: 'ak', label: 'Alaska' },
159
+ { value: 'az', label: 'Arizona' },
160
+ { value: 'ar', label: 'Arkansas' },
161
+ { value: 'ca', label: 'California' },
162
+ { value: 'co', label: 'Colorado' },
163
+ { value: 'ct', label: 'Connecticut' },
164
+ { value: 'de', label: 'Delaware' },
165
+ { value: 'fl', label: 'Florida' },
166
+ { value: 'ga', label: 'Georgia' }
167
+ ]
168
+ },
169
+ render: (args: any) => ({
170
+ components: { Select },
171
+ setup() {
172
+ const value = ref('')
173
+ return { args, value }
174
+ },
175
+ template: '<Select v-bind="args" v-model="value" />'
176
+ })
177
+ }
@@ -0,0 +1,225 @@
1
+ import { describe, it, expect } from 'vitest'
2
+ import { render, screen } from '@testing-library/vue'
3
+ import userEvent from '@testing-library/user-event'
4
+ import Select from './Select.vue'
5
+
6
+ const mockOptions = [
7
+ { value: 'option1', label: 'Option 1' },
8
+ { value: 'option2', label: 'Option 2' },
9
+ { value: 'option3', label: 'Option 3' }
10
+ ]
11
+
12
+ describe('Select', () => {
13
+ describe('rendering', () => {
14
+ it('renders with label', () => {
15
+ render(Select, {
16
+ props: {
17
+ label: 'Choose an option',
18
+ options: mockOptions
19
+ }
20
+ })
21
+
22
+ expect(screen.getByText('Choose an option')).toBeInTheDocument()
23
+ })
24
+
25
+ it('renders without label', () => {
26
+ const { container } = render(Select, {
27
+ props: {
28
+ options: mockOptions
29
+ }
30
+ })
31
+
32
+ expect(container.querySelector('.select-label')).not.toBeInTheDocument()
33
+ })
34
+
35
+ it('renders all options', () => {
36
+ render(Select, {
37
+ props: {
38
+ options: mockOptions
39
+ }
40
+ })
41
+
42
+ expect(screen.getByRole('option', { name: 'Option 1' })).toBeInTheDocument()
43
+ expect(screen.getByRole('option', { name: 'Option 2' })).toBeInTheDocument()
44
+ expect(screen.getByRole('option', { name: 'Option 3' })).toBeInTheDocument()
45
+ })
46
+
47
+ it('renders placeholder as disabled option', () => {
48
+ render(Select, {
49
+ props: {
50
+ placeholder: 'Select an option',
51
+ options: mockOptions
52
+ }
53
+ })
54
+
55
+ const placeholderOption = screen.getByRole('option', { name: 'Select an option' }) as HTMLOptionElement
56
+ expect(placeholderOption).toBeInTheDocument()
57
+ expect(placeholderOption.disabled).toBe(true)
58
+ })
59
+
60
+ it('renders error message', () => {
61
+ render(Select, {
62
+ props: {
63
+ error: 'This field is required',
64
+ options: mockOptions
65
+ }
66
+ })
67
+
68
+ expect(screen.getByText('This field is required')).toBeInTheDocument()
69
+ })
70
+
71
+ it('renders hint text', () => {
72
+ render(Select, {
73
+ props: {
74
+ hint: 'Choose wisely',
75
+ options: mockOptions
76
+ }
77
+ })
78
+
79
+ expect(screen.getByText('Choose wisely')).toBeInTheDocument()
80
+ })
81
+
82
+ it('does not show hint when error is present', () => {
83
+ render(Select, {
84
+ props: {
85
+ hint: 'Helper text',
86
+ error: 'Error message',
87
+ options: mockOptions
88
+ }
89
+ })
90
+
91
+ expect(screen.queryByText('Helper text')).not.toBeInTheDocument()
92
+ expect(screen.getByText('Error message')).toBeInTheDocument()
93
+ })
94
+
95
+ it('shows required asterisk when required', () => {
96
+ render(Select, {
97
+ props: {
98
+ label: 'Country',
99
+ required: true,
100
+ options: mockOptions
101
+ }
102
+ })
103
+
104
+ expect(screen.getByText('*')).toBeInTheDocument()
105
+ })
106
+
107
+ it('applies error class when error prop is set', () => {
108
+ render(Select, {
109
+ props: {
110
+ error: 'Error',
111
+ options: mockOptions
112
+ }
113
+ })
114
+
115
+ const select = screen.getByRole('combobox')
116
+ expect(select).toHaveClass('select-field--error')
117
+ })
118
+ })
119
+
120
+ describe('v-model', () => {
121
+ it('displays initial value', () => {
122
+ render(Select, {
123
+ props: {
124
+ modelValue: 'option2',
125
+ options: mockOptions
126
+ }
127
+ })
128
+
129
+ const select = screen.getByRole('combobox') as HTMLSelectElement
130
+ expect(select.value).toBe('option2')
131
+ })
132
+
133
+ it('emits update:modelValue on change', async () => {
134
+ const user = userEvent.setup()
135
+ const { emitted } = render(Select, {
136
+ props: {
137
+ options: mockOptions
138
+ }
139
+ })
140
+
141
+ const select = screen.getByRole('combobox')
142
+ await user.selectOptions(select, 'option2')
143
+
144
+ expect(emitted()['update:modelValue']).toBeTruthy()
145
+ expect(emitted()['update:modelValue'][0]).toEqual(['option2'])
146
+ })
147
+ })
148
+
149
+ describe('disabled state', () => {
150
+ it('disables the select when disabled prop is true', () => {
151
+ render(Select, {
152
+ props: {
153
+ disabled: true,
154
+ options: mockOptions
155
+ }
156
+ })
157
+
158
+ const select = screen.getByRole('combobox')
159
+ expect(select).toBeDisabled()
160
+ })
161
+
162
+ it('applies disabled class', () => {
163
+ render(Select, {
164
+ props: {
165
+ disabled: true,
166
+ options: mockOptions
167
+ }
168
+ })
169
+
170
+ const select = screen.getByRole('combobox')
171
+ expect(select).toHaveClass('select-field--disabled')
172
+ })
173
+ })
174
+
175
+ describe('required attribute', () => {
176
+ it('sets required attribute when required prop is true', () => {
177
+ render(Select, {
178
+ props: {
179
+ required: true,
180
+ options: mockOptions
181
+ }
182
+ })
183
+
184
+ const select = screen.getByRole('combobox')
185
+ expect(select).toBeRequired()
186
+ })
187
+
188
+ it('does not set required attribute by default', () => {
189
+ render(Select, {
190
+ props: {
191
+ options: mockOptions
192
+ }
193
+ })
194
+
195
+ const select = screen.getByRole('combobox')
196
+ expect(select).not.toBeRequired()
197
+ })
198
+ })
199
+
200
+ describe('accessibility', () => {
201
+ it('associates label with select using htmlFor', () => {
202
+ render(Select, {
203
+ props: {
204
+ label: 'Choose option',
205
+ options: mockOptions
206
+ }
207
+ })
208
+
209
+ const label = screen.getByText('Choose option') as HTMLLabelElement
210
+ const select = screen.getByRole('combobox')
211
+
212
+ expect(label.htmlFor).toBe(select.id)
213
+ })
214
+ })
215
+
216
+ describe('empty options', () => {
217
+ it('renders without options', () => {
218
+ const { container } = render(Select)
219
+
220
+ const select = container.querySelector('select')
221
+ expect(select).toBeInTheDocument()
222
+ expect(select?.children.length).toBe(0)
223
+ })
224
+ })
225
+ })