Skip to content

Commit a7a0942

Browse files
committed
introduce simplifiedElementCheck option, fixes #144
1 parent 912be2d commit a7a0942

File tree

11 files changed

+116
-25
lines changed

11 files changed

+116
-25
lines changed

browser/diffDOM.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

browser/diffDOM.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/TraceLogger.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ export class TraceLogger {
115115
if (typeof v === "string") {
116116
return v
117117
}
118-
if (checkElementType(v, "HTMLElement")) {
118+
// Use simplified check for HTMLElement since this is outside the main diff process
119+
if (checkElementType(v, true, "HTMLElement")) {
119120
return (v as HTMLElement).outerHTML || "<empty>"
120121
}
121122
if (v instanceof Array) {

src/diffDOM/dom/apply.ts

+10-10
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export function applyDiff(
5151

5252
switch (action) {
5353
case options._const.addAttribute:
54-
if (!node || !checkElementType(node, "Element")) {
54+
if (!node || !checkElementType(node, options.simplifiedElementCheck, "Element")) {
5555
return false
5656
}
5757
node.setAttribute(
@@ -60,28 +60,28 @@ export function applyDiff(
6060
)
6161
break
6262
case options._const.modifyAttribute:
63-
if (!node || !checkElementType(node, "Element")) {
63+
if (!node || !checkElementType(node, options.simplifiedElementCheck, "Element")) {
6464
return false
6565
}
6666
node.setAttribute(
6767
diff[options._const.name] as string,
6868
diff[options._const.newValue] as string,
6969
)
7070
if (
71-
checkElementType(node, "HTMLInputElement") &&
71+
checkElementType(node, options.simplifiedElementCheck, "HTMLInputElement") &&
7272
diff[options._const.name] === "value"
7373
) {
7474
node.value = diff[options._const.newValue] as string
7575
}
7676
break
7777
case options._const.removeAttribute:
78-
if (!node || !checkElementType(node, "Element")) {
78+
if (!node || !checkElementType(node, options.simplifiedElementCheck, "Element")) {
7979
return false
8080
}
8181
node.removeAttribute(diff[options._const.name] as string)
8282
break
8383
case options._const.modifyTextElement:
84-
if (!node || !checkElementType(node, "Text")) {
84+
if (!node || !checkElementType(node, options.simplifiedElementCheck, "Text")) {
8585
return false
8686
}
8787
options.textDiff(
@@ -90,7 +90,7 @@ export function applyDiff(
9090
diff[options._const.oldValue] as string,
9191
diff[options._const.newValue] as string,
9292
)
93-
if (checkElementType(node.parentNode, "HTMLTextAreaElement")) {
93+
if (checkElementType(node.parentNode, options.simplifiedElementCheck, "HTMLTextAreaElement")) {
9494
node.parentNode.value = diff[options._const.newValue] as string
9595
}
9696
break
@@ -101,7 +101,7 @@ export function applyDiff(
101101
node.value = diff[options._const.newValue]
102102
break
103103
case options._const.modifyComment:
104-
if (!node || !checkElementType(node, "Comment")) {
104+
if (!node || !checkElementType(node, options.simplifiedElementCheck, "Comment")) {
105105
return false
106106
}
107107
options.textDiff(
@@ -161,7 +161,7 @@ export function applyDiff(
161161
const parentRoute = route.slice()
162162
const c: number = parentRoute.splice(parentRoute.length - 1, 1)[0]
163163
node = getFromRoute(tree, parentRoute)
164-
if (!checkElementType(node, "Element")) {
164+
if (!checkElementType(node, options.simplifiedElementCheck, "Element")) {
165165
return false
166166
}
167167
node.insertBefore(
@@ -180,7 +180,7 @@ export function applyDiff(
180180
}
181181
const parentNode = node.parentNode
182182
parentNode.removeChild(node)
183-
if (checkElementType(parentNode, "HTMLTextAreaElement")) {
183+
if (checkElementType(parentNode, options.simplifiedElementCheck, "HTMLTextAreaElement")) {
184184
parentNode.value = ""
185185
}
186186
break
@@ -196,7 +196,7 @@ export function applyDiff(
196196
return false
197197
}
198198
node.insertBefore(newNode, node.childNodes[c] || null)
199-
if (checkElementType(node.parentNode, "HTMLTextAreaElement")) {
199+
if (checkElementType(node.parentNode, options.simplifiedElementCheck, "HTMLTextAreaElement")) {
200200
node.parentNode.value = diff[options._const.value] as string
201201
}
202202
break

src/diffDOM/dom/fromVirtual.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export function objToNode(
4646
objNode.value &&
4747
checkElementType(
4848
node,
49+
options.simplifiedElementCheck,
4950
"HTMLButtonElement",
5051
"HTMLDataElement",
5152
"HTMLInputElement",
@@ -68,12 +69,12 @@ export function objToNode(
6869
| HTMLParamElement
6970
).value = objNode.value
7071
}
71-
if (objNode.checked && checkElementType(node, "HTMLInputElement")) {
72+
if (objNode.checked && checkElementType(node, options.simplifiedElementCheck, "HTMLInputElement")) {
7273
;(node as HTMLInputElement).checked = objNode.checked
7374
}
7475
if (
7576
objNode.selected &&
76-
checkElementType(node, "HTMLOptionElement")
77+
checkElementType(node, options.simplifiedElementCheck, "HTMLOptionElement")
7778
) {
7879
;(node as HTMLOptionElement).selected = objNode.selected
7980
}

src/diffDOM/helpers.ts

+40-1
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,49 @@ export class Diff {
2424
}
2525
}
2626

27-
export function checkElementType(element, ...elementTypeNames: string[]) {
27+
/**
28+
* Checks if an element is of a certain type using direct property checking or DOM instanceof
29+
*
30+
* @param element The element to check
31+
* @param elementTypeNames The element type names to check against
32+
* @param simplifiedCheck If true, uses simplified checking based on nodeName/nodeType
33+
* @returns boolean indicating if the element matches any of the specified types
34+
*/
35+
export function checkElementType(element, simplifiedCheck = false, ...elementTypeNames: string[]) {
2836
if (typeof element === "undefined" || element === null) {
2937
return false
3038
}
39+
40+
// Simplified check for primitive virtual DOMs without ownerDocument
41+
if (simplifiedCheck) {
42+
return elementTypeNames.some((elementTypeName) => {
43+
// Special case for basic element types
44+
if (elementTypeName === "Element") {
45+
return element.nodeType === 1 ||
46+
(typeof element.nodeName === "string" &&
47+
element.nodeName !== "#text" &&
48+
element.nodeName !== "#comment");
49+
}
50+
if (elementTypeName === "Text") {
51+
return element.nodeType === 3 ||
52+
element.nodeName === "#text";
53+
}
54+
if (elementTypeName === "Comment") {
55+
return element.nodeType === 8 ||
56+
element.nodeName === "#comment";
57+
}
58+
59+
// For HTML element types, check nodeName
60+
if (elementTypeName.startsWith("HTML") && elementTypeName.endsWith("Element")) {
61+
const tagName = elementTypeName.slice(4, -7).toLowerCase();
62+
return (element.nodeName && element.nodeName.toLowerCase() === tagName);
63+
}
64+
65+
return false;
66+
});
67+
}
68+
69+
// DOM-based check
3170
return elementTypeNames.some(
3271
(elementTypeName) =>
3372
// We need to check if the specified type is defined

src/diffDOM/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const DEFAULT_OPTIONS = {
1818
maxDepth: false, // False or a numeral. If set to a numeral, limits the level of depth that the the diff mechanism looks for differences. If false, goes through the entire tree.
1919
maxChildCount: 50, // False or a numeral. If set to a numeral, only does a simplified form of diffing of contents so that the number of diffs cannot be higher than the number of child nodes.
2020
valueDiffing: true, // Whether to take into consideration the values of forms that differ from auto assigned values (when a user fills out a form).
21+
simplifiedElementCheck: true, // Whether to use simplified element type checking for primitive virtual DOMs without ownerDocument
2122
// syntax: textDiff: function (node, currentValue, expectedValue, newValue)
2223
textDiff(
2324
node: textNodeType,

src/diffDOM/types.ts

+1
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ interface DiffDOMOptions {
106106
maxChildCount: number // False or a numeral. If set to a numeral, only does a simplified form of diffing of contents so that the number of diffs cannot be higher than the number of child nodes.
107107
valueDiffing: boolean // Whether to take into consideration the values of forms that differ from auto assigned values (when a user fills out a form).
108108
caseSensitive: boolean // Whether to preserve the case of an input string. Important when including CML (XHTML, SVG, etc.)
109+
simplifiedElementCheck: boolean // Whether to use simplified element type checking for primitive virtual DOMs without ownerDocument
109110
// syntax: textDiff: function (node, currentValue, expectedValue, newValue)
110111
textDiff: (
111112
node: textNodeType | Text | Comment,

src/diffDOM/virtual/diff.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,15 @@ export class DiffFinder {
4040
this.options = options
4141
this.t1 = (
4242
typeof Element !== "undefined" &&
43-
checkElementType(t1Node, "Element")
43+
checkElementType(t1Node, this.options.simplifiedElementCheck, "Element")
4444
? nodeToObj(t1Node as Element, this.options)
4545
: typeof t1Node === "string"
4646
? stringToObj(t1Node, this.options)
4747
: JSON.parse(JSON.stringify(t1Node))
4848
) as elementDiffNodeType
4949
this.t2 = (
5050
typeof Element !== "undefined" &&
51-
checkElementType(t2Node, "Element")
51+
checkElementType(t2Node, this.options.simplifiedElementCheck, "Element")
5252
? nodeToObj(t2Node as Element, this.options)
5353
: typeof t2Node === "string"
5454
? stringToObj(t2Node, this.options)
@@ -59,14 +59,14 @@ export class DiffFinder {
5959
if (this.debug) {
6060
this.t1Orig =
6161
typeof Element !== "undefined" &&
62-
checkElementType(t1Node, "Element")
62+
checkElementType(t1Node, this.options.simplifiedElementCheck, "Element")
6363
? nodeToObj(t1Node as Element, this.options)
6464
: typeof t1Node === "string"
6565
? stringToObj(t1Node, this.options)
6666
: JSON.parse(JSON.stringify(t1Node))
6767
this.t2Orig =
6868
typeof Element !== "undefined" &&
69-
checkElementType(t2Node, "Element")
69+
checkElementType(t2Node, this.options.simplifiedElementCheck, "Element")
7070
? nodeToObj(t2Node as Element, this.options)
7171
: typeof t2Node === "string"
7272
? stringToObj(t2Node, this.options)

src/diffDOM/virtual/fromDOM.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ import { checkElementType } from "../helpers"
33

44
export function nodeToObj(
55
aNode: Element,
6-
options: DiffDOMOptionsPartial = { valueDiffing: true },
6+
options: DiffDOMOptionsPartial = { valueDiffing: true, simplifiedElementCheck: true },
77
) {
88
const objNode: elementNodeType | textNodeType = {
99
nodeName: aNode.nodeName,
1010
}
11-
if (checkElementType(aNode, "Text", "Comment")) {
11+
if (checkElementType(aNode, options.simplifiedElementCheck, "Text", "Comment")) {
1212
;(objNode as unknown as textNodeType).data = (
1313
aNode as unknown as Text | Comment
1414
).data
@@ -29,11 +29,11 @@ export function nodeToObj(
2929
)
3030
}
3131
if (options.valueDiffing) {
32-
if (checkElementType(aNode, "HTMLTextAreaElement")) {
32+
if (checkElementType(aNode, options.simplifiedElementCheck, "HTMLTextAreaElement")) {
3333
objNode.value = (aNode as HTMLTextAreaElement).value
3434
}
3535
if (
36-
checkElementType(aNode, "HTMLInputElement") &&
36+
checkElementType(aNode, options.simplifiedElementCheck, "HTMLInputElement") &&
3737
["radio", "checkbox"].includes(
3838
(aNode as HTMLInputElement).type.toLowerCase(),
3939
) &&
@@ -43,6 +43,7 @@ export function nodeToObj(
4343
} else if (
4444
checkElementType(
4545
aNode,
46+
options.simplifiedElementCheck,
4647
"HTMLButtonElement",
4748
"HTMLDataElement",
4849
"HTMLInputElement",
@@ -65,7 +66,7 @@ export function nodeToObj(
6566
| HTMLParamElement
6667
).value
6768
}
68-
if (checkElementType(aNode, "HTMLOptionElement")) {
69+
if (checkElementType(aNode, options.simplifiedElementCheck, "HTMLOptionElement")) {
6970
objNode.selected = (aNode as HTMLOptionElement).selected
7071
}
7172
}

tests/toObj.test.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* @jest-environment jsdom
3+
*/
4+
5+
import { nodeToObj } from "../dist/index"
6+
7+
const h1TextContent = "Section"
8+
9+
const htmlString = `
10+
<!DOCTYPE html>
11+
<html lang="en">
12+
<head>
13+
<title>Node To Obj Test</title>
14+
</head>
15+
<body>
16+
<div>
17+
<main>
18+
<h1>${h1TextContent}</h1>
19+
<h3>more stuff</h3>
20+
</main>
21+
</div>
22+
</body>
23+
</html>
24+
`
25+
26+
describe("parsing", () => {
27+
it("Can parse obj correctly", () => {
28+
const htmlDocument = new DOMParser().parseFromString(
29+
htmlString,
30+
"text/html",
31+
)
32+
33+
const h1TextContentInParsedDocument =
34+
htmlDocument.body.childNodes[1].childNodes[1].childNodes[1]
35+
.textContent
36+
37+
// Sanity check: Ensure the text we want to check actually exists
38+
expect(h1TextContentInParsedDocument).toEqual(h1TextContent)
39+
40+
const documentObject = nodeToObj(htmlDocument.body, {simplifiedElementCheck: true})
41+
const h1Object =
42+
documentObject.childNodes[1].childNodes[1].childNodes[1]
43+
.childNodes[0]
44+
45+
expect(h1Object.data).toEqual(h1TextContent)
46+
})
47+
})

0 commit comments

Comments
 (0)