mtrl 0.0.2 → 0.1.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/package.json +2 -2
- package/src/components/button/styles.scss +198 -161
- package/src/components/checkbox/checkbox.js +4 -3
- package/src/components/checkbox/styles.scss +105 -55
- package/src/components/container/styles.scss +65 -58
- package/src/components/list/styles.scss +240 -11
- package/src/components/menu/features/items-manager.js +5 -1
- package/src/components/menu/styles.scss +37 -30
- package/src/components/navigation/constants.js +19 -54
- package/src/components/navigation/styles.scss +406 -6
- package/src/components/snackbar/styles.scss +46 -17
- package/src/components/switch/styles.scss +104 -40
- package/src/components/switch/switch.js +1 -1
- package/src/components/textfield/styles.scss +351 -5
- package/src/core/build/_ripple.scss +79 -0
- package/src/core/compose/features/disabled.js +27 -7
- package/src/core/compose/features/input.js +9 -1
- package/src/core/compose/features/textinput.js +16 -20
- package/src/core/dom/create.js +0 -1
- package/src/styles/abstract/_mixins.scss +9 -7
- package/src/styles/abstract/_theme.scss +157 -0
- package/src/styles/abstract/_variables.scss +72 -6
- package/src/styles/base/_reset.scss +86 -0
- package/src/styles/base/_typography.scss +155 -0
- package/src/styles/main.scss +104 -57
- package/src/styles/themes/_base-theme.scss +2 -27
- package/src/styles/themes/_baseline.scss +64 -39
- package/src/styles/utilities/_color.scss +154 -0
- package/src/styles/utilities/_flexbox.scss +194 -0
- package/src/styles/utilities/_spacing.scss +139 -0
- package/src/styles/utilities/_typography.scss +178 -0
- package/src/styles/utilities/_visibility.scss +142 -0
- package/test/components/button.test.js +46 -34
- package/test/components/checkbox.test.js +238 -0
- package/test/components/list.test.js +105 -0
- package/test/components/menu.test.js +385 -0
- package/test/components/navigation.test.js +227 -0
- package/test/components/snackbar.test.js +234 -0
- package/test/components/switch.test.js +186 -0
- package/test/components/textfield.test.js +314 -0
- package/test/core/ripple.test.js +21 -120
- package/test/setup.js +152 -239
- package/src/components/list/styles/_list-item.scss +0 -142
- package/src/components/list/styles/_list.scss +0 -89
- package/src/components/list/styles/_variables.scss +0 -13
- package/src/components/navigation/styles/_bar.scss +0 -51
- package/src/components/navigation/styles/_base.scss +0 -129
- package/src/components/navigation/styles/_drawer.scss +0 -169
- package/src/components/navigation/styles/_rail.scss +0 -65
- package/src/components/textfield/styles/base.scss +0 -107
- package/src/components/textfield/styles/filled.scss +0 -58
- package/src/components/textfield/styles/outlined.scss +0 -66
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { describe, test, expect } from 'bun:test'
|
|
2
|
+
import createList from '../../src/components/list/list'
|
|
3
|
+
import { LIST_TYPES } from '../../src/components/list/constants'
|
|
4
|
+
|
|
5
|
+
describe('List Component', () => {
|
|
6
|
+
test('should create a default list element', () => {
|
|
7
|
+
const list = createList({
|
|
8
|
+
items: [{ id: 'item1', headline: 'Item 1' }]
|
|
9
|
+
})
|
|
10
|
+
expect(list.element).toBeDefined()
|
|
11
|
+
// Default type is "default" and role "list"
|
|
12
|
+
expect(list.element.getAttribute('data-type')).toBe(LIST_TYPES.DEFAULT)
|
|
13
|
+
expect(list.element.getAttribute('role')).toBe('list')
|
|
14
|
+
// Check at least one list item exists
|
|
15
|
+
const listItem = list.element.querySelector(`.${list.prefix}-list-item`)
|
|
16
|
+
expect(listItem).not.toBeNull()
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('should support single select behavior', () => {
|
|
20
|
+
const list = createList({
|
|
21
|
+
type: LIST_TYPES.SINGLE_SELECT,
|
|
22
|
+
items: [
|
|
23
|
+
{ id: 'item1', headline: 'Item 1' },
|
|
24
|
+
{ id: 'item2', headline: 'Item 2' }
|
|
25
|
+
]
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
// Simulate clicking on the first item
|
|
29
|
+
const items = list.element.querySelectorAll(`.${list.prefix}-list-item`)
|
|
30
|
+
const firstItem = items[0]
|
|
31
|
+
firstItem.dispatchEvent(new Event('click'))
|
|
32
|
+
expect(firstItem.getAttribute('aria-selected')).toBe('true')
|
|
33
|
+
|
|
34
|
+
// Now click the second item; the first should be deselected
|
|
35
|
+
const secondItem = items[1]
|
|
36
|
+
secondItem.dispatchEvent(new Event('click'))
|
|
37
|
+
expect(firstItem.getAttribute('aria-selected')).toBe('false')
|
|
38
|
+
expect(secondItem.getAttribute('aria-selected')).toBe('true')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('should support multi select behavior', () => {
|
|
42
|
+
const list = createList({
|
|
43
|
+
type: LIST_TYPES.MULTI_SELECT,
|
|
44
|
+
items: [
|
|
45
|
+
{ id: 'item1', headline: 'Item 1' },
|
|
46
|
+
{ id: 'item2', headline: 'Item 2' }
|
|
47
|
+
]
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
const items = list.element.querySelectorAll(`.${list.prefix}-list-item`)
|
|
51
|
+
const firstItem = items[0]
|
|
52
|
+
const secondItem = items[1]
|
|
53
|
+
|
|
54
|
+
// Click to select first item
|
|
55
|
+
firstItem.dispatchEvent(new Event('click'))
|
|
56
|
+
expect(firstItem.getAttribute('aria-selected')).toBe('true')
|
|
57
|
+
|
|
58
|
+
// Click to select second item
|
|
59
|
+
secondItem.dispatchEvent(new Event('click'))
|
|
60
|
+
expect(secondItem.getAttribute('aria-selected')).toBe('true')
|
|
61
|
+
expect(list.getSelected().length).toBe(2)
|
|
62
|
+
|
|
63
|
+
// Click first item again to deselect it
|
|
64
|
+
firstItem.dispatchEvent(new Event('click'))
|
|
65
|
+
expect(firstItem.getAttribute('aria-selected')).toBe('false')
|
|
66
|
+
expect(list.getSelected().length).toBe(1)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('should set selected items via setSelected', () => {
|
|
70
|
+
const list = createList({
|
|
71
|
+
type: LIST_TYPES.MULTI_SELECT,
|
|
72
|
+
items: [
|
|
73
|
+
{ id: 'item1', headline: 'Item 1' },
|
|
74
|
+
{ id: 'item2', headline: 'Item 2' },
|
|
75
|
+
{ id: 'item3', headline: 'Item 3' }
|
|
76
|
+
]
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
list.setSelected(['item2', 'item3'])
|
|
80
|
+
const items = Array.from(
|
|
81
|
+
list.element.querySelectorAll(`.${list.prefix}-list-item`)
|
|
82
|
+
)
|
|
83
|
+
const item2 = items.find(i => i.dataset.id === 'item2')
|
|
84
|
+
const item3 = items.find(i => i.dataset.id === 'item3')
|
|
85
|
+
|
|
86
|
+
expect(item2.getAttribute('aria-selected')).toBe('true')
|
|
87
|
+
expect(item3.getAttribute('aria-selected')).toBe('true')
|
|
88
|
+
expect(list.getSelected()).toEqual(expect.arrayContaining(['item2', 'item3']))
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test('should add and remove items dynamically', () => {
|
|
92
|
+
const list = createList({
|
|
93
|
+
items: [{ id: 'item1', headline: 'Item 1' }]
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const initialCount = list.element.querySelectorAll(`.${list.prefix}-list-item`).length
|
|
97
|
+
list.addItem({ id: 'item2', headline: 'Item 2' })
|
|
98
|
+
const newCount = list.element.querySelectorAll(`.${list.prefix}-list-item`).length
|
|
99
|
+
expect(newCount).toBe(initialCount + 1)
|
|
100
|
+
|
|
101
|
+
list.removeItem('item1')
|
|
102
|
+
const finalCount = list.element.querySelectorAll(`.${list.prefix}-list-item`).length
|
|
103
|
+
expect(finalCount).toBe(newCount - 1)
|
|
104
|
+
})
|
|
105
|
+
})
|
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
// test/components/menu.test.js
|
|
2
|
+
import { describe, test, expect, mock, beforeEach, afterEach } from 'bun:test'
|
|
3
|
+
import createMenu from '../../src/components/menu/menu'
|
|
4
|
+
import { MENU_ALIGN, MENU_VERTICAL_ALIGN, MENU_EVENTS, MENU_ITEM_TYPES } from '../../src/components/menu/constants'
|
|
5
|
+
|
|
6
|
+
// Mock DOM APIs that aren't available in the test environment
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
// Mock Element.prototype methods
|
|
9
|
+
Element.prototype.getBoundingClientRect = function () {
|
|
10
|
+
return {
|
|
11
|
+
width: 100,
|
|
12
|
+
height: 100,
|
|
13
|
+
top: 0,
|
|
14
|
+
left: 0,
|
|
15
|
+
right: 100,
|
|
16
|
+
bottom: 100
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
Element.prototype.closest = function (selector) {
|
|
21
|
+
return null // Simple mock that returns null by default
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
Element.prototype.matches = function (selector) {
|
|
25
|
+
return false // Simple mock that returns false by default
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Save original createElement to restore later
|
|
29
|
+
const originalCreateElement = document.createElement
|
|
30
|
+
document.createElement = function (tag) {
|
|
31
|
+
const element = originalCreateElement.call(document, tag)
|
|
32
|
+
|
|
33
|
+
// Add closest method for our tests
|
|
34
|
+
element.closest = function (selector) {
|
|
35
|
+
if (selector.includes('menu-item')) {
|
|
36
|
+
return this.classList && this.classList.contains('mtrl-menu-item') ? this : null
|
|
37
|
+
}
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Add matches method for our tests
|
|
42
|
+
element.matches = function (selector) {
|
|
43
|
+
if (selector === ':hover') return false
|
|
44
|
+
return this.classList && this.classList.contains(selector.replace('.', ''))
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Mock the querySelectorAll method
|
|
48
|
+
element.querySelectorAll = function (selector) {
|
|
49
|
+
return [] // Return empty array by default
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Mock the querySelector method
|
|
53
|
+
element.querySelector = function (selector) {
|
|
54
|
+
return null // Return null by default
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return element
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Mock window properties
|
|
61
|
+
global.window = {
|
|
62
|
+
...global.window,
|
|
63
|
+
innerWidth: 1024,
|
|
64
|
+
innerHeight: 768
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Mock event listeners
|
|
68
|
+
if (!global.eventListeners) {
|
|
69
|
+
global.eventListeners = new Map()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const originalAddEventListener = Element.prototype.addEventListener
|
|
73
|
+
Element.prototype.addEventListener = function (event, handler) {
|
|
74
|
+
if (!global.eventListeners.has(this)) {
|
|
75
|
+
global.eventListeners.set(this, new Map())
|
|
76
|
+
}
|
|
77
|
+
if (!global.eventListeners.get(this).has(event)) {
|
|
78
|
+
global.eventListeners.get(this).set(event, new Set())
|
|
79
|
+
}
|
|
80
|
+
global.eventListeners.get(this).get(event).add(handler)
|
|
81
|
+
|
|
82
|
+
// Call original if it exists
|
|
83
|
+
if (originalAddEventListener) {
|
|
84
|
+
originalAddEventListener.call(this, event, handler)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const originalRemoveEventListener = Element.prototype.removeEventListener
|
|
89
|
+
Element.prototype.removeEventListener = function (event, handler) {
|
|
90
|
+
if (global.eventListeners.has(this) &&
|
|
91
|
+
global.eventListeners.get(this).has(event)) {
|
|
92
|
+
global.eventListeners.get(this).get(event).delete(handler)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Call original if it exists
|
|
96
|
+
if (originalRemoveEventListener) {
|
|
97
|
+
originalRemoveEventListener.call(this, event, handler)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Mock offsetHeight/offsetWidth
|
|
102
|
+
Object.defineProperty(Element.prototype, 'offsetHeight', {
|
|
103
|
+
configurable: true,
|
|
104
|
+
get: function () { return 100 }
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
Object.defineProperty(Element.prototype, 'offsetWidth', {
|
|
108
|
+
configurable: true,
|
|
109
|
+
get: function () { return 100 }
|
|
110
|
+
})
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
afterEach(() => {
|
|
114
|
+
// Clean up our mocks
|
|
115
|
+
delete Element.prototype.getBoundingClientRect
|
|
116
|
+
delete Element.prototype.closest
|
|
117
|
+
delete Element.prototype.matches
|
|
118
|
+
|
|
119
|
+
document.createElement = document.createElement.__originalFunction || document.createElement
|
|
120
|
+
|
|
121
|
+
// Clear event listeners
|
|
122
|
+
if (global.eventListeners) {
|
|
123
|
+
global.eventListeners.clear()
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
describe('Menu Component', () => {
|
|
128
|
+
// Sample menu items for testing
|
|
129
|
+
const testItems = [
|
|
130
|
+
{
|
|
131
|
+
name: 'copy',
|
|
132
|
+
text: 'Copy'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'paste',
|
|
136
|
+
text: 'Paste'
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'divider'
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'delete',
|
|
143
|
+
text: 'Delete',
|
|
144
|
+
disabled: true
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
// Sample nested menu items
|
|
149
|
+
const nestedTestItems = [
|
|
150
|
+
{
|
|
151
|
+
name: 'file',
|
|
152
|
+
text: 'File',
|
|
153
|
+
items: [
|
|
154
|
+
{
|
|
155
|
+
name: 'new',
|
|
156
|
+
text: 'New'
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'open',
|
|
160
|
+
text: 'Open'
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'edit',
|
|
166
|
+
text: 'Edit',
|
|
167
|
+
items: [
|
|
168
|
+
{
|
|
169
|
+
name: 'copy',
|
|
170
|
+
text: 'Copy'
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'paste',
|
|
174
|
+
text: 'Paste'
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
test('should create a menu element', () => {
|
|
181
|
+
const menu = createMenu()
|
|
182
|
+
|
|
183
|
+
expect(menu.element).toBeDefined()
|
|
184
|
+
expect(menu.element.tagName).toBe('DIV')
|
|
185
|
+
expect(menu.element.className).toContain('mtrl-menu')
|
|
186
|
+
expect(menu.element.getAttribute('role')).toBe('menu')
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
test('should apply custom class', () => {
|
|
190
|
+
const customClass = 'custom-menu'
|
|
191
|
+
const menu = createMenu({
|
|
192
|
+
class: customClass
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
expect(menu.element.className).toContain(customClass)
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
test('should add initial items', () => {
|
|
199
|
+
const menu = createMenu({
|
|
200
|
+
items: testItems
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
// Check if items methods exist
|
|
204
|
+
expect(typeof menu.getItems).toBe('function')
|
|
205
|
+
|
|
206
|
+
// Get items and verify we have a Map
|
|
207
|
+
const items = menu.getItems()
|
|
208
|
+
expect(items instanceof Map).toBe(true)
|
|
209
|
+
|
|
210
|
+
// Verify item names in map
|
|
211
|
+
expect(items.has('copy')).toBe(true)
|
|
212
|
+
expect(items.has('paste')).toBe(true)
|
|
213
|
+
expect(items.has('delete')).toBe(true)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('should have show/hide methods', () => {
|
|
217
|
+
const menu = createMenu()
|
|
218
|
+
|
|
219
|
+
// Check for API methods
|
|
220
|
+
expect(typeof menu.show).toBe('function')
|
|
221
|
+
expect(typeof menu.hide).toBe('function')
|
|
222
|
+
expect(typeof menu.isVisible).toBe('function')
|
|
223
|
+
|
|
224
|
+
// Test visibility state
|
|
225
|
+
expect(menu.isVisible()).toBe(false)
|
|
226
|
+
|
|
227
|
+
// Show menu
|
|
228
|
+
menu.show()
|
|
229
|
+
expect(menu.isVisible()).toBe(true)
|
|
230
|
+
expect(menu.element.classList.contains('mtrl-menu--visible')).toBe(true)
|
|
231
|
+
|
|
232
|
+
// Hide menu
|
|
233
|
+
menu.hide()
|
|
234
|
+
|
|
235
|
+
// Note: Due to animations, isVisible() might still return true immediately after hide()
|
|
236
|
+
// In a real environment, we'd wait for transitions to complete
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
test('should have positioning methods', () => {
|
|
240
|
+
const menu = createMenu()
|
|
241
|
+
const target = document.createElement('button')
|
|
242
|
+
|
|
243
|
+
// Check for API method
|
|
244
|
+
expect(typeof menu.position).toBe('function')
|
|
245
|
+
|
|
246
|
+
// Test with different alignments
|
|
247
|
+
const positionConfigs = [
|
|
248
|
+
{ align: MENU_ALIGN.LEFT, vAlign: MENU_VERTICAL_ALIGN.TOP },
|
|
249
|
+
{ align: MENU_ALIGN.RIGHT, vAlign: MENU_VERTICAL_ALIGN.BOTTOM },
|
|
250
|
+
{ align: MENU_ALIGN.CENTER, vAlign: MENU_VERTICAL_ALIGN.MIDDLE }
|
|
251
|
+
]
|
|
252
|
+
|
|
253
|
+
positionConfigs.forEach(config => {
|
|
254
|
+
try {
|
|
255
|
+
menu.position(target, config)
|
|
256
|
+
// If we reach here, no error was thrown
|
|
257
|
+
expect(true).toBe(true)
|
|
258
|
+
} catch (error) {
|
|
259
|
+
// If an error occurs, the test should fail
|
|
260
|
+
expect(error).toBeUndefined()
|
|
261
|
+
}
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
test('should add item dynamically', () => {
|
|
266
|
+
const menu = createMenu()
|
|
267
|
+
|
|
268
|
+
// Check for API method
|
|
269
|
+
expect(typeof menu.addItem).toBe('function')
|
|
270
|
+
|
|
271
|
+
// Test adding an item
|
|
272
|
+
const newItem = {
|
|
273
|
+
name: 'newItem',
|
|
274
|
+
text: 'New Item'
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
menu.addItem(newItem)
|
|
278
|
+
|
|
279
|
+
// Verify item was added
|
|
280
|
+
const items = menu.getItems()
|
|
281
|
+
expect(items.has('newItem')).toBe(true)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
test('should remove item dynamically', () => {
|
|
285
|
+
const menu = createMenu({
|
|
286
|
+
items: testItems
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
// Check for API method
|
|
290
|
+
expect(typeof menu.removeItem).toBe('function')
|
|
291
|
+
|
|
292
|
+
// Test removing an item
|
|
293
|
+
menu.removeItem('copy')
|
|
294
|
+
|
|
295
|
+
// Verify item was removed
|
|
296
|
+
const items = menu.getItems()
|
|
297
|
+
expect(items.has('copy')).toBe(false)
|
|
298
|
+
})
|
|
299
|
+
|
|
300
|
+
test('should register event handlers', () => {
|
|
301
|
+
const menu = createMenu()
|
|
302
|
+
|
|
303
|
+
// Check for API methods
|
|
304
|
+
expect(typeof menu.on).toBe('function')
|
|
305
|
+
expect(typeof menu.off).toBe('function')
|
|
306
|
+
|
|
307
|
+
// Create a mock handler
|
|
308
|
+
const mockHandler = mock(() => {})
|
|
309
|
+
|
|
310
|
+
// Register handler
|
|
311
|
+
menu.on(MENU_EVENTS.SELECT, mockHandler)
|
|
312
|
+
|
|
313
|
+
// We can't easily test if the handler is called in this environment
|
|
314
|
+
// But we can check that the method works without error
|
|
315
|
+
expect(mockHandler.mock.calls.length).toBe(0)
|
|
316
|
+
|
|
317
|
+
// Unregister handler
|
|
318
|
+
menu.off(MENU_EVENTS.SELECT, mockHandler)
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
test('should create nested menus for items with children', () => {
|
|
322
|
+
// This test would be more complex in a real environment
|
|
323
|
+
// For now, just verify the basic menu creation works with nested items
|
|
324
|
+
|
|
325
|
+
const menu = createMenu({
|
|
326
|
+
items: nestedTestItems
|
|
327
|
+
})
|
|
328
|
+
|
|
329
|
+
// Verify parent items exist
|
|
330
|
+
const items = menu.getItems()
|
|
331
|
+
expect(items.has('file')).toBe(true)
|
|
332
|
+
expect(items.has('edit')).toBe(true)
|
|
333
|
+
|
|
334
|
+
// We can't easily test the submenu creation here
|
|
335
|
+
// But we can check that the parent items are created without error
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
test('should properly clean up resources on destroy', () => {
|
|
339
|
+
const menu = createMenu()
|
|
340
|
+
|
|
341
|
+
// Check for API method
|
|
342
|
+
expect(typeof menu.destroy).toBe('function')
|
|
343
|
+
|
|
344
|
+
const parentElement = document.createElement('div')
|
|
345
|
+
parentElement.appendChild(menu.element)
|
|
346
|
+
|
|
347
|
+
// Destroy the component
|
|
348
|
+
menu.destroy()
|
|
349
|
+
|
|
350
|
+
// Check if element was removed
|
|
351
|
+
expect(parentElement.children.length).toBe(0)
|
|
352
|
+
})
|
|
353
|
+
|
|
354
|
+
test('should support keyboard navigation', () => {
|
|
355
|
+
// Skip detailed keyboard navigation tests due to test environment limitations
|
|
356
|
+
// Just verify the API methods exist
|
|
357
|
+
|
|
358
|
+
const menu = createMenu()
|
|
359
|
+
|
|
360
|
+
// Show the menu to initialize keyboard handlers
|
|
361
|
+
menu.show()
|
|
362
|
+
|
|
363
|
+
// In a real environment, we would dispatch keydown events and check results
|
|
364
|
+
// But here we just verify the basic setup happens without errors
|
|
365
|
+
|
|
366
|
+
// Hide and clean up
|
|
367
|
+
menu.hide()
|
|
368
|
+
})
|
|
369
|
+
|
|
370
|
+
test('should handle outside clicks', () => {
|
|
371
|
+
// This would typically close the menu
|
|
372
|
+
// We can't fully test this behavior in the current environment
|
|
373
|
+
|
|
374
|
+
const menu = createMenu()
|
|
375
|
+
menu.show()
|
|
376
|
+
|
|
377
|
+
// In a real environment, we would:
|
|
378
|
+
// 1. Create a click event outside the menu
|
|
379
|
+
// 2. Dispatch it
|
|
380
|
+
// 3. Verify menu is hidden
|
|
381
|
+
|
|
382
|
+
// For now, just ensure our menu API method is called without error
|
|
383
|
+
menu.hide()
|
|
384
|
+
})
|
|
385
|
+
})
|