Repository: capricorn86/happy-dom
Pull request #337 (2022-01-21 00:01:10)

#329@minor: Adds support for Window.CharacterData.


Patch:
diff --git a/package-lock.json b/package-lock.json
index bfa59794..b8c6076c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -924,29 +924,29 @@
 			}
 		},
 		"node_modules/@jest/environment": {
-			"version": "27.4.4",
-			"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.4.tgz",
-			"integrity": "sha512-q+niMx7cJgt/t/b6dzLOh4W8Ef/8VyKG7hxASK39jakijJzbFBGpptx3RXz13FFV7OishQ9lTbv+dQ5K3EhfDQ==",
+			"version": "27.4.6",
+			"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz",
+			"integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==",
 			"dependencies": {
-				"@jest/fake-timers": "^27.4.2",
+				"@jest/fake-timers": "^27.4.6",
 				"@jest/types": "^27.4.2",
 				"@types/node": "*",
-				"jest-mock": "^27.4.2"
+				"jest-mock": "^27.4.6"
 			},
 			"engines": {
 				"node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
 			}
 		},
 		"node_modules/@jest/fake-timers": {
-			"version": "27.4.2",
-			"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.2.tgz",
-			"integrity": "sha512-f/Xpzn5YQk5adtqBgvw1V6bF8Nx3hY0OIRRpCvWcfPl0EAjdqWPdhH3t/3XpiWZqtjIEHDyMKP9ajpva1l4Zmg==",
+			"version": "27.4.6",
+			"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz",
+			"integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==",
 			"dependencies": {
 				"@jest/types": "^27.4.2",
 				"@sinonjs/fake-timers": "^8.0.1",
 				"@types/node": "*",
-				"jest-message-util": "^27.4.2",
-				"jest-mock": "^27.4.2",
+				"jest-message-util": "^27.4.6",
+				"jest-mock": "^27.4.6",
 				"jest-util": "^27.4.2"
 			},
 			"engines": {
@@ -965,9 +965,9 @@
 			}
 		},
 		"node_modules/@jest/fake-timers/node_modules/jest-message-util": {
-			"version": "27.4.2",
-			"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.2.tgz",
-			"integrity": "sha512-OMRqRNd9E0DkBLZpFtZkAGYOXl6ZpoMtQJWTAREJKDOFa0M6ptB7L67tp+cszMBkvSgKOhNtQp2Vbcz3ZZKo/w==",
+			"version": "27.4.6",
+			"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz",
+			"integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==",
 			"dependencies": {
 				"@babel/code-frame": "^7.12.13",
 				"@jest/types": "^27.4.2",
@@ -975,7 +975,7 @@
 				"chalk": "^4.0.0",
 				"graceful-fs": "^4.2.4",
 				"micromatch": "^4.0.4",
-				"pretty-format": "^27.4.2",
+				"pretty-format": "^27.4.6",
 				"slash": "^3.0.0",
 				"stack-utils": "^2.0.3"
 			},
@@ -984,11 +984,10 @@
 			}
 		},
 		"node_modules/@jest/fake-timers/node_modules/pretty-format": {
-			"version": "27.4.2",
-			"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.2.tgz",
-			"integrity": "sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw==",
+			"version": "27.4.6",
+			"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz",
+			"integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==",
 			"dependencies": {
-				"@jest/types": "^27.4.2",
 				"ansi-regex": "^5.0.1",
 				"ansi-styles": "^5.0.0",
 				"react-is": "^17.0.1"
@@ -2674,9 +2673,9 @@
 			}
 		},
 		"node_modules/@lit/reactive-element": {
-			"version": "1.0.2",
-			"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.0.2.tgz",
-			"integrity": "sha512-oz3d3MKjQ2tXynQgyaQaMpGTDNyNDeBdo6dXf1AbjTwhA1IRINHmA7kSaVYv9ttKweNkEoNqp9DqteDdgWzPEg=="
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.2.0.tgz",
+			"integrity": "sha512-7i/Fz8enAQ2AN5DyJ2i2AFERufjP6x1NjuHoNgDyJkjjHxEoo8kVyyHxu1A9YyeShlksjt5FvpvENBDuivQHLA=="
 		},
 		"node_modules/@mrmlnc/readdir-enhanced": {
 			"version": "2.2.1",
@@ -4639,9 +4638,15 @@
 			"dev": true
 		},
 		"node_modules/chokidar": {
-			"version": "3.5.2",
-			"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
-			"integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
+			"version": "3.5.3",
+			"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+			"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+			"funding": [
+				{
+					"type": "individual",
+					"url": "https://paulmillr.com/funding/"
+				}
+			],
 			"optional": true,
 			"dependencies": {
 				"anymatch": "~3.1.2",
@@ -10413,9 +10418,9 @@
 			}
 		},
 		"node_modules/jest-mock": {
-			"version": "27.4.2",
-			"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.2.tgz",
-			"integrity": "sha512-PDDPuyhoukk20JrQKeofK12hqtSka7mWH0QQuxSNgrdiPsrnYYLS6wbzu/HDlxZRzji5ylLRULeuI/vmZZDrYA==",
+			"version": "27.4.6",
+			"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz",
+			"integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==",
 			"dependencies": {
 				"@jest/types": "^27.4.2",
 				"@types/node": "*"
@@ -11447,28 +11452,28 @@
 			"dev": true
 		},
 		"node_modules/lit": {
-			"version": "2.0.2",
-			"resolved": "https://registry.npmjs.org/lit/-/lit-2.0.2.tgz",
-			"integrity": "sha512-hKA/1YaSB+P+DvKWuR2q1Xzy/iayhNrJ3aveD0OQ9CKn6wUjsdnF/7LavDOJsKP/K5jzW/kXsuduPgRvTFrFJw==",
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/lit/-/lit-2.1.1.tgz",
+			"integrity": "sha512-yqDqf36IhXwOxIQSFqCMgpfvDCRdxLCLZl7m/+tO5C9W/OBHUj17qZpiMBT35v97QMVKcKEi1KZ3hZRyTwBNsQ==",
 			"dependencies": {
-				"@lit/reactive-element": "^1.0.0",
-				"lit-element": "^3.0.0",
-				"lit-html": "^2.0.0"
+				"@lit/reactive-element": "^1.1.0",
+				"lit-element": "^3.1.0",
+				"lit-html": "^2.1.0"
 			}
 		},
 		"node_modules/lit-element": {
-			"version": "3.0.2",
-			"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.0.2.tgz",
-			"integrity": "sha512-9vTJ47D2DSE4Jwhle7aMzEwO2ZcOPRikqfT3CVG7Qol2c9/I4KZwinZNW5Xv8hNm+G/enSSfIwqQhIXi6ioAUg==",
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.1.1.tgz",
+			"integrity": "sha512-14ClnMAU8EXnzC+M2/KDd3SFmNUn1QUw1+GxWkEMwGV3iaH8ObunMlO5svzvaWlkSV0WlxJCi40NGnDVJ2XZKQ==",
 			"dependencies": {
-				"@lit/reactive-element": "^1.0.0",
-				"lit-html": "^2.0.0"
+				"@lit/reactive-element": "^1.1.0",
+				"lit-html": "^2.1.0"
 			}
 		},
 		"node_modules/lit-html": {
-			"version": "2.0.2",
-			"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.0.2.tgz",
-			"integrity": "sha512-dON7Zg8btb14/fWohQLQBdSgkoiQA4mIUy87evmyJHtxRq7zS6LlC32bT5EPWiof5PUQaDpF45v2OlrxHA5Clg==",
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.1.1.tgz",
+			"integrity": "sha512-E4BImK6lopAYanJpvcGaAG8kQFF1ccIulPu2BRNZI7acFB6i4ujjjsnaPVFT1j/4lD9r8GKih0Y8d7/LH8SeyQ==",
 			"dependencies": {
 				"@types/trusted-types": "^2.0.2"
 			}
@@ -13822,13 +13827,13 @@
 			}
 		},
 		"node_modules/prop-types": {
-			"version": "15.7.2",
-			"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
-			"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+			"version": "15.8.1",
+			"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+			"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
 			"dependencies": {
 				"loose-envify": "^1.4.0",
 				"object-assign": "^4.1.1",
-				"react-is": "^16.8.1"
+				"react-is": "^16.13.1"
 			}
 		},
 		"node_modules/prop-types/node_modules/react-is": {
@@ -18821,26 +18826,26 @@
 			}
 		},
 		"@jest/environment": {
-			"version": "27.4.4",
-			"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.4.tgz",
-			"integrity": "sha512-q+niMx7cJgt/t/b6dzLOh4W8Ef/8VyKG7hxASK39jakijJzbFBGpptx3RXz13FFV7OishQ9lTbv+dQ5K3EhfDQ==",
+			"version": "27.4.6",
+			"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.4.6.tgz",
+			"integrity": "sha512-E6t+RXPfATEEGVidr84WngLNWZ8ffCPky8RqqRK6u1Bn0LK92INe0MDttyPl/JOzaq92BmDzOeuqk09TvM22Sg==",
 			"requires": {
-				"@jest/fake-timers": "^27.4.2",
+				"@jest/fake-timers": "^27.4.6",
 				"@jest/types": "^27.4.2",
 				"@types/node": "*",
-				"jest-mock": "^27.4.2"
+				"jest-mock": "^27.4.6"
 			}
 		},
 		"@jest/fake-timers": {
-			"version": "27.4.2",
-			"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.2.tgz",
-			"integrity": "sha512-f/Xpzn5YQk5adtqBgvw1V6bF8Nx3hY0OIRRpCvWcfPl0EAjdqWPdhH3t/3XpiWZqtjIEHDyMKP9ajpva1l4Zmg==",
+			"version": "27.4.6",
+			"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.4.6.tgz",
+			"integrity": "sha512-mfaethuYF8scV8ntPpiVGIHQgS0XIALbpY2jt2l7wb/bvq4Q5pDLk4EP4D7SAvYT1QrPOPVZAtbdGAOOyIgs7A==",
 			"requires": {
 				"@jest/types": "^27.4.2",
 				"@sinonjs/fake-timers": "^8.0.1",
 				"@types/node": "*",
-				"jest-message-util": "^27.4.2",
-				"jest-mock": "^27.4.2",
+				"jest-message-util": "^27.4.6",
+				"jest-mock": "^27.4.6",
 				"jest-util": "^27.4.2"
 			},
 			"dependencies": {
@@ -18850,9 +18855,9 @@
 					"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="
 				},
 				"jest-message-util": {
-					"version": "27.4.2",
-					"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.2.tgz",
-					"integrity": "sha512-OMRqRNd9E0DkBLZpFtZkAGYOXl6ZpoMtQJWTAREJKDOFa0M6ptB7L67tp+cszMBkvSgKOhNtQp2Vbcz3ZZKo/w==",
+					"version": "27.4.6",
+					"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.6.tgz",
+					"integrity": "sha512-0p5szriFU0U74czRSFjH6RyS7UYIAkn/ntwMuOwTGWrQIOh5NzXXrq72LOqIkJKKvFbPq+byZKuBz78fjBERBA==",
 					"requires": {
 						"@babel/code-frame": "^7.12.13",
 						"@jest/types": "^27.4.2",
@@ -18860,17 +18865,16 @@
 						"chalk": "^4.0.0",
 						"graceful-fs": "^4.2.4",
 						"micromatch": "^4.0.4",
-						"pretty-format": "^27.4.2",
+						"pretty-format": "^27.4.6",
 						"slash": "^3.0.0",
 						"stack-utils": "^2.0.3"
 					}
 				},
 				"pretty-format": {
-					"version": "27.4.2",
-					"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.2.tgz",
-					"integrity": "sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw==",
+					"version": "27.4.6",
+					"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.6.tgz",
+					"integrity": "sha512-NblstegA1y/RJW2VyML+3LlpFjzx62cUrtBIKIWDXEDkjNeleA7Od7nrzcs/VLQvAeV4CgSYhrN39DRN88Qi/g==",
 					"requires": {
-						"@jest/types": "^27.4.2",
 						"ansi-regex": "^5.0.1",
 						"ansi-styles": "^5.0.0",
 						"react-is": "^17.0.1"
@@ -20223,9 +20227,9 @@
 			}
 		},
 		"@lit/reactive-element": {
-			"version": "1.0.2",
-			"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.0.2.tgz",
-			"integrity": "sha512-oz3d3MKjQ2tXynQgyaQaMpGTDNyNDeBdo6dXf1AbjTwhA1IRINHmA7kSaVYv9ttKweNkEoNqp9DqteDdgWzPEg=="
+			"version": "1.2.0",
+			"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.2.0.tgz",
+			"integrity": "sha512-7i/Fz8enAQ2AN5DyJ2i2AFERufjP6x1NjuHoNgDyJkjjHxEoo8kVyyHxu1A9YyeShlksjt5FvpvENBDuivQHLA=="
 		},
 		"@mrmlnc/readdir-enhanced": {
 			"version": "2.2.1",
@@ -21833,9 +21837,9 @@
 			"dev": true
 		},
 		"chokidar": {
-			"version": "3.5.2",
-			"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.2.tgz",
-			"integrity": "sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==",
+			"version": "3.5.3",
+			"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+			"integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
 			"optional": true,
 			"requires": {
 				"anymatch": "~3.1.2",
@@ -26398,9 +26402,9 @@
 			}
 		},
 		"jest-mock": {
-			"version": "27.4.2",
-			"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.2.tgz",
-			"integrity": "sha512-PDDPuyhoukk20JrQKeofK12hqtSka7mWH0QQuxSNgrdiPsrnYYLS6wbzu/HDlxZRzji5ylLRULeuI/vmZZDrYA==",
+			"version": "27.4.6",
+			"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.4.6.tgz",
+			"integrity": "sha512-kvojdYRkst8iVSZ1EJ+vc1RRD9llueBjKzXzeCytH3dMM7zvPV/ULcfI2nr0v0VUgm3Bjt3hBCQvOeaBz+ZTHw==",
 			"requires": {
 				"@jest/types": "^27.4.2",
 				"@types/node": "*"
@@ -27232,28 +27236,28 @@
 			"dev": true
 		},
 		"lit": {
-			"version": "2.0.2",
-			"resolved": "https://registry.npmjs.org/lit/-/lit-2.0.2.tgz",
-			"integrity": "sha512-hKA/1YaSB+P+DvKWuR2q1Xzy/iayhNrJ3aveD0OQ9CKn6wUjsdnF/7LavDOJsKP/K5jzW/kXsuduPgRvTFrFJw==",
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/lit/-/lit-2.1.1.tgz",
+			"integrity": "sha512-yqDqf36IhXwOxIQSFqCMgpfvDCRdxLCLZl7m/+tO5C9W/OBHUj17qZpiMBT35v97QMVKcKEi1KZ3hZRyTwBNsQ==",
 			"requires": {
-				"@lit/reactive-element": "^1.0.0",
-				"lit-element": "^3.0.0",
-				"lit-html": "^2.0.0"
+				"@lit/reactive-element": "^1.1.0",
+				"lit-element": "^3.1.0",
+				"lit-html": "^2.1.0"
 			}
 		},
 		"lit-element": {
-			"version": "3.0.2",
-			"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.0.2.tgz",
-			"integrity": "sha512-9vTJ47D2DSE4Jwhle7aMzEwO2ZcOPRikqfT3CVG7Qol2c9/I4KZwinZNW5Xv8hNm+G/enSSfIwqQhIXi6ioAUg==",
+			"version": "3.1.1",
+			"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.1.1.tgz",
+			"integrity": "sha512-14ClnMAU8EXnzC+M2/KDd3SFmNUn1QUw1+GxWkEMwGV3iaH8ObunMlO5svzvaWlkSV0WlxJCi40NGnDVJ2XZKQ==",
 			"requires": {
-				"@lit/reactive-element": "^1.0.0",
-				"lit-html": "^2.0.0"
+				"@lit/reactive-element": "^1.1.0",
+				"lit-html": "^2.1.0"
 			}
 		},
 		"lit-html": {
-			"version": "2.0.2",
-			"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.0.2.tgz",
-			"integrity": "sha512-dON7Zg8btb14/fWohQLQBdSgkoiQA4mIUy87evmyJHtxRq7zS6LlC32bT5EPWiof5PUQaDpF45v2OlrxHA5Clg==",
+			"version": "2.1.1",
+			"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.1.1.tgz",
+			"integrity": "sha512-E4BImK6lopAYanJpvcGaAG8kQFF1ccIulPu2BRNZI7acFB6i4ujjjsnaPVFT1j/4lD9r8GKih0Y8d7/LH8SeyQ==",
 			"requires": {
 				"@types/trusted-types": "^2.0.2"
 			}
@@ -29155,13 +29159,13 @@
 			}
 		},
 		"prop-types": {
-			"version": "15.7.2",
-			"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
-			"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
+			"version": "15.8.1",
+			"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+			"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
 			"requires": {
 				"loose-envify": "^1.4.0",
 				"object-assign": "^4.1.1",
-				"react-is": "^16.8.1"
+				"react-is": "^16.13.1"
 			},
 			"dependencies": {
 				"react-is": {
diff --git a/packages/happy-dom/src/nodes/character-data/CharacterData.ts b/packages/happy-dom/src/nodes/character-data/CharacterData.ts
new file mode 100644
index 00000000..e69f7de1
--- /dev/null
+++ b/packages/happy-dom/src/nodes/character-data/CharacterData.ts
@@ -0,0 +1,221 @@
+import Node from '../node/Node';
+import CharacterDataUtility from './CharacterDataUtility';
+import ICharacterData from './ICharacterData';
+import IElement from '../element/IElement';
+import NonDocumentChildNodeUtility from '../child-node/NonDocumentChildNodeUtility';
+import ChildNodeUtility from '../child-node/ChildNodeUtility';
+import MutationRecord from '../../mutation-observer/MutationRecord';
+import MutationTypeEnum from '../../mutation-observer/MutationTypeEnum';
+
+/**
+ * Character data base class.
+ *
+ * Reference:
+ * https://developer.mozilla.org/en-US/docs/Web/API/CharacterData.
+ */
+export default abstract class CharacterData extends Node implements ICharacterData {
+	protected _data = '';
+
+	/**
+	 * Constructor.
+	 *
+	 * @param [data] Data.
+	 */
+	constructor(data?: string) {
+		super();
+
+		if (data) {
+			this._data = data;
+		}
+	}
+
+	/**
+	 * Returns text content.
+	 *
+	 * @returns Text content.
+	 */
+	public get length(): number {
+		return this._data.length;
+	}
+
+	/**
+	 * Returns text content.
+	 *
+	 * @returns Text content.
+	 */
+	public get data(): string {
+		return this._data;
+	}
+
+	/**
+	 * Sets text content.
+	 *
+	 * @param textContent Text content.
+	 */
+	public set data(data: string) {
+		const oldValue = this._data;
+		this._data = data;
+
+		// MutationObserver
+		if (this._observers.length > 0) {
+			for (const observer of this._observers) {
+				if (observer.options.characterData) {
+					const record = new MutationRecord();
+					record.type = MutationTypeEnum.characterData;
+					record.oldValue = observer.options.characterDataOldValue ? oldValue : null;
+					observer.callback([record]);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Returns text content.
+	 *
+	 * @returns Text content.
+	 */
+	public get textContent(): string {
+		return this._data;
+	}
+
+	/**
+	 * Sets text content.
+	 *
+	 * @param textContent Text content.
+	 */
+	public set textContent(textContent: string) {
+		this.data = textContent;
+	}
+
+	/**
+	 * Returns node value.
+	 *
+	 * @returns Node value.
+	 */
+	public get nodeValue(): string {
+		return this._data;
+	}
+
+	/**
+	 * Sets node value.
+	 *
+	 * @param nodeValue Node value.
+	 */
+	public set nodeValue(nodeValue: string) {
+		this.textContent = nodeValue;
+	}
+
+	/**
+	 * Appends the given DOMString to the CharacterData.data string; when this method returns, data contains the concatenated DOMString.
+	 *
+	 * @param data Data.
+	 */
+	public appendData(data: string): void {
+		CharacterDataUtility.appendData(this, data);
+	}
+
+	/**
+	 * Removes the specified amount of characters, starting at the specified offset, from the CharacterData.data string; when this method returns, data contains the shortened DOMString.
+	 *
+	 * @param offset Offset.
+	 * @param count Count.
+	 */
+	public deleteData(offset: number, count: number): void {
+		CharacterDataUtility.deleteData(this, offset, count);
+	}
+
+	/**
+	 * Inserts the specified characters, at the specified offset, in the CharacterData.data string; when this method returns, data contains the modified DOMString.
+	 *
+	 * @param offset Offset.
+	 * @param data Data.
+	 */
+	public insertData(offset: number, data: string): void {
+		CharacterDataUtility.insertData(this, offset, data);
+	}
+
+	/**
+	 * Replaces the specified amount of characters, starting at the specified offset, with the specified DOMString; when this method returns, data contains the modified DOMString.
+	 *
+	 * @param offset Offset.
+	 * @param count Count.
+	 * @param data Data.
+	 */
+	public replaceData(offset: number, count: number, data: string): void {
+		CharacterDataUtility.replaceData(this, offset, count, data);
+	}
+
+	/**
+	 * Returns a DOMString containing the part of CharacterData.data of the specified length and starting at the specified offset.
+	 *
+	 * @param offset Offset.
+	 * @param count Count.
+	 */
+	public substringData(offset: number, count: number): string {
+		return CharacterDataUtility.substringData(this, offset, count);
+	}
+	/**
+	 * Previous element sibling.
+	 *
+	 * @returns Element.
+	 */
+	public get previousElementSibling(): IElement {
+		return NonDocumentChildNodeUtility.previousElementSibling(this);
+	}
+
+	/**
+	 * Next element sibling.
+	 *
+	 * @returns Element.
+	 */
+	public get nextElementSibling(): IElement {
+		return NonDocumentChildNodeUtility.nextElementSibling(this);
+	}
+
+	/**
+	 * Removes the object from its parent children list.
+	 */
+	public remove(): void {
+		ChildNodeUtility.remove(this);
+	}
+
+	/**
+	 * The Node.replaceWith() method replaces this Node in the children list of its parent with a set of Node or DOMString objects.
+	 *
+	 * @param nodes List of Node or DOMString.
+	 */
+	public replaceWith(...nodes: (Node | string)[]): void {
+		ChildNodeUtility.replaceWith(this, ...nodes);
+	}
+
+	/**
+	 * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent, just before this ChildNode. DOMString objects are inserted as equivalent Text nodes.
+	 *
+	 * @param nodes List of Node or DOMString.
+	 */
+	public before(...nodes: (string | Node)[]): void {
+		ChildNodeUtility.before(this, ...nodes);
+	}
+
+	/**
+	 * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent, just after this ChildNode. DOMString objects are inserted as equivalent Text nodes.
+	 *
+	 * @param nodes List of Node or DOMString.
+	 */
+	public after(...nodes: (string | Node)[]): void {
+		ChildNodeUtility.after(this, ...nodes);
+	}
+
+	/**
+	 * Clones a node.
+	 *
+	 * @override
+	 * @param [deep=false] "true" to clone deep.
+	 * @returns Cloned node.
+	 */
+	public cloneNode(deep = false): ICharacterData {
+		const clone = <CharacterData>super.cloneNode(deep);
+		clone._data = this._data;
+		return clone;
+	}
+}
diff --git a/packages/happy-dom/src/nodes/character-data/ICharacterData.ts b/packages/happy-dom/src/nodes/character-data/ICharacterData.ts
index a4106fb2..d5e5c073 100644
--- a/packages/happy-dom/src/nodes/character-data/ICharacterData.ts
+++ b/packages/happy-dom/src/nodes/character-data/ICharacterData.ts
@@ -1,7 +1,8 @@
+import INode from '../node/INode';
 import IChildNode from '../child-node/IChildNode';
 import INonDocumentTypeChildNode from '../child-node/INonDocumentTypeChildNode';
 
-export default interface ICharacterData extends IChildNode, INonDocumentTypeChildNode {
+export default interface ICharacterData extends INode, IChildNode, INonDocumentTypeChildNode {
 	data: string;
 	readonly length: number;
 
@@ -52,5 +53,5 @@ export default interface ICharacterData extends IChildNode, INonDocumentTypeChil
 	 * @param [deep=false] "true" to clone deep.
 	 * @returns Cloned node.
 	 */
-	cloneNode(deep: boolean): ICharacterData;
+	cloneNode(deep?: boolean): ICharacterData;
 }
diff --git a/packages/happy-dom/src/nodes/comment/Comment.ts b/packages/happy-dom/src/nodes/comment/Comment.ts
index 9c9dc912..0615e15d 100644
--- a/packages/happy-dom/src/nodes/comment/Comment.ts
+++ b/packages/happy-dom/src/nodes/comment/Comment.ts
@@ -1,31 +1,12 @@
 import Node from '../node/Node';
-import MutationRecord from '../../mutation-observer/MutationRecord';
-import MutationTypeEnum from '../../mutation-observer/MutationTypeEnum';
-import CharacterDataUtility from '../character-data/CharacterDataUtility';
-import IElement from '../element/IElement';
-import NonDocumentChildNodeUtility from '../child-node/NonDocumentChildNodeUtility';
-import ChildNodeUtility from '../child-node/ChildNodeUtility';
+import CharacterData from '../character-data/CharacterData';
 import IComment from './IComment';
 
 /**
  * Comment node.
  */
-export default class Comment extends Node implements IComment {
+export default class Comment extends CharacterData implements IComment {
 	public readonly nodeType = Node.COMMENT_NODE;
-	private _data = '';
-
-	/**
-	 * Constructor.
-	 *
-	 * @param [comment] Comment.
-	 */
-	constructor(comment?: string) {
-		super();
-
-		if (comment) {
-			this._data = comment;
-		}
-	}
 
 	/**
 	 * Node name.
@@ -36,100 +17,6 @@ export default class Comment extends Node implements IComment {
 		return '#comment';
 	}
 
-	/**
-	 * Returns text content.
-	 *
-	 * @returns Text content.
-	 */
-	public get length(): number {
-		return this._data.length;
-	}
-
-	/**
-	 * Returns text content.
-	 *
-	 * @returns Text content.
-	 */
-	public get data(): string {
-		return this._data;
-	}
-
-	/**
-	 * Sets text content.
-	 *
-	 * @param textContent Text content.
-	 */
-	public set data(data: string) {
-		const oldValue = this._data;
-		this._data = data;
-
-		// MutationObserver
-		if (this._observers.length > 0) {
-			for (const observer of this._observers) {
-				if (observer.options.characterData) {
-					const record = new MutationRecord();
-					record.type = MutationTypeEnum.characterData;
-					record.oldValue = observer.options.characterDataOldValue ? oldValue : null;
-					observer.callback([record]);
-				}
-			}
-		}
-	}
-
-	/**
-	 * Returns text content.
-	 *
-	 * @returns Text content.
-	 */
-	public get textContent(): string {
-		return this._data;
-	}
-
-	/**
-	 * Sets text content.
-	 *
-	 * @param textContent Text content.
-	 */
-	public set textContent(textContent: string) {
-		this.data = textContent;
-	}
-
-	/**
-	 * Returns node value.
-	 *
-	 * @returns Node value.
-	 */
-	public get nodeValue(): string {
-		return this._data;
-	}
-
-	/**
-	 * Sets node value.
-	 *
-	 * @param nodeValue Node value.
-	 */
-	public set nodeValue(nodeValue: string) {
-		this.textContent = nodeValue;
-	}
-
-	/**
-	 * Previous element sibling.
-	 *
-	 * @returns Element.
-	 */
-	public get previousElementSibling(): IElement {
-		return NonDocumentChildNodeUtility.previousElementSibling(this);
-	}
-
-	/**
-	 * Next element sibling.
-	 *
-	 * @returns Element.
-	 */
-	public get nextElementSibling(): IElement {
-		return NonDocumentChildNodeUtility.nextElementSibling(this);
-	}
-
 	/**
 	 * Converts to string.
 	 *
@@ -139,90 +26,6 @@ export default class Comment extends Node implements IComment {
 		return '[object Comment]';
 	}
 
-	/**
-	 * Appends the given DOMString to the CharacterData.data string; when this method returns, data contains the concatenated DOMString.
-	 *
-	 * @param data Data.
-	 */
-	public appendData(data: string): void {
-		CharacterDataUtility.appendData(this, data);
-	}
-
-	/**
-	 * Removes the specified amount of characters, starting at the specified offset, from the CharacterData.data string; when this method returns, data contains the shortened DOMString.
-	 *
-	 * @param offset Offset.
-	 * @param count Count.
-	 */
-	public deleteData(offset: number, count: number): void {
-		CharacterDataUtility.deleteData(this, offset, count);
-	}
-
-	/**
-	 * Inserts the specified characters, at the specified offset, in the CharacterData.data string; when this method returns, data contains the modified DOMString.
-	 *
-	 * @param offset Offset.
-	 * @param data Data.
-	 */
-	public insertData(offset: number, data: string): void {
-		CharacterDataUtility.insertData(this, offset, data);
-	}
-
-	/**
-	 * Removes the object from its parent children list.
-	 */
-	public remove(): void {
-		ChildNodeUtility.remove(this);
-	}
-
-	/**
-	 * The Node.replaceWith() method replaces this Node in the children list of its parent with a set of Node or DOMString objects.
-	 *
-	 * @param nodes List of Node or DOMString.
-	 */
-	public replaceWith(...nodes: (Node | string)[]): void {
-		ChildNodeUtility.replaceWith(this, ...nodes);
-	}
-
-	/**
-	 * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent, just before this ChildNode. DOMString objects are inserted as equivalent Text nodes.
-	 *
-	 * @param nodes List of Node or DOMString.
-	 */
-	public before(...nodes: (string | Node)[]): void {
-		ChildNodeUtility.before(this, ...nodes);
-	}
-
-	/**
-	 * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent, just after this ChildNode. DOMString objects are inserted as equivalent Text nodes.
-	 *
-	 * @param nodes List of Node or DOMString.
-	 */
-	public after(...nodes: (string | Node)[]): void {
-		ChildNodeUtility.after(this, ...nodes);
-	}
-
-	/**
-	 * Replaces the specified amount of characters, starting at the specified offset, with the specified DOMString; when this method returns, data contains the modified DOMString.
-	 *
-	 * @param offset Offset.
-	 * @param count Count.
-	 * @param data Data.
-	 */
-	public replaceData(offset: number, count: number, data: string): void {
-		CharacterDataUtility.replaceData(this, offset, count, data);
-	}
-
-	/**
-	 * Returns a DOMString containing the part of CharacterData.data of the specified length and starting at the specified offset.
-	 *
-	 * @param offset Offset.
-	 * @param count Count.
-	 */
-	public substringData(offset: number, count: number): string {
-		return CharacterDataUtility.substringData(this, offset, count);
-	}
-
 	/**
 	 * Clones a node.
 	 *
@@ -231,8 +34,6 @@ export default class Comment extends Node implements IComment {
 	 * @returns Cloned node.
 	 */
 	public cloneNode(deep = false): IComment {
-		const clone = <Comment>super.cloneNode(deep);
-		clone._data = this._data;
-		return clone;
+		return <Comment>super.cloneNode(deep);
 	}
 }
diff --git a/packages/happy-dom/src/nodes/comment/IComment.ts b/packages/happy-dom/src/nodes/comment/IComment.ts
index b15602bb..ba91c8c2 100644
--- a/packages/happy-dom/src/nodes/comment/IComment.ts
+++ b/packages/happy-dom/src/nodes/comment/IComment.ts
@@ -8,5 +8,5 @@ export default interface IComment extends ICharacterData {
 	 * @param [deep=false] "true" to clone deep.
 	 * @returns Cloned node.
 	 */
-	cloneNode(deep: boolean): IComment;
+	cloneNode(deep?: boolean): IComment;
 }
diff --git a/packages/happy-dom/src/nodes/document/Document.ts b/packages/happy-dom/src/nodes/document/Document.ts
index 3bb452d8..dc543503 100644
--- a/packages/happy-dom/src/nodes/document/Document.ts
+++ b/packages/happy-dom/src/nodes/document/Document.ts
@@ -24,7 +24,8 @@ import IElement from '../element/IElement';
 import IHTMLElement from '../html-element/IHTMLElement';
 import IDocumentType from '../document-type/IDocumentType';
 import INode from '../node/INode';
-import ICharacterData from '../character-data/ICharacterData';
+import IComment from '../comment/IComment';
+import IText from '../text/IText';
 import IDocumentFragment from '../document-fragment/IDocumentFragment';
 import INodeList from '../node/INodeList';
 import IHTMLCollection from '../element/IHTMLCollection';
@@ -578,7 +579,7 @@ export default class Document extends Node implements IDocument {
 	 * @param [data] Text data.
 	 * @returns Text node.
 	 */
-	public createTextNode(data?: string): ICharacterData {
+	public createTextNode(data?: string): IText {
 		Text.ownerDocument = this;
 		return new Text(data);
 	}
@@ -589,7 +590,7 @@ export default class Document extends Node implements IDocument {
 	 * @param [data] Text data.
 	 * @returns Text node.
 	 */
-	public createComment(data?: string): ICharacterData {
+	public createComment(data?: string): IComment {
 		Comment.ownerDocument = this;
 		return new Comment(data);
 	}
diff --git a/packages/happy-dom/src/nodes/node/INode.ts b/packages/happy-dom/src/nodes/node/INode.ts
index 8ecdf272..ddae7aa8 100644
--- a/packages/happy-dom/src/nodes/node/INode.ts
+++ b/packages/happy-dom/src/nodes/node/INode.ts
@@ -9,12 +9,12 @@ export default interface INode extends IEventTarget {
 	readonly nodeType: number;
 	readonly childNodes: INode[];
 	readonly isConnected: boolean;
-	readonly nodeValue: string;
 	readonly nodeName: string;
 	readonly previousSibling: INode;
 	readonly nextSibling: INode;
 	readonly firstChild: INode;
 	readonly lastChild: INode;
+	nodeValue: string;
 	textContent: string;
 	connectedCallback?(): void;
 	disconnectedCallback?(): void;
diff --git a/packages/happy-dom/src/nodes/node/Node.ts b/packages/happy-dom/src/nodes/node/Node.ts
index 34578b77..491d5a6f 100644
--- a/packages/happy-dom/src/nodes/node/Node.ts
+++ b/packages/happy-dom/src/nodes/node/Node.ts
@@ -67,6 +67,13 @@ export default class Node extends EventTarget implements INode {
 		return null;
 	}
 
+	/**
+	 * Sets node value.
+	 */
+	public set nodeValue(_nodeValue: string) {
+		// Do nothing
+	}
+
 	/**
 	 * Node name.
 	 *
diff --git a/packages/happy-dom/src/nodes/text/IText.ts b/packages/happy-dom/src/nodes/text/IText.ts
index 0b9a183f..01e81856 100644
--- a/packages/happy-dom/src/nodes/text/IText.ts
+++ b/packages/happy-dom/src/nodes/text/IText.ts
@@ -8,5 +8,5 @@ export default interface IText extends ICharacterData {
 	 * @param [deep=false] "true" to clone deep.
 	 * @returns Cloned node.
 	 */
-	cloneNode(deep: boolean): IText;
+	cloneNode(deep?: boolean): IText;
 }
diff --git a/packages/happy-dom/src/nodes/text/Text.ts b/packages/happy-dom/src/nodes/text/Text.ts
index 45a3a139..b64f7622 100644
--- a/packages/happy-dom/src/nodes/text/Text.ts
+++ b/packages/happy-dom/src/nodes/text/Text.ts
@@ -1,31 +1,12 @@
 import Node from '../node/Node';
-import MutationRecord from '../../mutation-observer/MutationRecord';
-import MutationTypeEnum from '../../mutation-observer/MutationTypeEnum';
-import CharacterDataUtility from '../character-data/CharacterDataUtility';
-import NonDocumentChildNodeUtility from '../child-node/NonDocumentChildNodeUtility';
-import ChildNodeUtility from '../child-node/ChildNodeUtility';
-import IElement from '../element/IElement';
+import CharacterData from '../character-data/CharacterData';
 import IText from './IText';
 
 /**
  * Text node.
  */
-export default class Text extends Node implements IText {
+export default class Text extends CharacterData implements IText {
 	public readonly nodeType = Node.TEXT_NODE;
-	private _data = '';
-
-	/**
-	 * Constructor.
-	 *
-	 * @param [text] Text.
-	 */
-	constructor(text?: string) {
-		super();
-
-		if (text) {
-			this._data = text;
-		}
-	}
 
 	/**
 	 * Node name.
@@ -36,82 +17,6 @@ export default class Text extends Node implements IText {
 		return '#text';
 	}
 
-	/**
-	 * Returns text content.
-	 *
-	 * @returns Text content.
-	 */
-	public get length(): number {
-		return this._data.length;
-	}
-
-	/**
-	 * Returns text content.
-	 *
-	 * @returns Text content.
-	 */
-	public get data(): string {
-		return this._data;
-	}
-
-	/**
-	 * Sets text content.
-	 *
-	 * @param textContent Text content.
-	 */
-	public set data(data: string) {
-		const oldValue = this._data;
-		this._data = data;
-
-		// MutationObserver
-		if (this._observers.length > 0) {
-			for (const observer of this._observers) {
-				if (observer.options.characterData) {
-					const record = new MutationRecord();
-					record.type = MutationTypeEnum.characterData;
-					record.oldValue = observer.options.characterDataOldValue ? oldValue : null;
-					observer.callback([record]);
-				}
-			}
-		}
-	}
-
-	/**
-	 * Returns text content.
-	 *
-	 * @returns Text content.
-	 */
-	public get textContent(): string {
-		return this._data;
-	}
-
-	/**
-	 * Sets text content.
-	 *
-	 * @param textContent Text content.
-	 */
-	public set textContent(textContent: string) {
-		this.data = textContent;
-	}
-
-	/**
-	 * Returns node value.
-	 *
-	 * @returns Node value.
-	 */
-	public get nodeValue(): string {
-		return this._data;
-	}
-
-	/**
-	 * Sets node value.
-	 *
-	 * @param nodeValue Node value.
-	 */
-	public set nodeValue(nodeValue: string) {
-		this.textContent = nodeValue;
-	}
-
 	/**
 	 * Converts to string.
 	 *
@@ -121,108 +26,6 @@ export default class Text extends Node implements IText {
 		return '[object Text]';
 	}
 
-	/**
-	 * Previous element sibling.
-	 *
-	 * @returns Element.
-	 */
-	public get previousElementSibling(): IElement {
-		return NonDocumentChildNodeUtility.previousElementSibling(this);
-	}
-
-	/**
-	 * Next element sibling.
-	 *
-	 * @returns Element.
-	 */
-	public get nextElementSibling(): IElement {
-		return NonDocumentChildNodeUtility.nextElementSibling(this);
-	}
-
-	/**
-	 * Appends the given DOMString to the CharacterData.data string; when this method returns, data contains the concatenated DOMString.
-	 *
-	 * @param data Data.
-	 */
-	public appendData(data: string): void {
-		CharacterDataUtility.appendData(this, data);
-	}
-
-	/**
-	 * Removes the specified amount of characters, starting at the specified offset, from the CharacterData.data string; when this method returns, data contains the shortened DOMString.
-	 *
-	 * @param offset Offset.
-	 * @param count Count.
-	 */
-	public deleteData(offset: number, count: number): void {
-		CharacterDataUtility.deleteData(this, offset, count);
-	}
-
-	/**
-	 * Inserts the specified characters, at the specified offset, in the CharacterData.data string; when this method returns, data contains the modified DOMString.
-	 *
-	 * @param offset Offset.
-	 * @param data Data.
-	 */
-	public insertData(offset: number, data: string): void {
-		CharacterDataUtility.insertData(this, offset, data);
-	}
-
-	/**
-	 * Removes the object from its parent children list.
-	 */
-	public remove(): void {
-		ChildNodeUtility.remove(this);
-	}
-
-	/**
-	 * The Node.replaceWith() method replaces this Node in the children list of its parent with a set of Node or DOMString objects.
-	 *
-	 * @param nodes List of Node or DOMString.
-	 */
-	public replaceWith(...nodes: (Node | string)[]): void {
-		ChildNodeUtility.replaceWith(this, ...nodes);
-	}
-
-	/**
-	 * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent, just before this ChildNode. DOMString objects are inserted as equivalent Text nodes.
-	 *
-	 * @param nodes List of Node or DOMString.
-	 */
-	public before(...nodes: (string | Node)[]): void {
-		ChildNodeUtility.before(this, ...nodes);
-	}
-
-	/**
-	 * Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent, just after this ChildNode. DOMString objects are inserted as equivalent Text nodes.
-	 *
-	 * @param nodes List of Node or DOMString.
-	 */
-	public after(...nodes: (string | Node)[]): void {
-		ChildNodeUtility.after(this, ...nodes);
-	}
-
-	/**
-	 * Replaces the specified amount of characters, starting at the specified offset, with the specified DOMString; when this method returns, data contains the modified DOMString.
-	 *
-	 * @param offset Offset.
-	 * @param count Count.
-	 * @param data Data.
-	 */
-	public replaceData(offset: number, count: number, data: string): void {
-		CharacterDataUtility.replaceData(this, offset, count, data);
-	}
-
-	/**
-	 * Returns a DOMString containing the part of CharacterData.data of the specified length and starting at the specified offset.
-	 *
-	 * @param offset Offset.
-	 * @param count Count.
-	 */
-	public substringData(offset: number, count: number): string {
-		return CharacterDataUtility.substringData(this, offset, count);
-	}
-
 	/**
 	 * Clones a node.
 	 *
@@ -231,8 +34,6 @@ export default class Text extends Node implements IText {
 	 * @returns Cloned node.
 	 */
 	public cloneNode(deep = false): IText {
-		const clone = <Text>super.cloneNode(deep);
-		clone._data = this._data;
-		return clone;
+		return <Text>super.cloneNode(deep);
 	}
 }
diff --git a/packages/happy-dom/src/window/Window.ts b/packages/happy-dom/src/window/Window.ts
index 144fb88c..fbb5d1ce 100644
--- a/packages/happy-dom/src/window/Window.ts
+++ b/packages/happy-dom/src/window/Window.ts
@@ -19,6 +19,7 @@ import SVGElement from '../nodes/svg-element/SVGElement';
 import HTMLScriptElement from '../nodes/html-script-element/HTMLScriptElement';
 import HTMLImageElement from '../nodes/html-image-element/HTMLImageElement';
 import DocumentFragment from '../nodes/document-fragment/DocumentFragment';
+import CharacterData from '../nodes/character-data/CharacterData';
 import TreeWalker from '../tree-walker/TreeWalker';
 import Event from '../event/Event';
 import CustomEvent from '../event/events/CustomEvent';
@@ -98,6 +99,7 @@ export default class Window extends EventTarget implements IWindow, NodeJS.Globa
 	public readonly ShadowRoot = ShadowRoot;
 	public readonly Element = Element;
 	public readonly DocumentFragment = DocumentFragment;
+	public readonly CharacterData = CharacterData;
 	public readonly NodeFilter = NodeFilter;
 	public readonly TreeWalker = TreeWalker;
 	public readonly DOMParser = DOMParser;


Test patch:
diff --git a/packages/happy-dom/test/nodes/comment-node/CommentNode.test.ts b/packages/happy-dom/test/nodes/character-data/CharaterData.test.ts
similarity index 90%
rename from packages/happy-dom/test/nodes/comment-node/CommentNode.test.ts
rename to packages/happy-dom/test/nodes/character-data/CharaterData.test.ts
index 21346ac2..68a53f13 100644
--- a/packages/happy-dom/test/nodes/comment-node/CommentNode.test.ts
+++ b/packages/happy-dom/test/nodes/character-data/CharaterData.test.ts
@@ -1,11 +1,13 @@
-import NonDocumentChildNodeUtility from '../../../src/nodes/child-node/NonDocumentChildNodeUtility';
 import CharacterDataUtility from '../../../src/nodes/character-data/CharacterDataUtility';
-import Window from '../../../src/window/Window';
+import NonDocumentChildNodeUtility from '../../../src/nodes/child-node/NonDocumentChildNodeUtility';
 import ChildNodeUtility from '../../../src/nodes/child-node/ChildNodeUtility';
+import CharacterData from '../../../src/nodes/character-data/CharacterData';
+import Window from '../../../src/window/Window';
+import Document from '../../../src/nodes/document/Document';
 
-describe('CommentNode', () => {
-	let window;
-	let document;
+describe('CharaterData', () => {
+	let window: Window;
+	let document: Document;
 
 	beforeEach(() => {
 		window = new Window();
@@ -16,13 +18,6 @@ describe('CommentNode', () => {
 		jest.restoreAllMocks();
 	});
 
-	describe('get nodeName()', () => {
-		it('Returns "#comment".', () => {
-			const node = document.createComment('test');
-			expect(node.nodeName).toBe('#comment');
-		});
-	});
-
 	describe('get length()', () => {
 		it('Returns "#comment".', () => {
 			const node = document.createComment('test');
@@ -30,48 +25,149 @@ describe('CommentNode', () => {
 		});
 	});
 
-	describe('get textContent()', () => {
+	describe('get data()', () => {
 		it('Returns text content.', () => {
-			const node = document.createComment('test');
-			expect(node.textContent).toBe('test');
+			const node = <CharacterData>document.createComment('test');
+			expect(node.data).toBe('test');
 		});
 	});
 
-	describe('set textContent()', () => {
+	describe('set data()', () => {
 		it('Sets text content.', () => {
-			const node = document.createComment('test');
-			node.textContent = 'new text';
-			expect(node.textContent).toBe('new text');
+			const node = <CharacterData>document.createComment('test');
+			node.data = 'new text';
+			expect(node.data).toBe('new text');
 		});
 	});
 
 	describe('get nodeValue()', () => {
 		it('Returns text content.', () => {
-			const node = document.createComment('test');
+			const node = document.createTextNode('test');
 			expect(node.nodeValue).toBe('test');
 		});
 	});
 
 	describe('set nodeValue()', () => {
 		it('Sets text content.', () => {
-			const node = document.createComment('test');
+			const node = document.createTextNode('test');
 			node.nodeValue = 'new text';
 			expect(node.nodeValue).toBe('new text');
 		});
 	});
 
-	describe('get data()', () => {
+	describe('get textContent()', () => {
 		it('Returns text content.', () => {
 			const node = document.createComment('test');
-			expect(node.data).toBe('test');
+			expect(node.textContent).toBe('test');
 		});
 	});
 
-	describe('set data()', () => {
+	describe('set textContent()', () => {
 		it('Sets text content.', () => {
 			const node = document.createComment('test');
-			node.data = 'new text';
-			expect(node.data).toBe('new text');
+			node.textContent = 'new text';
+			expect(node.textContent).toBe('new text');
+		});
+	});
+
+	describe('appendData()', () => {
+		it('Appends data.', () => {
+			const node = <CharacterData>document.createComment('test');
+			const expectedData = 'data';
+			let isCalled = false;
+
+			jest.spyOn(CharacterDataUtility, 'appendData').mockImplementation((characterData, data) => {
+				expect(characterData).toBe(node);
+				expect(data).toBe(expectedData);
+				isCalled = true;
+			});
+
+			node.appendData(expectedData);
+			expect(isCalled).toBe(true);
+		});
+	});
+
+	describe('deleteData()', () => {
+		it('Deletes data.', () => {
+			const node = <CharacterData>document.createComment('test');
+			const expectedOffset = -1;
+			const expectedCount = -1;
+			let isCalled = false;
+
+			jest
+				.spyOn(CharacterDataUtility, 'deleteData')
+				.mockImplementation((characterData, offset, count) => {
+					expect(characterData).toBe(node);
+					expect(offset).toBe(expectedOffset);
+					expect(count).toBe(expectedCount);
+					isCalled = true;
+				});
+
+			node.deleteData(expectedOffset, expectedCount);
+			expect(isCalled).toBe(true);
+		});
+	});
+
+	describe('insertData()', () => {
+		it('Inserts data.', () => {
+			const node = <CharacterData>document.createComment('test');
+			const expectedOffset = -1;
+			const expectedData = 'data';
+			let isCalled = false;
+
+			jest
+				.spyOn(CharacterDataUtility, 'insertData')
+				.mockImplementation((characterData, offset, data) => {
+					expect(characterData).toBe(node);
+					expect(offset).toBe(expectedOffset);
+					expect(data).toBe(expectedData);
+					isCalled = true;
+				});
+
+			node.insertData(expectedOffset, expectedData);
+			expect(isCalled).toBe(true);
+		});
+	});
+
+	describe('replaceData()', () => {
+		it('Replaces data.', () => {
+			const node = <CharacterData>document.createComment('test');
+			const expectedOffset = -1;
+			const expectedCount = -1;
+			const expectedData = 'data';
+			let isCalled = false;
+
+			jest
+				.spyOn(CharacterDataUtility, 'replaceData')
+				.mockImplementation((characterData, offset, count, data) => {
+					expect(characterData).toBe(node);
+					expect(offset).toBe(expectedOffset);
+					expect(count).toBe(expectedCount);
+					expect(data).toBe(expectedData);
+					isCalled = true;
+				});
+
+			node.replaceData(expectedOffset, expectedCount, expectedData);
+			expect(isCalled).toBe(true);
+		});
+	});
+
+	describe('substringData()', () => {
+		it('Returns a sub-string.', () => {
+			const node = document.createComment('test');
+			const expectedOffset = -1;
+			const expectedCount = -1;
+
+			jest
+				.spyOn(CharacterDataUtility, 'substringData')
+				.mockImplementation((characterData, offset, count) => {
+					expect(characterData).toBe(node);
+					expect(offset).toBe(expectedOffset);
+					expect(count).toBe(expectedCount);
+					return 'substring';
+				});
+
+			expect(node.substringData(expectedOffset, expectedCount)).toBe('substring');
 		});
 	});
 
@@ -105,13 +201,6 @@ describe('CommentNode', () => {
 		});
 	});
 
-	describe('toString()', () => {
-		it('Returns "[object Comment]".', () => {
-			const node = document.createComment('test');
-			expect(node.toString()).toBe('[object Comment]');
-		});
-	});
-
 	describe('remove()', () => {
 		it('Removes the node from its parent.', () => {
 			const comment = document.createComment('test');
@@ -181,107 +270,6 @@ describe('CommentNode', () => {
 		});
 	});
 
-	describe('appendData()', () => {
-		it('Appends data.', () => {
-			const node = document.createComment('test');
-			const expectedData = 'data';
-			let isCalled = false;
-
-			jest.spyOn(CharacterDataUtility, 'appendData').mockImplementation((characterData, data) => {
-				expect(characterData).toBe(node);
-				expect(data).toBe(expectedData);
-				isCalled = true;
-			});
-
-			node.appendData(expectedData);
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('deleteData()', () => {
-		it('Deletes data.', () => {
-			const node = document.createComment('test');
-			const expectedOffset = -1;
-			const expectedCount = -1;
-			let isCalled = false;
-
-			jest
-				.spyOn(CharacterDataUtility, 'deleteData')
-				.mockImplementation((characterData, offset, count) => {
-					expect(characterData).toBe(node);
-					expect(offset).toBe(expectedOffset);
-					expect(count).toBe(expectedCount);
-					isCalled = true;
-				});
-
-			node.deleteData(expectedOffset, expectedCount);
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('insertData()', () => {
-		it('Inserts data.', () => {
-			const node = document.createComment('test');
-			const expectedOffset = -1;
-			const expectedData = 'data';
-			let isCalled = false;
-
-			jest
-				.spyOn(CharacterDataUtility, 'insertData')
-				.mockImplementation((characterData, offset, data) => {
-					expect(characterData).toBe(node);
-					expect(offset).toBe(expectedOffset);
-					expect(data).toBe(expectedData);
-					isCalled = true;
-				});
-
-			node.insertData(expectedOffset, expectedData);
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('replaceData()', () => {
-		it('Replaces data.', () => {
-			const node = document.createComment('test');
-			const expectedOffset = -1;
-			const expectedCount = -1;
-			const expectedData = 'data';
-			let isCalled = false;
-
-			jest
-				.spyOn(CharacterDataUtility, 'replaceData')
-				.mockImplementation((characterData, offset, count, data) => {
-					expect(characterData).toBe(node);
-					expect(offset).toBe(expectedOffset);
-					expect(count).toBe(expectedCount);
-					expect(data).toBe(expectedData);
-					isCalled = true;
-				});
-
-			node.replaceData(expectedOffset, expectedCount, expectedData);
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('substringData()', () => {
-		it('Returns a sub-string.', () => {
-			const node = document.createComment('test');
-			const expectedOffset = -1;
-			const expectedCount = -1;
-
-			jest
-				.spyOn(CharacterDataUtility, 'substringData')
-				.mockImplementation((characterData, offset, count) => {
-					expect(characterData).toBe(node);
-					expect(offset).toBe(expectedOffset);
-					expect(count).toBe(expectedCount);
-					return 'substring';
-				});
-
-			expect(node.substringData(expectedOffset, expectedCount)).toBe('substring');
-		});
-	});
-
 	describe('cloneNode()', () => {
 		it('Clones the node.', () => {
 			const node = document.createComment('test');
diff --git a/packages/happy-dom/test/nodes/comment/Comment.test.ts b/packages/happy-dom/test/nodes/comment/Comment.test.ts
new file mode 100644
index 00000000..96b079f7
--- /dev/null
+++ b/packages/happy-dom/test/nodes/comment/Comment.test.ts
@@ -0,0 +1,38 @@
+import Window from '../../../src/window/Window';
+import Document from '../../../src/nodes/document/Document';
+
+describe('Comment', () => {
+	let window: Window;
+	let document: Document;
+
+	beforeEach(() => {
+		window = new Window();
+		document = window.document;
+	});
+
+	afterEach(() => {
+		jest.restoreAllMocks();
+	});
+
+	describe('get nodeName()', () => {
+		it('Returns "#comment".', () => {
+			const node = document.createComment('test');
+			expect(node.nodeName).toBe('#comment');
+		});
+	});
+
+	describe('toString()', () => {
+		it('Returns "[object Comment]".', () => {
+			const node = document.createComment('test');
+			expect(node.toString()).toBe('[object Comment]');
+		});
+	});
+
+	describe('cloneNode()', () => {
+		it('Clones the node.', () => {
+			const node = document.createComment('test');
+			const clone = node.cloneNode();
+			expect(clone.data).toBe(node.data);
+		});
+	});
+});
diff --git a/packages/happy-dom/test/nodes/text-node/TextNode.test.ts b/packages/happy-dom/test/nodes/text-node/TextNode.test.ts
deleted file mode 100644
index 8db73acc..00000000
--- a/packages/happy-dom/test/nodes/text-node/TextNode.test.ts
+++ /dev/null
@@ -1,290 +0,0 @@
-import NonDocumentChildNodeUtility from '../../../src/nodes/child-node/NonDocumentChildNodeUtility';
-import CharacterDataUtility from '../../../src/nodes/character-data/CharacterDataUtility';
-import Window from '../../../src/window/Window';
-import ChildNodeUtility from '../../../src/nodes/child-node/ChildNodeUtility';
-
-describe('TextNode', () => {
-	let window;
-	let document;
-
-	beforeEach(() => {
-		window = new Window();
-		document = window.document;
-	});
-
-	afterEach(() => {
-		jest.restoreAllMocks();
-	});
-
-	describe('get nodeName()', () => {
-		it('Returns "#text".', () => {
-			const node = document.createTextNode('test');
-			expect(node.nodeName).toBe('#text');
-		});
-	});
-	describe('get length()', () => {
-		it('Returns "#comment".', () => {
-			const node = document.createTextNode('test');
-			expect(node.length).toBe(4);
-		});
-	});
-
-	describe('get textContent()', () => {
-		it('Returns text content.', () => {
-			const node = document.createTextNode('test');
-			expect(node.textContent).toBe('test');
-		});
-	});
-
-	describe('set textContent()', () => {
-		it('Sets text content.', () => {
-			const node = document.createTextNode('test');
-			node.textContent = 'new text';
-			expect(node.textContent).toBe('new text');
-		});
-	});
-
-	describe('get nodeValue()', () => {
-		it('Returns text content.', () => {
-			const node = document.createTextNode('test');
-			expect(node.nodeValue).toBe('test');
-		});
-	});
-
-	describe('set nodeValue()', () => {
-		it('Sets text content.', () => {
-			const node = document.createTextNode('test');
-			node.nodeValue = 'new text';
-			expect(node.nodeValue).toBe('new text');
-		});
-	});
-
-	describe('get data()', () => {
-		it('Returns text content.', () => {
-			const node = document.createTextNode('test');
-			expect(node.data).toBe('test');
-		});
-	});
-
-	describe('set data()', () => {
-		it('Sets text content.', () => {
-			const node = document.createTextNode('test');
-			node.data = 'new text';
-			expect(node.data).toBe('new text');
-		});
-	});
-
-	describe('get previousElementSibling()', () => {
-		it('Returns previous element sibling..', () => {
-			const node = document.createTextNode('test');
-			const previousElementSibling = document.createElement('div');
-			jest
-				.spyOn(NonDocumentChildNodeUtility, 'previousElementSibling')
-				.mockImplementation(childNode => {
-					expect(childNode).toBe(node);
-					return previousElementSibling;
-				});
-
-			expect(node.previousElementSibling).toBe(previousElementSibling);
-		});
-	});
-
-	describe('get nextElementSibling()', () => {
-		it('Returns next element sibling..', () => {
-			const node = document.createTextNode('test');
-			const nextElementSibling = document.createElement('div');
-			jest
-				.spyOn(NonDocumentChildNodeUtility, 'nextElementSibling')
-				.mockImplementation(childNode => {
-					expect(childNode).toBe(node);
-					return nextElementSibling;
-				});
-
-			expect(node.nextElementSibling).toBe(nextElementSibling);
-		});
-	});
-
-	describe('toString()', () => {
-		it('Returns "[object Text]".', () => {
-			const node = document.createTextNode('test');
-			expect(node.toString()).toBe('[object Text]');
-		});
-	});
-
-	describe('remove()', () => {
-		it('Removes the node from its parent.', () => {
-			const text = document.createTextNode('test');
-			let isCalled = false;
-
-			jest.spyOn(ChildNodeUtility, 'remove').mockImplementation(childNode => {
-				expect(childNode).toBe(text);
-				isCalled = true;
-			});
-
-			text.remove();
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('replaceWith()', () => {
-		it('Replaces a Node in the children list of its parent with a set of Node or DOMString objects.', () => {
-			const text = document.createTextNode('test');
-			const node1 = document.createComment('test1');
-			const node2 = document.createComment('test2');
-			let isCalled = false;
-
-			jest.spyOn(ChildNodeUtility, 'replaceWith').mockImplementation((childNode, ...nodes) => {
-				expect(childNode).toBe(text);
-				expect(nodes).toEqual([node1, node2]);
-				isCalled = true;
-			});
-
-			text.replaceWith(node1, node2);
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('before()', () => {
-		it("Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent, just before this ChildNode. DOMString objects are inserted as equivalent Text nodes.", () => {
-			const text = document.createTextNode('test');
-			const node1 = document.createComment('test1');
-			const node2 = document.createComment('test2');
-			let isCalled = false;
-
-			jest.spyOn(ChildNodeUtility, 'before').mockImplementation((childNode, ...nodes) => {
-				expect(childNode).toBe(text);
-				expect(nodes).toEqual([node1, node2]);
-				isCalled = true;
-			});
-
-			text.before(node1, node2);
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('after()', () => {
-		it("Inserts a set of Node or DOMString objects in the children list of this ChildNode's parent, just after this ChildNode. DOMString objects are inserted as equivalent Text nodes.", () => {
-			const text = document.createTextNode('test');
-			const node1 = document.createComment('test1');
-			const node2 = document.createComment('test2');
-			let isCalled = false;
-
-			jest.spyOn(ChildNodeUtility, 'after').mockImplementation((childNode, ...nodes) => {
-				expect(childNode).toBe(text);
-				expect(nodes).toEqual([node1, node2]);
-				isCalled = true;
-			});
-
-			text.after(node1, node2);
-			expect(isCalled).toBe(true);
-		});
-	});
-	describe('appendData()', () => {
-		it('Appends data.', () => {
-			const node = document.createTextNode('test');
-			const expectedData = 'data';
-			let isCalled = false;
-
-			jest.spyOn(CharacterDataUtility, 'appendData').mockImplementation((characterData, data) => {
-				expect(characterData).toBe(node);
-				expect(data).toBe(expectedData);
-				isCalled = true;
-			});
-
-			node.appendData(expectedData);
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('deleteData()', () => {
-		it('Deletes data.', () => {
-			const node = document.createTextNode('test');
-			const expectedOffset = -1;
-			const expectedCount = -1;
-			let isCalled = false;
-
-			jest
-				.spyOn(CharacterDataUtility, 'deleteData')
-				.mockImplementation((characterData, offset, count) => {
-					expect(characterData).toBe(node);
-					expect(offset).toBe(expectedOffset);
-					expect(count).toBe(expectedCount);
-					isCalled = true;
-				});
-
-			node.deleteData(expectedOffset, expectedCount);
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('insertData()', () => {
-		it('Inserts data.', () => {
-			const node = document.createTextNode('test');
-			const expectedOffset = -1;
-			const expectedData = 'data';
-			let isCalled = false;
-
-			jest
-				.spyOn(CharacterDataUtility, 'insertData')
-				.mockImplementation((characterData, offset, data) => {
-					expect(characterData).toBe(node);
-					expect(offset).toBe(expectedOffset);
-					expect(data).toBe(expectedData);
-					isCalled = true;
-				});
-
-			node.insertData(expectedOffset, expectedData);
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('replaceData()', () => {
-		it('Replaces data.', () => {
-			const node = document.createTextNode('test');
-			const expectedOffset = -1;
-			const expectedCount = -1;
-			const expectedData = 'data';
-			let isCalled = false;
-
-			jest
-				.spyOn(CharacterDataUtility, 'replaceData')
-				.mockImplementation((characterData, offset, count, data) => {
-					expect(characterData).toBe(node);
-					expect(offset).toBe(expectedOffset);
-					expect(count).toBe(expectedCount);
-					expect(data).toBe(expectedData);
-					isCalled = true;
-				});
-
-			node.replaceData(expectedOffset, expectedCount, expectedData);
-			expect(isCalled).toBe(true);
-		});
-	});
-
-	describe('substringData()', () => {
-		it('Returns a sub-string.', () => {
-			const node = document.createTextNode('test');
-			const expectedOffset = -1;
-			const expectedCount = -1;
-
-			jest
-				.spyOn(CharacterDataUtility, 'substringData')
-				.mockImplementation((characterData, offset, count) => {
-					expect(characterData).toBe(node);
-					expect(offset).toBe(expectedOffset);
-					expect(count).toBe(expectedCount);
-					return 'substring';
-				});
-
-			expect(node.substringData(expectedOffset, expectedCount)).toBe('substring');
-		});
-	});
-
-	describe('cloneNode()', () => {
-		it('Clones the node.', () => {
-			const node = document.createTextNode('test');
-			const clone = node.cloneNode();
-			expect(clone.data).toBe(node.data);
-		});
-	});
-});
diff --git a/packages/happy-dom/test/nodes/text/Text.test.ts b/packages/happy-dom/test/nodes/text/Text.test.ts
new file mode 100644
index 00000000..cf8bf9de
--- /dev/null
+++ b/packages/happy-dom/test/nodes/text/Text.test.ts
@@ -0,0 +1,38 @@
+import Window from '../../../src/window/Window';
+import Document from '../../../src/nodes/document/Document';
+
+describe('Text', () => {
+	let window: Window;
+	let document: Document;
+
+	beforeEach(() => {
+		window = new Window();
+		document = window.document;
+	});
+
+	afterEach(() => {
+		jest.restoreAllMocks();
+	});
+
+	describe('get nodeName()', () => {
+		it('Returns "#text".', () => {
+			const node = document.createTextNode('test');
+			expect(node.nodeName).toBe('#text');
+		});
+	});
+
+	describe('toString()', () => {
+		it('Returns "[object Text]".', () => {
+			const node = document.createTextNode('test');
+			expect(node.toString()).toBe('[object Text]');
+		});
+	});
+
+	describe('cloneNode()', () => {
+		it('Clones the node.', () => {
+			const node = document.createTextNode('test');
+			const clone = node.cloneNode();
+			expect(clone.data).toBe(node.data);
+		});
+	});
+});