@oslokommune/punkt-elements 13.5.5 → 13.5.6
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/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ og skriver commits ca etter [Conventional Commits](https://conventionalcommits.o
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## [13.5.6](https://github.com/oslokommune/punkt/compare/13.5.5...13.5.6) (2025-09-09)
|
|
9
|
+
|
|
10
|
+
### ⚠ BREAKING CHANGES
|
|
11
|
+
Ingen
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
Ingen
|
|
15
|
+
|
|
16
|
+
### Bug Fixes
|
|
17
|
+
Ingen
|
|
18
|
+
|
|
19
|
+
### Chores
|
|
20
|
+
Ingen
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
|
|
8
25
|
## [13.5.5](https://github.com/oslokommune/punkt/compare/13.5.4...13.5.5) (2025-09-09)
|
|
9
26
|
|
|
10
27
|
### ⚠ BREAKING CHANGES
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oslokommune/punkt-elements",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.6",
|
|
4
4
|
"description": "Komponentbiblioteket til Punkt, et designsystem laget av Oslo Origo",
|
|
5
5
|
"homepage": "https://punkt.oslo.kommune.no",
|
|
6
6
|
"author": "Team Designsystem, Oslo Origo",
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"url": "https://github.com/oslokommune/punkt/issues"
|
|
74
74
|
},
|
|
75
75
|
"license": "MIT",
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "5f0019a262ea4b9086590f7b6d572f5a39142f31"
|
|
77
77
|
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import { fireEvent } from '@testing-library/dom'
|
|
4
|
+
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
|
|
5
|
+
import { CustomElementFor } from '../../tests/component-registry'
|
|
6
|
+
import { type IPktTag } from './tag'
|
|
7
|
+
import './tag'
|
|
8
|
+
|
|
9
|
+
export interface TagTestConfig extends Partial<IPktTag>, BaseTestConfig {}
|
|
10
|
+
|
|
11
|
+
// Use shared framework
|
|
12
|
+
export const createTagTest = async (config: TagTestConfig = {}) => {
|
|
13
|
+
const { container, element } = await createElementTest<
|
|
14
|
+
CustomElementFor<'pkt-tag'>,
|
|
15
|
+
TagTestConfig
|
|
16
|
+
>('pkt-tag', config)
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
container,
|
|
20
|
+
tag: element,
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
expect.extend(toHaveNoViolations)
|
|
25
|
+
|
|
26
|
+
// Test data constants
|
|
27
|
+
const VALID_SKINS = [
|
|
28
|
+
'blue',
|
|
29
|
+
'blue-dark',
|
|
30
|
+
'blue-light',
|
|
31
|
+
'green',
|
|
32
|
+
'red',
|
|
33
|
+
'yellow',
|
|
34
|
+
'beige',
|
|
35
|
+
'gray',
|
|
36
|
+
'grey',
|
|
37
|
+
] as const
|
|
38
|
+
const VALID_SIZES = ['small', 'medium', 'large'] as const
|
|
39
|
+
const VALID_TYPES = ['button', 'reset', 'submit'] as const
|
|
40
|
+
|
|
41
|
+
afterEach(() => {
|
|
42
|
+
document.body.innerHTML = ''
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('PktTag', () => {
|
|
46
|
+
describe('Basic Rendering', () => {
|
|
47
|
+
test('renders without errors', async () => {
|
|
48
|
+
const { tag } = await createTagTest()
|
|
49
|
+
expect(tag).toBeInTheDocument()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('renders with default properties', async () => {
|
|
53
|
+
const { tag } = await createTagTest()
|
|
54
|
+
expect(tag.closeTag).toBe(false)
|
|
55
|
+
expect(tag.size).toBe('medium')
|
|
56
|
+
expect(tag.skin).toBe('blue')
|
|
57
|
+
expect(tag.type).toBe('button')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('renders content in slot', async () => {
|
|
61
|
+
const content = 'Test Tag Content'
|
|
62
|
+
const { tag } = await createTagTest({ content })
|
|
63
|
+
expect(tag.textContent?.trim()).toBe(content)
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
describe('Skin Variations', () => {
|
|
68
|
+
test('applies different skin classes correctly', async () => {
|
|
69
|
+
for (const skin of VALID_SKINS) {
|
|
70
|
+
const { tag } = await createTagTest({ skin })
|
|
71
|
+
// For non-closeable tags, check the span element
|
|
72
|
+
const tagElement = tag.querySelector('span') || tag.querySelector('button')
|
|
73
|
+
expect(tagElement).toHaveClass(`pkt-tag--${skin}`)
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
describe('Size Variations', () => {
|
|
79
|
+
test('applies different size classes correctly', async () => {
|
|
80
|
+
for (const size of VALID_SIZES) {
|
|
81
|
+
const { tag } = await createTagTest({ size })
|
|
82
|
+
// For non-closeable tags, check the span element
|
|
83
|
+
const tagElement = tag.querySelector('span') || tag.querySelector('button')
|
|
84
|
+
expect(tagElement).toHaveClass(`pkt-tag--${size}`)
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
describe('Type Variations', () => {
|
|
90
|
+
test('sets correct type attribute', async () => {
|
|
91
|
+
for (const type of VALID_TYPES) {
|
|
92
|
+
const { tag } = await createTagTest({ type })
|
|
93
|
+
expect(tag.type).toBe(type)
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
describe('Icon Functionality', () => {
|
|
99
|
+
test('renders icon when iconName provided', async () => {
|
|
100
|
+
const { tag } = await createTagTest({ iconName: 'arrow-right' })
|
|
101
|
+
const icon = tag.querySelector('pkt-icon')
|
|
102
|
+
expect(icon).toBeInTheDocument()
|
|
103
|
+
expect(icon?.getAttribute('name')).toBe('arrow-right')
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('does not render icon when iconName not provided', async () => {
|
|
107
|
+
const { tag } = await createTagTest()
|
|
108
|
+
const icon = tag.querySelector('pkt-icon')
|
|
109
|
+
expect(icon).not.toBeInTheDocument()
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
describe('Close Functionality', () => {
|
|
114
|
+
test('renders close button when closeTag is true', async () => {
|
|
115
|
+
const { tag } = await createTagTest({ closeTag: true })
|
|
116
|
+
const closeIcon = tag.querySelector('.pkt-tag__close-btn')
|
|
117
|
+
expect(closeIcon).toBeInTheDocument()
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
test('does not render close button when closeTag is false', async () => {
|
|
121
|
+
const { tag } = await createTagTest({ closeTag: false })
|
|
122
|
+
const closeIcon = tag.querySelector('.pkt-tag__close-btn')
|
|
123
|
+
expect(closeIcon).not.toBeInTheDocument()
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
test('dispatches close event when close button is clicked', async () => {
|
|
127
|
+
const { tag } = await createTagTest({ closeTag: true })
|
|
128
|
+
const closeSpy = jest.fn()
|
|
129
|
+
tag.addEventListener('close', closeSpy)
|
|
130
|
+
|
|
131
|
+
const button = tag.querySelector('button') as HTMLButtonElement
|
|
132
|
+
fireEvent.click(button)
|
|
133
|
+
|
|
134
|
+
expect(closeSpy).toHaveBeenCalled()
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test('hides tag when closed', async () => {
|
|
138
|
+
const { tag } = await createTagTest({ closeTag: true })
|
|
139
|
+
const button = tag.querySelector('button') as HTMLButtonElement
|
|
140
|
+
|
|
141
|
+
fireEvent.click(button)
|
|
142
|
+
await tag.updateComplete
|
|
143
|
+
|
|
144
|
+
expect(button).toHaveClass('pkt-hide')
|
|
145
|
+
})
|
|
146
|
+
})
|
|
147
|
+
|
|
148
|
+
describe('Text Style', () => {
|
|
149
|
+
test('applies text style class when provided', async () => {
|
|
150
|
+
const { tag } = await createTagTest({ textStyle: 'thin-text' })
|
|
151
|
+
const tagElement = tag.querySelector('span') || tag.querySelector('button')
|
|
152
|
+
expect(tagElement).toHaveClass('pkt-tag--thin-text')
|
|
153
|
+
})
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
describe('Accessibility', () => {
|
|
157
|
+
test('applies aria-label when provided', async () => {
|
|
158
|
+
const { tag } = await createTagTest({ closeTag: true, ariaLabel: 'Close tag' })
|
|
159
|
+
const button = tag.querySelector('button')
|
|
160
|
+
expect(button?.getAttribute('aria-label')).toBe('Close tag')
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('tag is accessible', async () => {
|
|
164
|
+
const { tag } = await createTagTest({
|
|
165
|
+
content: 'Accessible Tag',
|
|
166
|
+
closeTag: true,
|
|
167
|
+
ariaLabel: 'Close tag',
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
const results = await axe(tag)
|
|
171
|
+
expect(results).toHaveNoViolations()
|
|
172
|
+
})
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
describe('Complex Configurations', () => {
|
|
176
|
+
test('renders with all properties set', async () => {
|
|
177
|
+
const config: TagTestConfig = {
|
|
178
|
+
content: 'Complete Tag',
|
|
179
|
+
closeTag: true,
|
|
180
|
+
size: 'large',
|
|
181
|
+
skin: 'green',
|
|
182
|
+
iconName: 'check',
|
|
183
|
+
type: 'submit',
|
|
184
|
+
textStyle: 'thin-text',
|
|
185
|
+
ariaLabel: 'Complete tag',
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const { tag } = await createTagTest(config)
|
|
189
|
+
|
|
190
|
+
expect(tag.textContent?.trim()).toBe(config.content)
|
|
191
|
+
expect(tag.closeTag).toBe(config.closeTag)
|
|
192
|
+
expect(tag.size).toBe(config.size)
|
|
193
|
+
expect(tag.skin).toBe(config.skin)
|
|
194
|
+
expect(tag.iconName).toBe(config.iconName)
|
|
195
|
+
expect(tag.type).toBe(config.type)
|
|
196
|
+
expect(tag.textStyle).toBe(config.textStyle)
|
|
197
|
+
expect(tag.ariaLabel).toBe(config.ariaLabel)
|
|
198
|
+
|
|
199
|
+
const button = tag.querySelector('button')
|
|
200
|
+
expect(button).toHaveClass(`pkt-tag--${config.size}`)
|
|
201
|
+
expect(button).toHaveClass(`pkt-tag--${config.skin}`)
|
|
202
|
+
expect(button).toHaveClass(`pkt-tag--${config.textStyle}`)
|
|
203
|
+
|
|
204
|
+
const icon = tag.querySelector('pkt-icon')
|
|
205
|
+
expect(icon?.getAttribute('name')).toBe(config.iconName)
|
|
206
|
+
|
|
207
|
+
const closeIcon = tag.querySelector('.pkt-tag__close-btn')
|
|
208
|
+
expect(closeIcon).toBeInTheDocument()
|
|
209
|
+
})
|
|
210
|
+
})
|
|
211
|
+
})
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import { fireEvent } from '@testing-library/dom'
|
|
4
|
+
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
|
|
5
|
+
import { CustomElementFor } from '../../tests/component-registry'
|
|
6
|
+
import './textarea'
|
|
7
|
+
|
|
8
|
+
export interface TextareaTestConfig extends BaseTestConfig {
|
|
9
|
+
// From PktTextarea specific properties
|
|
10
|
+
value?: string
|
|
11
|
+
autocomplete?: string
|
|
12
|
+
rows?: number | null
|
|
13
|
+
|
|
14
|
+
// From PktInputElement base class (commonly used ones)
|
|
15
|
+
id?: string
|
|
16
|
+
label?: string
|
|
17
|
+
name?: string
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
readonly?: boolean
|
|
20
|
+
required?: boolean
|
|
21
|
+
placeholder?: string | null
|
|
22
|
+
maxlength?: number | null
|
|
23
|
+
minlength?: number | null
|
|
24
|
+
hasError?: boolean
|
|
25
|
+
errorMessage?: string
|
|
26
|
+
helptext?: string
|
|
27
|
+
fullwidth?: boolean
|
|
28
|
+
counter?: boolean
|
|
29
|
+
inline?: boolean
|
|
30
|
+
ariaLabelledby?: string | null
|
|
31
|
+
ariaDescribedBy?: string | null
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Use shared framework
|
|
35
|
+
export const createTextareaTest = async (config: TextareaTestConfig = {}) => {
|
|
36
|
+
const { container, element } = await createElementTest<
|
|
37
|
+
CustomElementFor<'pkt-textarea'>,
|
|
38
|
+
TextareaTestConfig
|
|
39
|
+
>('pkt-textarea', config)
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
container,
|
|
43
|
+
textarea: element,
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
expect.extend(toHaveNoViolations)
|
|
48
|
+
|
|
49
|
+
afterEach(() => {
|
|
50
|
+
document.body.innerHTML = ''
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('PktTextarea', () => {
|
|
54
|
+
describe('Basic Rendering', () => {
|
|
55
|
+
test('renders without errors', async () => {
|
|
56
|
+
const { textarea } = await createTextareaTest()
|
|
57
|
+
expect(textarea).toBeInTheDocument()
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('renders with default properties', async () => {
|
|
61
|
+
const { textarea } = await createTextareaTest()
|
|
62
|
+
expect(textarea.value).toBe('')
|
|
63
|
+
expect(textarea.autocomplete).toBe('off')
|
|
64
|
+
expect(textarea.rows).toBe(null)
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('renders textarea element', async () => {
|
|
68
|
+
const { textarea } = await createTextareaTest()
|
|
69
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
70
|
+
expect(textareaElement).toBeInTheDocument()
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('Properties and Attributes', () => {
|
|
75
|
+
test('sets value correctly', async () => {
|
|
76
|
+
const value = 'Test textarea content'
|
|
77
|
+
const { textarea } = await createTextareaTest({ value })
|
|
78
|
+
|
|
79
|
+
expect(textarea.value).toBe(value)
|
|
80
|
+
const textareaElement = textarea.querySelector('textarea') as HTMLTextAreaElement
|
|
81
|
+
expect(textareaElement.value).toBe(value)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('sets rows correctly', async () => {
|
|
85
|
+
const { textarea } = await createTextareaTest({ rows: 5 })
|
|
86
|
+
|
|
87
|
+
expect(textarea.rows).toBe(5)
|
|
88
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
89
|
+
expect(textareaElement?.getAttribute('rows')).toBe('5')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('sets autocomplete correctly', async () => {
|
|
93
|
+
const { textarea } = await createTextareaTest({ autocomplete: 'on' })
|
|
94
|
+
|
|
95
|
+
expect(textarea.autocomplete).toBe('on')
|
|
96
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
97
|
+
expect(textareaElement?.getAttribute('autocomplete')).toBe('on')
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('handles disabled state', async () => {
|
|
101
|
+
const { textarea } = await createTextareaTest({ disabled: true })
|
|
102
|
+
|
|
103
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
104
|
+
expect(textareaElement?.hasAttribute('disabled')).toBe(true)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
test('handles readonly state', async () => {
|
|
108
|
+
const { textarea } = await createTextareaTest({ readonly: true })
|
|
109
|
+
|
|
110
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
111
|
+
expect(textareaElement?.hasAttribute('readonly')).toBe(true)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test('handles required state', async () => {
|
|
115
|
+
const { textarea } = await createTextareaTest({ required: true })
|
|
116
|
+
|
|
117
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
118
|
+
expect(textareaElement?.hasAttribute('required')).toBe(false) // Not set as attribute on textarea
|
|
119
|
+
|
|
120
|
+
const inputWrapper = textarea.querySelector('pkt-input-wrapper')
|
|
121
|
+
expect(inputWrapper?.hasAttribute('required')).toBe(true) // But passed to wrapper
|
|
122
|
+
})
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
describe('Input Wrapper Integration', () => {
|
|
126
|
+
test('displays label correctly', async () => {
|
|
127
|
+
const { textarea } = await createTextareaTest({ label: 'Comment' })
|
|
128
|
+
|
|
129
|
+
const inputWrapper = textarea.querySelector('pkt-input-wrapper')
|
|
130
|
+
expect(inputWrapper?.getAttribute('label')).toBe('Comment')
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('displays helptext correctly', async () => {
|
|
134
|
+
const { textarea } = await createTextareaTest({ helptext: 'Enter your message' })
|
|
135
|
+
|
|
136
|
+
// helptext is passed as a property, not attribute to input-wrapper
|
|
137
|
+
expect(textarea.helptext).toBe('Enter your message')
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
test('handles error state', async () => {
|
|
141
|
+
const { textarea } = await createTextareaTest({
|
|
142
|
+
hasError: true,
|
|
143
|
+
errorMessage: 'This field is required',
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
expect(textarea.hasError).toBe(true)
|
|
147
|
+
expect(textarea.errorMessage).toBe('This field is required')
|
|
148
|
+
|
|
149
|
+
const inputWrapper = textarea.querySelector('pkt-input-wrapper')
|
|
150
|
+
expect(inputWrapper?.hasAttribute('hasError')).toBe(true)
|
|
151
|
+
|
|
152
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
153
|
+
expect(textareaElement?.getAttribute('aria-invalid')).toBe('true')
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('handles fullwidth styling', async () => {
|
|
157
|
+
const { textarea } = await createTextareaTest({ fullwidth: true })
|
|
158
|
+
|
|
159
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
160
|
+
expect(textareaElement?.className).toContain('pkt-input--fullwidth')
|
|
161
|
+
})
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
describe('Character Counter', () => {
|
|
165
|
+
test('shows counter when enabled', async () => {
|
|
166
|
+
const { textarea } = await createTextareaTest({
|
|
167
|
+
counter: true,
|
|
168
|
+
maxlength: 100,
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
const inputWrapper = textarea.querySelector('pkt-input-wrapper')
|
|
172
|
+
expect(inputWrapper?.hasAttribute('counter')).toBe(true)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test('updates counter on value change', async () => {
|
|
176
|
+
const { textarea } = await createTextareaTest({
|
|
177
|
+
counter: true,
|
|
178
|
+
maxlength: 100,
|
|
179
|
+
value: 'Hello',
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
expect(textarea.counterCurrent).toBe(5)
|
|
183
|
+
})
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
describe('User Interaction', () => {
|
|
187
|
+
test('updates value on user input', async () => {
|
|
188
|
+
const { textarea } = await createTextareaTest()
|
|
189
|
+
const textareaElement = textarea.querySelector('textarea') as HTMLTextAreaElement
|
|
190
|
+
|
|
191
|
+
fireEvent.input(textareaElement, { target: { value: 'New content' } })
|
|
192
|
+
await textarea.updateComplete
|
|
193
|
+
|
|
194
|
+
expect(textarea.value).toBe('New content')
|
|
195
|
+
expect(textarea.touched).toBe(true)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('handles focus and blur events', async () => {
|
|
199
|
+
const { textarea } = await createTextareaTest()
|
|
200
|
+
const textareaElement = textarea.querySelector('textarea') as HTMLTextAreaElement
|
|
201
|
+
|
|
202
|
+
// Focus and input to trigger touched state
|
|
203
|
+
fireEvent.focus(textareaElement)
|
|
204
|
+
fireEvent.input(textareaElement, { target: { value: 'test input' } })
|
|
205
|
+
await textarea.updateComplete
|
|
206
|
+
fireEvent.blur(textareaElement)
|
|
207
|
+
await textarea.updateComplete
|
|
208
|
+
|
|
209
|
+
// Test that input with value change sets touched state
|
|
210
|
+
expect(textarea.touched).toBe(true)
|
|
211
|
+
})
|
|
212
|
+
})
|
|
213
|
+
|
|
214
|
+
describe('Validation', () => {
|
|
215
|
+
test('respects maxlength constraint', async () => {
|
|
216
|
+
const { textarea } = await createTextareaTest({ maxlength: 10 })
|
|
217
|
+
|
|
218
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
219
|
+
expect(textareaElement?.getAttribute('maxlength')).toBe('10')
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
test('respects minlength constraint', async () => {
|
|
223
|
+
const { textarea } = await createTextareaTest({ minlength: 5 })
|
|
224
|
+
|
|
225
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
226
|
+
expect(textareaElement?.getAttribute('minlength')).toBe('5')
|
|
227
|
+
})
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
describe('Accessibility', () => {
|
|
231
|
+
test('passes through accessibility attributes', async () => {
|
|
232
|
+
const { textarea } = await createTextareaTest({
|
|
233
|
+
ariaLabelledby: 'external-label',
|
|
234
|
+
ariaDescribedBy: 'external-description',
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
238
|
+
expect(textareaElement?.getAttribute('aria-labelledby')).toBe('external-label')
|
|
239
|
+
|
|
240
|
+
// ariaDescribedBy is passed as property to input-wrapper
|
|
241
|
+
expect(textarea.ariaDescribedBy).toBe('external-description')
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
test('textarea is accessible', async () => {
|
|
245
|
+
const { textarea } = await createTextareaTest({
|
|
246
|
+
label: 'Message',
|
|
247
|
+
helptext: 'Enter your message here',
|
|
248
|
+
required: true,
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
const results = await axe(textarea)
|
|
252
|
+
expect(results).toHaveNoViolations()
|
|
253
|
+
})
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
describe('Complex Configuration', () => {
|
|
257
|
+
test('renders with all properties set', async () => {
|
|
258
|
+
const config: TextareaTestConfig = {
|
|
259
|
+
label: 'Feedback',
|
|
260
|
+
value: 'Initial feedback text',
|
|
261
|
+
placeholder: 'Enter your feedback...',
|
|
262
|
+
rows: 6,
|
|
263
|
+
maxlength: 500,
|
|
264
|
+
counter: true,
|
|
265
|
+
required: true,
|
|
266
|
+
helptext: 'Please provide detailed feedback',
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const { textarea } = await createTextareaTest(config)
|
|
270
|
+
|
|
271
|
+
expect(textarea.value).toBe(config.value)
|
|
272
|
+
expect(textarea.rows).toBe(config.rows)
|
|
273
|
+
expect(textarea.maxlength).toBe(config.maxlength)
|
|
274
|
+
expect(textarea.required).toBe(config.required)
|
|
275
|
+
|
|
276
|
+
const inputWrapper = textarea.querySelector('pkt-input-wrapper')
|
|
277
|
+
expect(inputWrapper?.getAttribute('label')).toBe(config.label)
|
|
278
|
+
expect(textarea.helptext).toBe(config.helptext) // Property, not attribute
|
|
279
|
+
expect(inputWrapper?.hasAttribute('counter')).toBe(true)
|
|
280
|
+
|
|
281
|
+
const textareaElement = textarea.querySelector('textarea')
|
|
282
|
+
expect(textareaElement?.getAttribute('placeholder')).toBe(config.placeholder)
|
|
283
|
+
expect(textareaElement?.getAttribute('rows')).toBe(String(config.rows))
|
|
284
|
+
expect(textareaElement?.getAttribute('maxlength')).toBe(String(config.maxlength))
|
|
285
|
+
// required is handled by input-wrapper, not set directly on textarea
|
|
286
|
+
expect(textarea.required).toBe(true)
|
|
287
|
+
})
|
|
288
|
+
})
|
|
289
|
+
})
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import '@testing-library/jest-dom'
|
|
2
|
+
import { axe, toHaveNoViolations } from 'jest-axe'
|
|
3
|
+
import { fireEvent } from '@testing-library/dom'
|
|
4
|
+
import { createElementTest, BaseTestConfig } from '../../tests/test-framework'
|
|
5
|
+
import { CustomElementFor } from '../../tests/component-registry'
|
|
6
|
+
import './textinput'
|
|
7
|
+
|
|
8
|
+
export interface TextinputTestConfig extends BaseTestConfig {
|
|
9
|
+
// From PktTextinput specific properties
|
|
10
|
+
type?: string
|
|
11
|
+
value?: string
|
|
12
|
+
autocomplete?: string | null
|
|
13
|
+
iconNameRight?: string | null
|
|
14
|
+
prefix?: string | null
|
|
15
|
+
suffix?: string | null
|
|
16
|
+
size?: number | null
|
|
17
|
+
omitSearchIcon?: boolean
|
|
18
|
+
|
|
19
|
+
// From PktInputElement base class (commonly used ones)
|
|
20
|
+
id?: string
|
|
21
|
+
label?: string
|
|
22
|
+
name?: string
|
|
23
|
+
disabled?: boolean
|
|
24
|
+
readonly?: boolean
|
|
25
|
+
required?: boolean
|
|
26
|
+
placeholder?: string | null
|
|
27
|
+
maxlength?: number | null
|
|
28
|
+
minlength?: number | null
|
|
29
|
+
hasError?: boolean
|
|
30
|
+
errorMessage?: string
|
|
31
|
+
helptext?: string
|
|
32
|
+
fullwidth?: boolean
|
|
33
|
+
counter?: boolean
|
|
34
|
+
inline?: boolean
|
|
35
|
+
ariaLabelledby?: string | null
|
|
36
|
+
ariaDescribedBy?: string | null
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Use shared framework
|
|
40
|
+
export const createTextinputTest = async (config: TextinputTestConfig = {}) => {
|
|
41
|
+
const { container, element } = await createElementTest<
|
|
42
|
+
CustomElementFor<'pkt-textinput'>,
|
|
43
|
+
TextinputTestConfig
|
|
44
|
+
>('pkt-textinput', config)
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
container,
|
|
48
|
+
textinput: element,
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
expect.extend(toHaveNoViolations)
|
|
53
|
+
|
|
54
|
+
afterEach(() => {
|
|
55
|
+
document.body.innerHTML = ''
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
describe('PktTextinput', () => {
|
|
59
|
+
describe('Basic Rendering', () => {
|
|
60
|
+
test('renders without errors', async () => {
|
|
61
|
+
const { textinput } = await createTextinputTest()
|
|
62
|
+
expect(textinput).toBeInTheDocument()
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test('renders with default properties', async () => {
|
|
66
|
+
const { textinput } = await createTextinputTest()
|
|
67
|
+
expect(textinput.type).toBe('text')
|
|
68
|
+
expect(textinput.value).toBe('')
|
|
69
|
+
expect(textinput.autocomplete).toBe(null) // Property defaults to null, template sets 'off'
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test('renders input element', async () => {
|
|
73
|
+
const { textinput } = await createTextinputTest()
|
|
74
|
+
const inputElement = textinput.querySelector('input')
|
|
75
|
+
expect(inputElement).toBeInTheDocument()
|
|
76
|
+
})
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
describe('Input Types', () => {
|
|
80
|
+
test('renders text input by default', async () => {
|
|
81
|
+
const { textinput } = await createTextinputTest()
|
|
82
|
+
const inputElement = textinput.querySelector('input')
|
|
83
|
+
expect(inputElement?.getAttribute('type')).toBe('text')
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('renders email input type', async () => {
|
|
87
|
+
const { textinput } = await createTextinputTest({ type: 'email' })
|
|
88
|
+
const inputElement = textinput.querySelector('input')
|
|
89
|
+
expect(inputElement?.getAttribute('type')).toBe('email')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('renders password input type', async () => {
|
|
93
|
+
const { textinput } = await createTextinputTest({ type: 'password' })
|
|
94
|
+
const inputElement = textinput.querySelector('input')
|
|
95
|
+
expect(inputElement?.getAttribute('type')).toBe('password')
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('renders tel input type', async () => {
|
|
99
|
+
const { textinput } = await createTextinputTest({ type: 'tel' })
|
|
100
|
+
const inputElement = textinput.querySelector('input')
|
|
101
|
+
expect(inputElement?.getAttribute('type')).toBe('tel')
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test('renders url input type', async () => {
|
|
105
|
+
const { textinput } = await createTextinputTest({ type: 'url' })
|
|
106
|
+
const inputElement = textinput.querySelector('input')
|
|
107
|
+
expect(inputElement?.getAttribute('type')).toBe('url')
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test('renders search input type', async () => {
|
|
111
|
+
const { textinput } = await createTextinputTest({ type: 'search' })
|
|
112
|
+
const inputElement = textinput.querySelector('input')
|
|
113
|
+
expect(inputElement?.getAttribute('type')).toBe('search')
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
describe('Properties and Attributes', () => {
|
|
118
|
+
test('sets value correctly', async () => {
|
|
119
|
+
const value = 'Test input value'
|
|
120
|
+
const { textinput } = await createTextinputTest({ value })
|
|
121
|
+
|
|
122
|
+
expect(textinput.value).toBe(value)
|
|
123
|
+
const inputElement = textinput.querySelector('input') as HTMLInputElement
|
|
124
|
+
expect(inputElement.value).toBe(value)
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test('sets autocomplete correctly', async () => {
|
|
128
|
+
const { textinput } = await createTextinputTest({ autocomplete: 'email' })
|
|
129
|
+
|
|
130
|
+
expect(textinput.autocomplete).toBe('email')
|
|
131
|
+
const inputElement = textinput.querySelector('input')
|
|
132
|
+
expect(inputElement?.getAttribute('autocomplete')).toBe('email')
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('handles disabled state', async () => {
|
|
136
|
+
const { textinput } = await createTextinputTest({ disabled: true })
|
|
137
|
+
|
|
138
|
+
const inputElement = textinput.querySelector('input')
|
|
139
|
+
expect(inputElement?.hasAttribute('disabled')).toBe(true)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test('handles readonly state', async () => {
|
|
143
|
+
const { textinput } = await createTextinputTest({ readonly: true })
|
|
144
|
+
|
|
145
|
+
const inputElement = textinput.querySelector('input')
|
|
146
|
+
expect(inputElement?.hasAttribute('readonly')).toBe(true)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
test('handles required state', async () => {
|
|
150
|
+
const { textinput } = await createTextinputTest({ required: true })
|
|
151
|
+
|
|
152
|
+
const inputElement = textinput.querySelector('input')
|
|
153
|
+
expect(inputElement?.hasAttribute('required')).toBe(false) // Not set as attribute on input
|
|
154
|
+
|
|
155
|
+
const inputWrapper = textinput.querySelector('pkt-input-wrapper')
|
|
156
|
+
expect(inputWrapper?.hasAttribute('required')).toBe(true) // But passed to wrapper
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
describe('Icons', () => {
|
|
161
|
+
test('renders right icon', async () => {
|
|
162
|
+
const { textinput } = await createTextinputTest({
|
|
163
|
+
iconNameRight: 'search',
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
expect(textinput.iconNameRight).toBe('search')
|
|
167
|
+
|
|
168
|
+
const icon = textinput.querySelector('pkt-icon')
|
|
169
|
+
expect(icon).toBeInTheDocument()
|
|
170
|
+
expect(icon?.getAttribute('name')).toBe('search')
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
test('renders search icon for search type by default', async () => {
|
|
174
|
+
const { textinput } = await createTextinputTest({ type: 'search' })
|
|
175
|
+
|
|
176
|
+
const icon = textinput.querySelector('pkt-icon')
|
|
177
|
+
expect(icon).toBeInTheDocument()
|
|
178
|
+
expect(icon?.getAttribute('name')).toBe('magnifying-glass-big')
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
test('can omit search icon for search type', async () => {
|
|
182
|
+
const { textinput } = await createTextinputTest({
|
|
183
|
+
type: 'search',
|
|
184
|
+
omitSearchIcon: true,
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
const icon = textinput.querySelector('pkt-icon')
|
|
188
|
+
expect(icon).not.toBeInTheDocument()
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
describe('Prefix and Suffix', () => {
|
|
193
|
+
test('renders prefix text', async () => {
|
|
194
|
+
const { textinput } = await createTextinputTest({ prefix: 'https://' })
|
|
195
|
+
|
|
196
|
+
expect(textinput.prefix).toBe('https://')
|
|
197
|
+
const prefixElement = textinput.querySelector('.pkt-input-prefix')
|
|
198
|
+
expect(prefixElement).toBeInTheDocument()
|
|
199
|
+
expect(prefixElement?.textContent).toBe('https://')
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
test('renders suffix text', async () => {
|
|
203
|
+
const { textinput } = await createTextinputTest({ suffix: '.com' })
|
|
204
|
+
|
|
205
|
+
expect(textinput.suffix).toBe('.com')
|
|
206
|
+
const suffixElement = textinput.querySelector('.pkt-input-suffix')
|
|
207
|
+
expect(suffixElement).toBeInTheDocument()
|
|
208
|
+
expect(suffixElement?.textContent?.trim()).toBe('.com')
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
test('renders both prefix and suffix', async () => {
|
|
212
|
+
const { textinput } = await createTextinputTest({
|
|
213
|
+
prefix: '$',
|
|
214
|
+
suffix: 'USD',
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const prefixElement = textinput.querySelector('.pkt-input-prefix')
|
|
218
|
+
const suffixElement = textinput.querySelector('.pkt-input-suffix')
|
|
219
|
+
|
|
220
|
+
expect(prefixElement?.textContent).toBe('$')
|
|
221
|
+
expect(suffixElement?.textContent?.trim()).toBe('USD')
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
describe('Input Wrapper Integration', () => {
|
|
226
|
+
test('displays label correctly', async () => {
|
|
227
|
+
const { textinput } = await createTextinputTest({ label: 'Email Address' })
|
|
228
|
+
|
|
229
|
+
const inputWrapper = textinput.querySelector('pkt-input-wrapper')
|
|
230
|
+
expect(inputWrapper?.getAttribute('label')).toBe('Email Address')
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
test('displays helptext correctly', async () => {
|
|
234
|
+
const { textinput } = await createTextinputTest({ helptext: 'Enter a valid email' })
|
|
235
|
+
|
|
236
|
+
// helptext is passed as a property, not attribute to input-wrapper
|
|
237
|
+
expect(textinput.helptext).toBe('Enter a valid email')
|
|
238
|
+
})
|
|
239
|
+
|
|
240
|
+
test('handles error state', async () => {
|
|
241
|
+
const { textinput } = await createTextinputTest({
|
|
242
|
+
hasError: true,
|
|
243
|
+
errorMessage: 'Email is required',
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
expect(textinput.hasError).toBe(true)
|
|
247
|
+
expect(textinput.errorMessage).toBe('Email is required')
|
|
248
|
+
|
|
249
|
+
const inputWrapper = textinput.querySelector('pkt-input-wrapper')
|
|
250
|
+
expect(inputWrapper?.hasAttribute('hasError')).toBe(true)
|
|
251
|
+
|
|
252
|
+
const inputElement = textinput.querySelector('input')
|
|
253
|
+
expect(inputElement?.getAttribute('aria-invalid')).toBe('true')
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
test('handles fullwidth styling', async () => {
|
|
257
|
+
const { textinput } = await createTextinputTest({ fullwidth: true })
|
|
258
|
+
|
|
259
|
+
const inputElement = textinput.querySelector('input')
|
|
260
|
+
expect(inputElement?.className).toContain('pkt-input--fullwidth')
|
|
261
|
+
})
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
describe('Character Counter', () => {
|
|
265
|
+
test('shows counter when enabled', async () => {
|
|
266
|
+
const { textinput } = await createTextinputTest({
|
|
267
|
+
counter: true,
|
|
268
|
+
maxlength: 50,
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
const inputWrapper = textinput.querySelector('pkt-input-wrapper')
|
|
272
|
+
expect(inputWrapper?.hasAttribute('counter')).toBe(true)
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test('updates counter on value change', async () => {
|
|
276
|
+
const { textinput } = await createTextinputTest({
|
|
277
|
+
counter: true,
|
|
278
|
+
maxlength: 50,
|
|
279
|
+
value: 'Hello',
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
expect(textinput.counterCurrent).toBe(5)
|
|
283
|
+
})
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
describe('User Interaction', () => {
|
|
287
|
+
test('updates value on user input', async () => {
|
|
288
|
+
const { textinput } = await createTextinputTest()
|
|
289
|
+
const inputElement = textinput.querySelector('input') as HTMLInputElement
|
|
290
|
+
|
|
291
|
+
fireEvent.input(inputElement, { target: { value: 'new value' } })
|
|
292
|
+
await textinput.updateComplete
|
|
293
|
+
|
|
294
|
+
expect(textinput.value).toBe('new value')
|
|
295
|
+
expect(textinput.touched).toBe(true)
|
|
296
|
+
})
|
|
297
|
+
|
|
298
|
+
test('handles focus and blur events', async () => {
|
|
299
|
+
const { textinput } = await createTextinputTest()
|
|
300
|
+
const inputElement = textinput.querySelector('input') as HTMLInputElement
|
|
301
|
+
|
|
302
|
+
// Focus and input to trigger touched state
|
|
303
|
+
fireEvent.focus(inputElement)
|
|
304
|
+
fireEvent.input(inputElement, { target: { value: 'test input' } })
|
|
305
|
+
await textinput.updateComplete
|
|
306
|
+
fireEvent.blur(inputElement)
|
|
307
|
+
await textinput.updateComplete
|
|
308
|
+
|
|
309
|
+
// Test that input with value change sets touched state
|
|
310
|
+
expect(textinput.touched).toBe(true)
|
|
311
|
+
})
|
|
312
|
+
})
|
|
313
|
+
|
|
314
|
+
describe('Validation', () => {
|
|
315
|
+
test('respects maxlength constraint', async () => {
|
|
316
|
+
const { textinput } = await createTextinputTest({ maxlength: 20 })
|
|
317
|
+
|
|
318
|
+
const inputElement = textinput.querySelector('input')
|
|
319
|
+
expect(inputElement?.getAttribute('maxlength')).toBe('20')
|
|
320
|
+
})
|
|
321
|
+
|
|
322
|
+
test('respects minlength constraint', async () => {
|
|
323
|
+
const { textinput } = await createTextinputTest({ minlength: 3 })
|
|
324
|
+
|
|
325
|
+
const inputElement = textinput.querySelector('input')
|
|
326
|
+
expect(inputElement?.getAttribute('minlength')).toBe('3')
|
|
327
|
+
})
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
describe('Accessibility', () => {
|
|
331
|
+
test('passes through accessibility attributes', async () => {
|
|
332
|
+
const { textinput } = await createTextinputTest({
|
|
333
|
+
ariaLabelledby: 'external-label',
|
|
334
|
+
ariaDescribedBy: 'external-description',
|
|
335
|
+
})
|
|
336
|
+
|
|
337
|
+
const inputElement = textinput.querySelector('input')
|
|
338
|
+
expect(inputElement?.getAttribute('aria-labelledby')).toBe('external-label')
|
|
339
|
+
|
|
340
|
+
// ariaDescribedBy is passed as property to input-wrapper
|
|
341
|
+
expect(textinput.ariaDescribedBy).toBe('external-description')
|
|
342
|
+
})
|
|
343
|
+
|
|
344
|
+
test('textinput is accessible', async () => {
|
|
345
|
+
const { textinput } = await createTextinputTest({
|
|
346
|
+
label: 'Email',
|
|
347
|
+
type: 'email',
|
|
348
|
+
helptext: 'Enter your email address',
|
|
349
|
+
required: true,
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
const results = await axe(textinput)
|
|
353
|
+
expect(results).toHaveNoViolations()
|
|
354
|
+
})
|
|
355
|
+
})
|
|
356
|
+
|
|
357
|
+
describe('Complex Configuration', () => {
|
|
358
|
+
test('renders email input with all features', async () => {
|
|
359
|
+
const config: TextinputTestConfig = {
|
|
360
|
+
type: 'email',
|
|
361
|
+
label: 'Email Address',
|
|
362
|
+
value: 'user@example.com',
|
|
363
|
+
placeholder: 'Enter your email...',
|
|
364
|
+
iconNameRight: 'mail',
|
|
365
|
+
maxlength: 100,
|
|
366
|
+
counter: true,
|
|
367
|
+
required: true,
|
|
368
|
+
autocomplete: 'email',
|
|
369
|
+
helptext: 'We will never share your email',
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const { textinput } = await createTextinputTest(config)
|
|
373
|
+
|
|
374
|
+
expect(textinput.type).toBe(config.type)
|
|
375
|
+
expect(textinput.value).toBe(config.value)
|
|
376
|
+
expect(textinput.iconNameRight).toBe(config.iconNameRight)
|
|
377
|
+
expect(textinput.maxlength).toBe(config.maxlength)
|
|
378
|
+
expect(textinput.required).toBe(config.required)
|
|
379
|
+
expect(textinput.autocomplete).toBe(config.autocomplete)
|
|
380
|
+
|
|
381
|
+
const inputWrapper = textinput.querySelector('pkt-input-wrapper')
|
|
382
|
+
expect(inputWrapper?.getAttribute('label')).toBe(config.label)
|
|
383
|
+
expect(textinput.helptext).toBe(config.helptext) // Property, not attribute
|
|
384
|
+
expect(inputWrapper?.hasAttribute('counter')).toBe(true)
|
|
385
|
+
|
|
386
|
+
const inputElement = textinput.querySelector('input')
|
|
387
|
+
expect(inputElement?.getAttribute('type')).toBe(config.type)
|
|
388
|
+
expect(inputElement?.getAttribute('placeholder')).toBe(config.placeholder)
|
|
389
|
+
expect(inputElement?.getAttribute('maxlength')).toBe(String(config.maxlength))
|
|
390
|
+
expect(inputElement?.getAttribute('autocomplete')).toBe(config.autocomplete)
|
|
391
|
+
|
|
392
|
+
const icon = textinput.querySelector('pkt-icon')
|
|
393
|
+
expect(icon?.getAttribute('name')).toBe(config.iconNameRight)
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
test('renders URL input with prefix and suffix', async () => {
|
|
397
|
+
const config: TextinputTestConfig = {
|
|
398
|
+
type: 'url',
|
|
399
|
+
label: 'Website URL',
|
|
400
|
+
prefix: 'https://',
|
|
401
|
+
suffix: '.com',
|
|
402
|
+
placeholder: 'example',
|
|
403
|
+
fullwidth: true,
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const { textinput } = await createTextinputTest(config)
|
|
407
|
+
|
|
408
|
+
expect(textinput.prefix).toBe(config.prefix)
|
|
409
|
+
expect(textinput.suffix).toBe(config.suffix)
|
|
410
|
+
|
|
411
|
+
const prefixElement = textinput.querySelector('.pkt-input-prefix')
|
|
412
|
+
const suffixElement = textinput.querySelector('.pkt-input-suffix')
|
|
413
|
+
|
|
414
|
+
expect(prefixElement?.textContent).toBe(config.prefix)
|
|
415
|
+
expect(suffixElement?.textContent?.trim()).toBe(config.suffix)
|
|
416
|
+
|
|
417
|
+
const inputElement = textinput.querySelector('input')
|
|
418
|
+
expect(inputElement?.className).toContain('pkt-input--fullwidth')
|
|
419
|
+
})
|
|
420
|
+
})
|
|
421
|
+
})
|