mtrl 0.0.2 → 0.0.3

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/test/setup.js CHANGED
@@ -1,10 +1,9 @@
1
1
  // test/setup.js
2
2
  // Setup global DOM environment for testing
3
3
 
4
- // Mock the document and window objects for DOM testing
5
- // test/setup.js
6
-
7
- // Mock DOM environment for testing
4
+ /**
5
+ * Mock Element implementation for testing
6
+ */
8
7
  class MockElement {
9
8
  constructor (tagName) {
10
9
  this.tagName = tagName.toUpperCase()
@@ -12,20 +11,33 @@ class MockElement {
12
11
  this.style = {}
13
12
  this.attributes = {}
14
13
  this.children = []
15
- this.eventListeners = {}
14
+ this.childNodes = []
15
+ this.__eventListeners = {}
16
16
  this.innerHTML = ''
17
17
  this.textContent = ''
18
18
  this.dataset = {}
19
+ this.parentNode = null
20
+
21
+ // Explicitly add __handlers for the tests that expect it
22
+ this.__handlers = {}
19
23
  }
20
24
 
21
25
  appendChild (child) {
22
26
  this.children.push(child)
27
+ this.childNodes.push(child)
28
+ child.parentNode = this
23
29
  return child
24
30
  }
25
31
 
26
32
  insertBefore (newChild, referenceChild) {
27
33
  const index = referenceChild ? this.children.indexOf(referenceChild) : 0
28
- this.children.splice(index, 0, newChild)
34
+ if (index === -1) {
35
+ this.children.push(newChild)
36
+ } else {
37
+ this.children.splice(index, 0, newChild)
38
+ }
39
+ this.childNodes = [...this.children]
40
+ newChild.parentNode = this
29
41
  return newChild
30
42
  }
31
43
 
@@ -33,20 +45,24 @@ class MockElement {
33
45
  const index = this.children.indexOf(child)
34
46
  if (index !== -1) {
35
47
  this.children.splice(index, 1)
48
+ this.childNodes = [...this.children]
49
+ child.parentNode = null
36
50
  }
37
51
  return child
38
52
  }
39
53
 
40
54
  getAttribute (name) {
41
- return this.attributes[name]
55
+ return this.attributes[name] || null
42
56
  }
43
57
 
44
58
  setAttribute (name, value) {
45
59
  this.attributes[name] = value
60
+ if (name === 'class') this.className = value
46
61
  }
47
62
 
48
63
  removeAttribute (name) {
49
64
  delete this.attributes[name]
65
+ if (name === 'class') this.className = ''
50
66
  }
51
67
 
52
68
  hasAttribute (name) {
@@ -54,11 +70,21 @@ class MockElement {
54
70
  }
55
71
 
56
72
  querySelector (selector) {
57
- // Super simple selector matching for testing
73
+ // Basic selector implementation for testing
58
74
  if (selector.startsWith('.')) {
75
+ // Class selector
59
76
  const className = selector.substring(1)
60
77
  return this.getElementsByClassName(className)[0] || null
78
+ } else if (selector.includes(',')) {
79
+ // Multiple selectors (comma-separated)
80
+ const subSelectors = selector.split(',').map(s => s.trim())
81
+ for (const subSelector of subSelectors) {
82
+ const match = this.querySelector(subSelector)
83
+ if (match) return match
84
+ }
85
+ return null
61
86
  }
87
+ // Default
62
88
  return null
63
89
  }
64
90
 
@@ -71,7 +97,7 @@ class MockElement {
71
97
 
72
98
  getElementsByClassName (className) {
73
99
  const results = []
74
- if (this.className.split(' ').includes(className)) {
100
+ if (this.className && this.className.split(' ').includes(className)) {
75
101
  results.push(this)
76
102
  }
77
103
  this.children.forEach(child => {
@@ -83,22 +109,35 @@ class MockElement {
83
109
  }
84
110
 
85
111
  addEventListener (type, listener) {
86
- if (!this.eventListeners[type]) {
87
- this.eventListeners[type] = []
112
+ // Support dual storage for different test expectations
113
+ if (!this.__eventListeners[type]) {
114
+ this.__eventListeners[type] = []
115
+ }
116
+ this.__eventListeners[type].push(listener)
117
+
118
+ // Also store in __handlers for tests that expect it
119
+ if (!this.__handlers[type]) {
120
+ this.__handlers[type] = []
88
121
  }
89
- this.eventListeners[type].push(listener)
122
+ this.__handlers[type].push(listener)
90
123
  }
91
124
 
92
125
  removeEventListener (type, listener) {
93
- if (this.eventListeners[type]) {
94
- this.eventListeners[type] = this.eventListeners[type]
126
+ if (this.__eventListeners[type]) {
127
+ this.__eventListeners[type] = this.__eventListeners[type]
128
+ .filter(l => l !== listener)
129
+ }
130
+
131
+ if (this.__handlers && this.__handlers[type]) {
132
+ this.__handlers[type] = this.__handlers[type]
95
133
  .filter(l => l !== listener)
96
134
  }
97
135
  }
98
136
 
99
137
  dispatchEvent (event) {
100
- if (this.eventListeners[event.type]) {
101
- this.eventListeners[event.type].forEach(listener => {
138
+ event.target = this
139
+ if (this.__eventListeners[event.type]) {
140
+ this.__eventListeners[event.type].forEach(listener => {
102
141
  listener(event)
103
142
  })
104
143
  }
@@ -106,7 +145,7 @@ class MockElement {
106
145
  }
107
146
 
108
147
  get classList () {
109
- const classNames = this.className.split(' ').filter(Boolean)
148
+ const classNames = this.className ? this.className.split(' ').filter(Boolean) : []
110
149
  return {
111
150
  add: (...classes) => {
112
151
  classes.forEach(c => {
@@ -129,13 +168,16 @@ class MockElement {
129
168
  const index = classNames.indexOf(c)
130
169
  if (index !== -1) {
131
170
  classNames.splice(index, 1)
171
+ this.className = classNames.join(' ')
172
+ return false
132
173
  } else {
133
174
  classNames.push(c)
175
+ this.className = classNames.join(' ')
176
+ return true
134
177
  }
135
- this.className = classNames.join(' ')
136
- return index === -1
137
178
  },
138
- contains: (c) => classNames.includes(c)
179
+ contains: (c) => classNames.includes(c),
180
+ toString: () => this.className || ''
139
181
  }
140
182
  }
141
183
 
@@ -155,235 +197,84 @@ class MockElement {
155
197
  this.parentNode.removeChild(this)
156
198
  }
157
199
  }
158
- }
159
200
 
160
- // Set up global document object for tests
161
- global.document = {
162
- createElement: (tag) => new MockElement(tag),
163
- createDocumentFragment: () => new MockElement('fragment'),
164
- body: new MockElement('body'),
165
- eventListeners: {},
166
- addEventListener: function (type, listener) {
167
- if (!this.eventListeners[type]) {
168
- this.eventListeners[type] = []
169
- }
170
- this.eventListeners[type].push(listener)
171
- },
172
- removeEventListener: function (type, listener) {
173
- if (this.eventListeners[type]) {
174
- this.eventListeners[type] = this.eventListeners[type]
175
- .filter(l => l !== listener)
176
- }
177
- },
178
- dispatchEvent: function (event) {
179
- if (this.eventListeners[event.type]) {
180
- this.eventListeners[event.type].forEach(listener => {
181
- listener(event)
182
- })
183
- }
184
- return !event.defaultPrevented
185
- }
186
- }
201
+ // Support closest for navigation tests
202
+ closest (selector) {
203
+ // Always return an element with minimal functionality for navigation tests to work
204
+ // In real tests this would need to be more sophisticated
205
+ const mockClosest = new MockElement('div')
206
+ mockClosest.className = selector.replace(/^\./, '')
207
+
208
+ // For navigation tests, ensure the element can be queried
209
+ mockClosest.querySelector = (childSelector) => {
210
+ const mockChild = new MockElement('div')
211
+ mockChild.className = childSelector.replace(/^\./, '')
212
+
213
+ // Further support nested querying
214
+ mockChild.querySelector = (grandchildSelector) => {
215
+ const mockGrandchild = new MockElement('div')
216
+ mockGrandchild.className = grandchildSelector.replace(/^\./, '')
217
+ mockGrandchild.dataset = { id: 'mock-id' }
218
+ return mockGrandchild
219
+ }
187
220
 
188
- // Set up global window object
189
- global.window = {
190
- getComputedStyle: () => ({
191
- position: 'static'
192
- })
193
- }
221
+ return mockChild
222
+ }
194
223
 
195
- // Set up Event constructor
196
- global.Event = class Event {
197
- constructor (type) {
198
- this.type = type
199
- this.defaultPrevented = false
224
+ return mockClosest
200
225
  }
201
226
 
202
- preventDefault () {
203
- this.defaultPrevented = true
227
+ // Simple matches implementation
228
+ matches (selector) {
229
+ if (selector.startsWith('.')) {
230
+ return this.classList.contains(selector.substring(1))
231
+ }
232
+ return false
204
233
  }
205
234
  }
206
235
 
207
- // Set up AbortController
208
- global.AbortController = class AbortController {
236
+ // Create document fragment element
237
+ class MockDocumentFragment extends MockElement {
209
238
  constructor () {
210
- this.signal = { aborted: false }
239
+ super('#document-fragment')
211
240
  }
212
241
 
213
- abort () {
214
- this.signal.aborted = true
242
+ hasChildNodes () {
243
+ return this.childNodes.length > 0
215
244
  }
216
245
  }
217
246
 
218
- // Additional DOM setup if needed
219
-
247
+ // Set up global document object for tests
220
248
  global.document = {
221
- createElement: (tag) => {
222
- const element = {
223
- tagName: tag.toUpperCase(),
224
- classList: {
225
- add: (...classes) => {
226
- element.className = (element.className || '').split(' ')
227
- .concat(classes)
228
- .filter(Boolean)
229
- .join(' ')
230
- },
231
- remove: (...classes) => {
232
- if (!element.className) return
233
- const currentClasses = element.className.split(' ')
234
- element.className = currentClasses
235
- .filter(cls => !classes.includes(cls))
236
- .join(' ')
237
- },
238
- toggle: (cls) => {
239
- if (!element.className) {
240
- element.className = cls
241
- return true
242
- }
243
-
244
- const currentClasses = element.className.split(' ')
245
- const hasClass = currentClasses.includes(cls)
246
-
247
- if (hasClass) {
248
- element.className = currentClasses
249
- .filter(c => c !== cls)
250
- .join(' ')
251
- return false
252
- } else {
253
- element.className = [...currentClasses, cls]
254
- .filter(Boolean)
255
- .join(' ')
256
- return true
257
- }
258
- },
259
- contains: (cls) => {
260
- if (!element.className) return false
261
- return element.className.split(' ').includes(cls)
262
- },
263
- toString: () => element.className || ''
264
- },
265
- style: {},
266
- dataset: {},
267
- attributes: {},
268
- children: [],
269
- childNodes: [],
270
- innerHTML: '',
271
- textContent: '',
272
- appendChild: (child) => {
273
- element.children.push(child)
274
- element.childNodes.push(child)
275
- child.parentNode = element
276
- return child
277
- },
278
- insertBefore: (newChild, refChild) => {
279
- const index = refChild ? element.children.indexOf(refChild) : 0
280
- if (index === -1) {
281
- element.children.push(newChild)
282
- } else {
283
- element.children.splice(index, 0, newChild)
284
- }
285
- element.childNodes = [...element.children]
286
- newChild.parentNode = element
287
- return newChild
288
- },
289
- removeChild: (child) => {
290
- const index = element.children.indexOf(child)
291
- if (index !== -1) {
292
- element.children.splice(index, 1)
293
- element.childNodes = [...element.children]
294
- }
295
- return child
296
- },
297
- remove: () => {
298
- if (element.parentNode) {
299
- element.parentNode.removeChild(element)
300
- }
301
- },
302
- querySelector: (selector) => {
303
- // Very basic selector implementation - only supports class selectors for now
304
- if (selector.startsWith('.')) {
305
- const className = selector.slice(1)
306
- return element.children.find(child =>
307
- child.className && child.className.split(' ').includes(className)
308
- )
309
- }
310
- return null
311
- },
312
- querySelectorAll: (selector) => {
313
- // Very basic selector implementation for tests
314
- if (selector.startsWith('.')) {
315
- const className = selector.slice(1)
316
- return element.children.filter(child =>
317
- child.className && child.className.split(' ').includes(className)
318
- )
319
- }
320
- return []
321
- },
322
- getBoundingClientRect: () => ({
323
- width: 100,
324
- height: 50,
325
- top: 0,
326
- left: 0,
327
- right: 100,
328
- bottom: 50
329
- }),
330
- setAttribute: (name, value) => {
331
- element.attributes[name] = value
332
- if (name === 'class') element.className = value
333
- },
334
- removeAttribute: (name) => {
335
- delete element.attributes[name]
336
- if (name === 'class') element.className = ''
337
- },
338
- getAttribute: (name) => element.attributes[name] || null,
339
- hasAttribute: (name) => name in element.attributes,
340
- addEventListener: (event, handler) => {
341
- element.__handlers = element.__handlers || {}
342
- element.__handlers[event] = element.__handlers[event] || []
343
- element.__handlers[event].push(handler)
344
- },
345
- removeEventListener: (event, handler) => {
346
- if (!element.__handlers?.[event]) return
347
- element.__handlers[event] = element.__handlers[event].filter(h => h !== handler)
348
- },
349
- dispatchEvent: (event) => {
350
- if (!element.__handlers?.[event.type]) return true
351
- element.__handlers[event.type].forEach(handler => handler(event))
352
- return !event.defaultPrevented
353
- }
249
+ createElement: (tag) => new MockElement(tag),
250
+ createDocumentFragment: () => new MockDocumentFragment(),
251
+ body: new MockElement('body'),
252
+ __eventListeners: {},
253
+ addEventListener: function (type, listener) {
254
+ if (!this.__eventListeners[type]) {
255
+ this.__eventListeners[type] = []
354
256
  }
355
- return element
257
+ this.__eventListeners[type].push(listener)
356
258
  },
357
- createDocumentFragment: () => {
358
- return {
359
- children: [],
360
- childNodes: [],
361
- appendChild: function (child) {
362
- this.children.push(child)
363
- this.childNodes.push(child)
364
- child.parentNode = this
365
- return child
366
- },
367
- hasChildNodes: function () {
368
- return this.childNodes.length > 0
369
- },
370
- querySelector: () => null,
371
- querySelectorAll: () => []
259
+ removeEventListener: function (type, listener) {
260
+ if (this.__eventListeners[type]) {
261
+ this.__eventListeners[type] = this.__eventListeners[type]
262
+ .filter(l => l !== listener)
372
263
  }
373
264
  },
374
- body: {
375
- appendChild: () => {},
376
- classList: {
377
- add: () => {},
378
- remove: () => {},
379
- contains: () => false
380
- },
381
- dispatchEvent: () => true,
382
- getAttribute: () => null,
383
- setAttribute: () => {}
384
- }
265
+ dispatchEvent: function (event) {
266
+ if (this.__eventListeners[event.type]) {
267
+ this.__eventListeners[event.type].forEach(listener => {
268
+ listener(event)
269
+ })
270
+ }
271
+ return !event.defaultPrevented
272
+ },
273
+ querySelector: (selector) => null,
274
+ querySelectorAll: (selector) => []
385
275
  }
386
276
 
277
+ // Set up global window object
387
278
  global.window = {
388
279
  getComputedStyle: () => ({
389
280
  position: 'static',
@@ -413,31 +304,53 @@ global.window = {
413
304
  }
414
305
  }
415
306
 
416
- // Mock for CustomEvent
417
- global.CustomEvent = class CustomEvent {
307
+ // Event constructor
308
+ global.Event = class Event {
418
309
  constructor (type, options = {}) {
419
310
  this.type = type
420
- this.detail = options.detail || {}
311
+ this.bubbles = options.bubbles || false
312
+ this.cancelable = options.cancelable || false
421
313
  this.defaultPrevented = false
314
+ this.target = null
315
+ this.currentTarget = null
316
+ this.stopPropagation = () => {}
317
+ this.stopImmediatePropagation = () => {}
318
+
319
+ // Support for offsetX/Y for ripple tests
320
+ this.offsetX = options.offsetX || 0
321
+ this.offsetY = options.offsetY || 0
422
322
  }
423
323
 
424
324
  preventDefault () {
425
- this.defaultPrevented = true
325
+ if (this.cancelable) {
326
+ this.defaultPrevented = true
327
+ }
426
328
  }
427
329
  }
428
330
 
429
- global.Event = class Event {
430
- constructor (type) {
431
- this.type = type
432
- this.defaultPrevented = false
331
+ // CustomEvent constructor
332
+ global.CustomEvent = class CustomEvent extends global.Event {
333
+ constructor (type, options = {}) {
334
+ super(type, options)
335
+ this.detail = options.detail || {}
433
336
  }
337
+ }
434
338
 
435
- preventDefault () {
436
- this.defaultPrevented = true
339
+ // AbortController
340
+ global.AbortController = class AbortController {
341
+ constructor () {
342
+ this.signal = { aborted: false }
343
+ }
344
+
345
+ abort () {
346
+ this.signal.aborted = true
437
347
  }
438
348
  }
439
349
 
440
- // Mock console methods to prevent test output pollution
350
+ // Set up Element constructor
351
+ global.Element = MockElement
352
+
353
+ // Console mocking (preventing test output pollution)
441
354
  const originalConsole = { ...console }
442
355
  global.console = {
443
356
  ...console,