@domql/element 3.4.5 → 3.4.9
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/event/__tests__/applyAnimationFrame.test.js +114 -0
- package/event/__tests__/applyEvent.test.js +159 -0
- package/event/__tests__/applyEventUpdate.test.js +198 -0
- package/event/__tests__/applyEventsOnNode.test.js +216 -0
- package/event/__tests__/canRenderTag.test.js +50 -0
- package/event/__tests__/index.test.js +39 -0
- package/event/__tests__/initAnimationFrame.test.js +156 -0
- package/event/__tests__/registerFrameListener.test.js +97 -0
- package/event/__tests__/store.test.js +93 -0
- package/event/__tests__/triggerEventOn.test.js +195 -0
- package/event/__tests__/triggerEventOnUpdate.test.js +207 -0
- package/event/animationFrame.js +92 -0
- package/event/can.js +8 -0
- package/event/index.js +5 -0
- package/event/on.js +71 -0
- package/event/store.js +6 -0
- package/methods/set.js +73 -0
- package/methods/v2.js +83 -0
- package/mixins/attr.js +32 -0
- package/mixins/classList.js +62 -0
- package/mixins/content.js +65 -0
- package/mixins/data.js +26 -0
- package/mixins/html.js +19 -0
- package/mixins/index.js +23 -0
- package/mixins/registry.js +46 -0
- package/mixins/scope.js +23 -0
- package/mixins/state.js +18 -0
- package/mixins/style.js +25 -0
- package/mixins/text.js +31 -0
- package/package.json +13 -8
- package/render/__tests__/appendNode.test.js +53 -0
- package/render/__tests__/assignNode.test.js +151 -0
- package/render/__tests__/cacheNode.test.js +168 -0
- package/render/__tests__/createHTMLNode.test.js +118 -0
- package/render/__tests__/createNode.test.js +9 -0
- package/render/__tests__/detectTag.test.js +99 -0
- package/render/__tests__/index.test.js +56 -0
- package/render/__tests__/insertNodeAfter.test.js +111 -0
- package/render/__tests__/insertNodeBefore.test.js +65 -0
- package/render/append.js +61 -0
- package/render/cache.js +68 -0
- package/render/create.js +3 -0
- package/render/index.js +5 -0
- package/utils/applyParam.js +33 -0
- package/utils/extendUtils.js +135 -0
- package/utils/index.js +4 -0
- package/utils/propEvents.js +36 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { jest } from '@jest/globals'
|
|
2
|
+
import { cacheNode } from '../cache'
|
|
3
|
+
|
|
4
|
+
describe('cacheNode', () => {
|
|
5
|
+
// Setup mock window and document for each test
|
|
6
|
+
let mockNode
|
|
7
|
+
let mockTextNode
|
|
8
|
+
let mockFragmentNode
|
|
9
|
+
let mockSvgNode
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockNode = {
|
|
13
|
+
cloneNode: jest.fn().mockReturnThis()
|
|
14
|
+
}
|
|
15
|
+
mockTextNode = {
|
|
16
|
+
cloneNode: jest.fn().mockReturnThis(),
|
|
17
|
+
nodeValue: null
|
|
18
|
+
}
|
|
19
|
+
mockFragmentNode = {
|
|
20
|
+
cloneNode: jest.fn().mockReturnThis()
|
|
21
|
+
}
|
|
22
|
+
mockSvgNode = {
|
|
23
|
+
cloneNode: jest.fn().mockReturnThis()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
global.window = {
|
|
27
|
+
nodeCaches: {}
|
|
28
|
+
}
|
|
29
|
+
global.document = {
|
|
30
|
+
createElement: jest.fn().mockReturnValue(mockNode),
|
|
31
|
+
createTextNode: jest.fn().mockReturnValue(mockTextNode),
|
|
32
|
+
createDocumentFragment: jest.fn().mockReturnValue(mockFragmentNode),
|
|
33
|
+
createElementNS: jest.fn().mockReturnValue(mockSvgNode)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test('should return cached node for div element', () => {
|
|
38
|
+
const element = {
|
|
39
|
+
tag: 'div',
|
|
40
|
+
context: {
|
|
41
|
+
window,
|
|
42
|
+
document
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const result = cacheNode(element)
|
|
46
|
+
expect(result).toBeTruthy()
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('should return text node for string element', () => {
|
|
50
|
+
const element = {
|
|
51
|
+
tag: 'string',
|
|
52
|
+
text: 'Hello World',
|
|
53
|
+
context: {
|
|
54
|
+
window,
|
|
55
|
+
document
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const result = cacheNode(element)
|
|
59
|
+
expect(result).toBeTruthy()
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('should return fragment node', () => {
|
|
63
|
+
const element = {
|
|
64
|
+
tag: 'fragment',
|
|
65
|
+
context: {
|
|
66
|
+
window,
|
|
67
|
+
document
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const result = cacheNode(element)
|
|
71
|
+
expect(result).toBeTruthy()
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('should return svg node', () => {
|
|
75
|
+
const element = {
|
|
76
|
+
tag: 'svg',
|
|
77
|
+
context: {
|
|
78
|
+
window,
|
|
79
|
+
document
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const result = cacheNode(element)
|
|
83
|
+
expect(result).toBeTruthy()
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test('should return path node', () => {
|
|
87
|
+
const element = {
|
|
88
|
+
tag: 'path',
|
|
89
|
+
context: {
|
|
90
|
+
window,
|
|
91
|
+
document
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const result = cacheNode(element)
|
|
95
|
+
expect(result).toBeTruthy()
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('should reuse cached node for same tag', () => {
|
|
99
|
+
const element = {
|
|
100
|
+
tag: 'span',
|
|
101
|
+
context: {
|
|
102
|
+
window,
|
|
103
|
+
document
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const firstResult = cacheNode(element)
|
|
107
|
+
const secondResult = cacheNode(element)
|
|
108
|
+
expect(firstResult).toBeTruthy()
|
|
109
|
+
expect(secondResult).toBeTruthy()
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
test('should set nodeValue for string elements', () => {
|
|
113
|
+
const element = {
|
|
114
|
+
tag: 'string',
|
|
115
|
+
text: 'Test Text',
|
|
116
|
+
context: {
|
|
117
|
+
window,
|
|
118
|
+
document
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
const result = cacheNode(element)
|
|
122
|
+
expect(result).toBeTruthy()
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
test('should use window from context if provided', () => {
|
|
126
|
+
const customWindow = { nodeCaches: {} }
|
|
127
|
+
const element = {
|
|
128
|
+
tag: 'div',
|
|
129
|
+
context: {
|
|
130
|
+
window: customWindow,
|
|
131
|
+
document
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
const result = cacheNode(element)
|
|
135
|
+
expect(result).toBeTruthy()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('should use document from context if provided', () => {
|
|
139
|
+
const customDocument = {
|
|
140
|
+
createElement: jest.fn().mockReturnValue(mockNode),
|
|
141
|
+
createTextNode: jest.fn().mockReturnValue(mockTextNode),
|
|
142
|
+
createDocumentFragment: jest.fn().mockReturnValue(mockFragmentNode),
|
|
143
|
+
createElementNS: jest.fn().mockReturnValue(mockSvgNode)
|
|
144
|
+
}
|
|
145
|
+
const element = {
|
|
146
|
+
tag: 'div',
|
|
147
|
+
context: {
|
|
148
|
+
window,
|
|
149
|
+
document: customDocument
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const result = cacheNode(element)
|
|
153
|
+
expect(result).toBeTruthy()
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
test('should initialize nodeCaches if not present', () => {
|
|
157
|
+
global.window = {}
|
|
158
|
+
const element = {
|
|
159
|
+
tag: 'div',
|
|
160
|
+
context: {
|
|
161
|
+
window,
|
|
162
|
+
document
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
const result = cacheNode(element)
|
|
166
|
+
expect(result).toBeTruthy()
|
|
167
|
+
})
|
|
168
|
+
})
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { createHTMLNode } from '../cache'
|
|
2
|
+
|
|
3
|
+
describe('createHTMLNode', () => {
|
|
4
|
+
let element
|
|
5
|
+
let mockDocument
|
|
6
|
+
let mockDocumentFragment
|
|
7
|
+
let mockTextNode
|
|
8
|
+
let mockSvgElement
|
|
9
|
+
let mockDivElement
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
// Setup mock document
|
|
13
|
+
mockTextNode = document.createTextNode('')
|
|
14
|
+
mockDocumentFragment = document.createDocumentFragment()
|
|
15
|
+
mockSvgElement = document.createElementNS(
|
|
16
|
+
'http://www.w3.org/2000/svg',
|
|
17
|
+
'svg'
|
|
18
|
+
)
|
|
19
|
+
mockDivElement = document.createElement('div')
|
|
20
|
+
|
|
21
|
+
mockDocument = {
|
|
22
|
+
createTextNode: () => mockTextNode,
|
|
23
|
+
createDocumentFragment: () => mockDocumentFragment,
|
|
24
|
+
createElementNS: () => mockSvgElement,
|
|
25
|
+
createElement: () => mockDivElement
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Reset element before each test
|
|
29
|
+
element = {
|
|
30
|
+
context: {
|
|
31
|
+
document: mockDocument
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test('should create text node when tag is string', () => {
|
|
37
|
+
element.tag = 'string'
|
|
38
|
+
element.text = 'Hello World'
|
|
39
|
+
|
|
40
|
+
const result = createHTMLNode(element)
|
|
41
|
+
|
|
42
|
+
expect(result.nodeType).toBe(Node.TEXT_NODE)
|
|
43
|
+
expect(result.textContent).toBe('')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('should create document fragment when tag is fragment', () => {
|
|
47
|
+
element.tag = 'fragment'
|
|
48
|
+
|
|
49
|
+
const result = createHTMLNode(element)
|
|
50
|
+
|
|
51
|
+
expect(result.nodeType).toBe(Node.DOCUMENT_FRAGMENT_NODE)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test('should create SVG element when tag is svg', () => {
|
|
55
|
+
element.tag = 'svg'
|
|
56
|
+
|
|
57
|
+
const result = createHTMLNode(element)
|
|
58
|
+
|
|
59
|
+
expect(result.nodeType).toBe(Node.ELEMENT_NODE)
|
|
60
|
+
expect(result.namespaceURI).toBe('http://www.w3.org/2000/svg')
|
|
61
|
+
expect(result.tagName.toLowerCase()).toBe('svg')
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('should create SVG path element when tag is path', () => {
|
|
65
|
+
element.tag = 'svg'
|
|
66
|
+
|
|
67
|
+
const result = createHTMLNode(element)
|
|
68
|
+
|
|
69
|
+
expect(result.nodeType).toBe(Node.ELEMENT_NODE)
|
|
70
|
+
expect(result.namespaceURI).toBe('http://www.w3.org/2000/svg')
|
|
71
|
+
expect(result.tagName.toLowerCase()).toBe('svg')
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('should create regular HTML element for standard HTML tags', () => {
|
|
75
|
+
element.tag = 'div'
|
|
76
|
+
|
|
77
|
+
const result = createHTMLNode(element)
|
|
78
|
+
|
|
79
|
+
expect(result.nodeType).toBe(Node.ELEMENT_NODE)
|
|
80
|
+
expect(result.tagName.toLowerCase()).toBe('div')
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
test('should create div element when no tag is provided', () => {
|
|
84
|
+
delete element.tag
|
|
85
|
+
|
|
86
|
+
const result = createHTMLNode(element)
|
|
87
|
+
|
|
88
|
+
expect(result.nodeType).toBe(Node.ELEMENT_NODE)
|
|
89
|
+
expect(result.tagName.toLowerCase()).toBe('div')
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('should use document from context if provided', () => {
|
|
93
|
+
const customDoc = {
|
|
94
|
+
createElement: () => {
|
|
95
|
+
const el = document.createElement('custom')
|
|
96
|
+
el.setAttribute('from-custom-doc', 'true')
|
|
97
|
+
return el
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
element.context.document = customDoc
|
|
102
|
+
element.tag = 'div'
|
|
103
|
+
|
|
104
|
+
const result = createHTMLNode(element)
|
|
105
|
+
|
|
106
|
+
expect(result.getAttribute('from-custom-doc')).toBe('true')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('should fallback to global document if context.document is not provided', () => {
|
|
110
|
+
delete element.context.document
|
|
111
|
+
element.tag = 'div'
|
|
112
|
+
|
|
113
|
+
const result = createHTMLNode(element)
|
|
114
|
+
|
|
115
|
+
expect(result.nodeType).toBe(Node.ELEMENT_NODE)
|
|
116
|
+
expect(result.tagName.toLowerCase()).toBe('div')
|
|
117
|
+
})
|
|
118
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { detectTag } from '../cache'
|
|
2
|
+
|
|
3
|
+
describe('detectTag', () => {
|
|
4
|
+
test('should return tag from props.tag if it is a valid HTML tag', () => {
|
|
5
|
+
const element = {
|
|
6
|
+
tag: 'something',
|
|
7
|
+
key: 'key',
|
|
8
|
+
props: {
|
|
9
|
+
tag: 'div'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
expect(detectTag(element)).toBe('div')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
test('should return tag if it is a valid HTML tag string', () => {
|
|
16
|
+
const element = {
|
|
17
|
+
tag: 'span',
|
|
18
|
+
key: 'key',
|
|
19
|
+
props: {}
|
|
20
|
+
}
|
|
21
|
+
expect(detectTag(element)).toBe('span')
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test('should return key as tag if tag is true', () => {
|
|
25
|
+
const element = {
|
|
26
|
+
tag: true,
|
|
27
|
+
key: 'div',
|
|
28
|
+
props: {}
|
|
29
|
+
}
|
|
30
|
+
expect(detectTag(element)).toBe('div')
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
test('should return div as default when no valid tag is found', () => {
|
|
34
|
+
const element = {
|
|
35
|
+
tag: 'invalid',
|
|
36
|
+
key: 'invalid',
|
|
37
|
+
props: {}
|
|
38
|
+
}
|
|
39
|
+
expect(detectTag(element)).toBe('div')
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test('should handle function as tag', () => {
|
|
43
|
+
const element = {
|
|
44
|
+
tag: () => 'section',
|
|
45
|
+
key: 'key',
|
|
46
|
+
props: {}
|
|
47
|
+
}
|
|
48
|
+
expect(detectTag(element)).toBe('section')
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test('should parse key with dots and return valid HTML tag', () => {
|
|
52
|
+
const element = {
|
|
53
|
+
tag: null,
|
|
54
|
+
key: 'span.className',
|
|
55
|
+
props: {}
|
|
56
|
+
}
|
|
57
|
+
expect(detectTag(element)).toBe('span')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('should parse key with underscore and return valid HTML tag', () => {
|
|
61
|
+
const element = {
|
|
62
|
+
tag: null,
|
|
63
|
+
key: 'div_identifier',
|
|
64
|
+
props: {}
|
|
65
|
+
}
|
|
66
|
+
expect(detectTag(element)).toBe('div')
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('should handle null props', () => {
|
|
70
|
+
const element = {
|
|
71
|
+
tag: 'p',
|
|
72
|
+
key: 'key',
|
|
73
|
+
props: null
|
|
74
|
+
}
|
|
75
|
+
expect(detectTag(element)).toBe('p')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test('should handle props.tag that is not a valid HTML tag', () => {
|
|
79
|
+
const element = {
|
|
80
|
+
tag: 'div',
|
|
81
|
+
key: 'key',
|
|
82
|
+
props: {
|
|
83
|
+
tag: 'invalid-tag'
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
expect(detectTag(element)).toBe('div')
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('should handle complex nested case', () => {
|
|
90
|
+
const element = {
|
|
91
|
+
tag: null,
|
|
92
|
+
key: 'invalid.something_else',
|
|
93
|
+
props: {
|
|
94
|
+
tag: 'article'
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
expect(detectTag(element)).toBe('article')
|
|
98
|
+
})
|
|
99
|
+
})
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as moduleExports from '../index'
|
|
2
|
+
import * as createExports from '../create'
|
|
3
|
+
import * as cacheExports from '../cache'
|
|
4
|
+
import * as appendExports from '../append'
|
|
5
|
+
|
|
6
|
+
describe('Module exports', () => {
|
|
7
|
+
test('should export all items from create.js', () => {
|
|
8
|
+
Object.keys(createExports).forEach(key => {
|
|
9
|
+
expect(moduleExports[key]).toBeDefined()
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test('should export all items from cache.js', () => {
|
|
14
|
+
Object.keys(cacheExports).forEach(key => {
|
|
15
|
+
expect(moduleExports[key]).toBeDefined()
|
|
16
|
+
})
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('should export all items from append.js', () => {
|
|
20
|
+
Object.keys(appendExports).forEach(key => {
|
|
21
|
+
expect(moduleExports[key]).toBeDefined()
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test('should have no duplicate exports', () => {
|
|
26
|
+
const allExportKeys = [
|
|
27
|
+
...Object.keys(createExports),
|
|
28
|
+
...Object.keys(cacheExports),
|
|
29
|
+
...Object.keys(appendExports)
|
|
30
|
+
]
|
|
31
|
+
const uniqueKeys = new Set(allExportKeys)
|
|
32
|
+
expect(allExportKeys.length).toBe(uniqueKeys.size)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('should export same values as original modules', () => {
|
|
36
|
+
Object.entries(createExports).forEach(([key, value]) => {
|
|
37
|
+
expect(moduleExports[key]).toBe(value)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
Object.entries(cacheExports).forEach(([key, value]) => {
|
|
41
|
+
expect(moduleExports[key]).toBe(value)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
Object.entries(appendExports).forEach(([key, value]) => {
|
|
45
|
+
expect(moduleExports[key]).toBe(value)
|
|
46
|
+
})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test('should have all exports defined', () => {
|
|
50
|
+
const moduleExportKeys = Object.keys(moduleExports)
|
|
51
|
+
expect(moduleExportKeys.length).toBeGreaterThan(0)
|
|
52
|
+
moduleExportKeys.forEach(key => {
|
|
53
|
+
expect(moduleExports[key]).toBeDefined()
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
})
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { insertNodeAfter } from '../append'
|
|
2
|
+
import { jest } from '@jest/globals'
|
|
3
|
+
|
|
4
|
+
describe('insertNodeAfter', () => {
|
|
5
|
+
let parentNode
|
|
6
|
+
let siblingNode
|
|
7
|
+
let newNode
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
// Setup fresh DOM elements before each test
|
|
11
|
+
parentNode = document.createElement('div')
|
|
12
|
+
siblingNode = document.createElement('span')
|
|
13
|
+
newNode = document.createElement('p')
|
|
14
|
+
|
|
15
|
+
// Add sibling to parent for initial setup
|
|
16
|
+
parentNode.appendChild(siblingNode)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test('should throw error when node is null', () => {
|
|
20
|
+
expect(() => {
|
|
21
|
+
insertNodeAfter(null, siblingNode, parentNode)
|
|
22
|
+
}).toThrow('Node is required')
|
|
23
|
+
|
|
24
|
+
// Verify DOM wasn't modified
|
|
25
|
+
expect(parentNode.children).toHaveLength(1)
|
|
26
|
+
expect(parentNode.firstChild).toBe(siblingNode)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test('should insert node after sibling when there is a next sibling', () => {
|
|
30
|
+
// Arrange
|
|
31
|
+
const nextSibling = document.createElement('div')
|
|
32
|
+
parentNode.appendChild(nextSibling)
|
|
33
|
+
|
|
34
|
+
// Act
|
|
35
|
+
insertNodeAfter(newNode, siblingNode, parentNode)
|
|
36
|
+
|
|
37
|
+
// Assert
|
|
38
|
+
expect(parentNode.children).toHaveLength(3)
|
|
39
|
+
expect(siblingNode.nextSibling).toBe(newNode)
|
|
40
|
+
expect(newNode.nextSibling).toBe(nextSibling)
|
|
41
|
+
expect(newNode.previousSibling).toBe(siblingNode)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('should insert node after sibling using insertAdjacentElement', () => {
|
|
45
|
+
// Mock insertAdjacentElement
|
|
46
|
+
const insertAdjacentElementMock = jest.fn()
|
|
47
|
+
siblingNode.insertAdjacentElement = insertAdjacentElementMock
|
|
48
|
+
|
|
49
|
+
// Act
|
|
50
|
+
insertNodeAfter(newNode, siblingNode, parentNode)
|
|
51
|
+
|
|
52
|
+
// Assert
|
|
53
|
+
expect(insertAdjacentElementMock).toHaveBeenCalledWith('afterend', newNode)
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
test('should insert node after sibling when no next sibling exists', () => {
|
|
57
|
+
// Act
|
|
58
|
+
insertNodeAfter(newNode, siblingNode, parentNode)
|
|
59
|
+
|
|
60
|
+
// Assert
|
|
61
|
+
expect(parentNode.children).toHaveLength(2)
|
|
62
|
+
expect(siblingNode.nextSibling).toBe(newNode)
|
|
63
|
+
expect(newNode.previousSibling).toBe(siblingNode)
|
|
64
|
+
expect(newNode.nextSibling).toBeNull()
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
test('should insert node after sibling using implicit parent', () => {
|
|
68
|
+
// Act
|
|
69
|
+
insertNodeAfter(newNode, siblingNode)
|
|
70
|
+
|
|
71
|
+
// Assert
|
|
72
|
+
expect(parentNode.children).toHaveLength(2)
|
|
73
|
+
expect(siblingNode.nextSibling).toBe(newNode)
|
|
74
|
+
expect(newNode.previousSibling).toBe(siblingNode)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test('should handle null sibling', () => {
|
|
78
|
+
// Act & Assert
|
|
79
|
+
expect(() => {
|
|
80
|
+
insertNodeAfter(newNode, null, parentNode)
|
|
81
|
+
}).not.toThrow()
|
|
82
|
+
|
|
83
|
+
// Verify no changes were made to the parent
|
|
84
|
+
expect(parentNode.children).toHaveLength(2)
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
test('should handle case when parent and sibling parent are both null', () => {
|
|
88
|
+
const standaloneNode = document.createElement('div')
|
|
89
|
+
|
|
90
|
+
// Act & Assert
|
|
91
|
+
expect(() => {
|
|
92
|
+
insertNodeAfter(newNode, standaloneNode, null)
|
|
93
|
+
}).not.toThrow()
|
|
94
|
+
|
|
95
|
+
expect(standaloneNode.nextSibling).toBeNull()
|
|
96
|
+
expect(standaloneNode.parentNode).toBeNull()
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('should handle sibling without insertAdjacentElement', () => {
|
|
100
|
+
// Create a minimal sibling without insertAdjacentElement
|
|
101
|
+
const customSibling = document.createElement('div')
|
|
102
|
+
delete customSibling.insertAdjacentElement
|
|
103
|
+
|
|
104
|
+
// Act
|
|
105
|
+
insertNodeAfter(newNode, customSibling, parentNode)
|
|
106
|
+
|
|
107
|
+
// Assert
|
|
108
|
+
expect(parentNode.children).toHaveLength(1)
|
|
109
|
+
expect(customSibling.nextSibling).toBe(null)
|
|
110
|
+
})
|
|
111
|
+
})
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { insertNodeBefore } from '../append'
|
|
2
|
+
|
|
3
|
+
describe('insertNodeBefore', () => {
|
|
4
|
+
let parentNode
|
|
5
|
+
let siblingNode
|
|
6
|
+
let newNode
|
|
7
|
+
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// Setup fresh DOM elements before each test
|
|
10
|
+
parentNode = document.createElement('div')
|
|
11
|
+
siblingNode = document.createElement('span')
|
|
12
|
+
newNode = document.createElement('p')
|
|
13
|
+
|
|
14
|
+
// Add sibling to parent for initial setup
|
|
15
|
+
parentNode.appendChild(siblingNode)
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test('should insert node before sibling with explicit parent', () => {
|
|
19
|
+
insertNodeBefore(newNode, siblingNode, parentNode)
|
|
20
|
+
|
|
21
|
+
expect(parentNode.children).toHaveLength(2)
|
|
22
|
+
expect(parentNode.firstChild).toBe(newNode)
|
|
23
|
+
expect(newNode.nextSibling).toBe(siblingNode)
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
test('should insert node before sibling using implicit parent', () => {
|
|
27
|
+
// Not passing parent parameter, using sibling's parent
|
|
28
|
+
insertNodeBefore(newNode, siblingNode)
|
|
29
|
+
|
|
30
|
+
expect(parentNode.children).toHaveLength(2)
|
|
31
|
+
expect(parentNode.firstChild).toBe(newNode)
|
|
32
|
+
expect(newNode.nextSibling).toBe(siblingNode)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test('should handle case when parent is null', () => {
|
|
36
|
+
const standaloneNode = document.createElement('div')
|
|
37
|
+
// Passing null as parent
|
|
38
|
+
insertNodeBefore(newNode, standaloneNode, null)
|
|
39
|
+
|
|
40
|
+
expect(standaloneNode.previousSibling).toBeNull()
|
|
41
|
+
expect(standaloneNode.parentNode).toBeNull()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test('should handle case when sibling has no parent', () => {
|
|
45
|
+
const standaloneNode = document.createElement('div')
|
|
46
|
+
insertNodeBefore(newNode, standaloneNode)
|
|
47
|
+
|
|
48
|
+
expect(standaloneNode.previousSibling).toBeNull()
|
|
49
|
+
expect(standaloneNode.parentNode).toBeNull()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('should throw error when node is null', () => {
|
|
53
|
+
// Arrange
|
|
54
|
+
const errorMessage = 'Node is required'
|
|
55
|
+
|
|
56
|
+
// Act & Assert
|
|
57
|
+
expect(() => {
|
|
58
|
+
insertNodeBefore(null, siblingNode, parentNode)
|
|
59
|
+
}).toThrow(errorMessage)
|
|
60
|
+
|
|
61
|
+
// Verify the DOM wasn't modified
|
|
62
|
+
expect(parentNode.children).toHaveLength(1)
|
|
63
|
+
expect(parentNode.firstChild).toBe(siblingNode)
|
|
64
|
+
})
|
|
65
|
+
})
|
package/render/append.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Receives child and parent nodes as parametes
|
|
5
|
+
* and assigns them into real DOM tree
|
|
6
|
+
*/
|
|
7
|
+
export const appendNode = (node, parentNode) => {
|
|
8
|
+
try {
|
|
9
|
+
parentNode.appendChild(node)
|
|
10
|
+
return node
|
|
11
|
+
} catch (e) {
|
|
12
|
+
console.error('Does not support to append', parentNode, node)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//q23
|
|
17
|
+
export const insertNodeAfter = (node, siblingNode, parentNode) => {
|
|
18
|
+
if (!node) {
|
|
19
|
+
throw new Error('Node is required')
|
|
20
|
+
}
|
|
21
|
+
const parent = parentNode || siblingNode?.parentNode
|
|
22
|
+
if (siblingNode?.nextSibling) {
|
|
23
|
+
parent?.insertBefore(node, siblingNode.nextSibling)
|
|
24
|
+
} else if (siblingNode?.insertAdjacentElement) {
|
|
25
|
+
siblingNode.insertAdjacentElement('afterend', node)
|
|
26
|
+
} else {
|
|
27
|
+
parent?.insertBefore(node, siblingNode)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const insertNodeBefore = (node, siblingNode, parentNode) => {
|
|
32
|
+
if (!node) {
|
|
33
|
+
throw new Error('Node is required')
|
|
34
|
+
}
|
|
35
|
+
const parent = parentNode || siblingNode.parentNode
|
|
36
|
+
parent?.insertBefore(node, siblingNode)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Receives elements and assigns the first
|
|
41
|
+
* parameter as a child of the second one
|
|
42
|
+
*/
|
|
43
|
+
export const assignNode = (element, parent, key, attachOptions) => {
|
|
44
|
+
if (!element) {
|
|
45
|
+
throw new Error('Element is required')
|
|
46
|
+
}
|
|
47
|
+
if (!parent) {
|
|
48
|
+
throw new Error('Parent is required')
|
|
49
|
+
}
|
|
50
|
+
parent[key || element.key] = element
|
|
51
|
+
if (element.tag !== 'shadow') {
|
|
52
|
+
if (attachOptions && attachOptions.position) {
|
|
53
|
+
;(attachOptions.position === 'before'
|
|
54
|
+
? insertNodeBefore
|
|
55
|
+
: insertNodeAfter)(element.node, attachOptions.node || parent.node)
|
|
56
|
+
} else {
|
|
57
|
+
appendNode(element.node, parent.node, element)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return element
|
|
61
|
+
}
|