@tkeron/html-parser 0.1.4 → 0.1.7
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/README.md +6 -6
- package/bun.lock +3 -3
- package/index.ts +0 -5
- package/package.json +7 -6
- package/src/css-selector.ts +45 -32
- package/src/dom-simulator.ts +243 -46
- package/src/parser.ts +0 -39
- package/src/tokenizer.ts +0 -116
- package/tests/advanced.test.ts +2 -2
- package/tests/cloneNode.test.ts +50 -50
- package/tests/custom-elements.test.ts +8 -8
- package/tests/dom-manipulation.test.ts +638 -0
- package/tests/official/acid/acid-tests.test.ts +6 -6
- package/tests/official/final-output/final-output.test.ts +15 -15
- package/tests/official/html5lib/tokenizer-utils.ts +19 -31
- package/tests/official/html5lib/tokenizer.test.ts +4 -4
- package/tests/official/html5lib/tree-construction-utils.ts +20 -34
- package/tests/official/html5lib/tree-construction.test.ts +5 -5
- package/tests/official/validator/validator-tests.test.ts +11 -11
- package/tests/official/wpt/wpt-tests.test.ts +5 -5
- package/tests/outerHTML-replacement.test.ts +208 -0
- package/tests/parser.test.ts +1 -1
- package/tests/selectors.test.ts +64 -1
- package/tests/test-page-0.txt +12 -355
- package/tests/tokenizer.test.ts +86 -0
- package/tests/void-elements.test.ts +471 -0
- package/tests/api-integration.test.ts +0 -114
- package/tests/cloneNode-bug-reproduction.test.ts +0 -325
- package/tests/cloneNode-interactive.ts +0 -235
- package/tests/dom-adoption.test.ts +0 -363
- package/tests/dom-synchronization.test.ts +0 -675
- package/tests/setAttribute-outerHTML.test.ts +0 -102
|
@@ -686,3 +686,641 @@ describe("DOM Manipulation - insertAfter", () => {
|
|
|
686
686
|
});
|
|
687
687
|
});
|
|
688
688
|
});
|
|
689
|
+
|
|
690
|
+
describe("DOM Manipulation - prepend", () => {
|
|
691
|
+
describe("Basic prepend functionality", () => {
|
|
692
|
+
it("should prepend a single node to an element with children", () => {
|
|
693
|
+
const doc = parseHTML("<div><span>Second</span><span>Third</span></div>");
|
|
694
|
+
const div = doc.querySelector("div");
|
|
695
|
+
const secondSpan = div.childNodes[0];
|
|
696
|
+
|
|
697
|
+
const newSpan = doc.createElement("span");
|
|
698
|
+
newSpan.textContent = "First";
|
|
699
|
+
|
|
700
|
+
div.prepend(newSpan);
|
|
701
|
+
|
|
702
|
+
expect(div.childNodes.length).toBe(3);
|
|
703
|
+
expect(div.childNodes[0]).toBe(newSpan);
|
|
704
|
+
expect(div.childNodes[1]).toBe(secondSpan);
|
|
705
|
+
expect(div.firstChild).toBe(newSpan);
|
|
706
|
+
});
|
|
707
|
+
|
|
708
|
+
it("should prepend a node to an empty element", () => {
|
|
709
|
+
const doc = parseHTML("<div></div>");
|
|
710
|
+
const div = doc.querySelector("div");
|
|
711
|
+
|
|
712
|
+
const span = doc.createElement("span");
|
|
713
|
+
span.textContent = "First";
|
|
714
|
+
|
|
715
|
+
div.prepend(span);
|
|
716
|
+
|
|
717
|
+
expect(div.childNodes.length).toBe(1);
|
|
718
|
+
expect(div.childNodes[0]).toBe(span);
|
|
719
|
+
expect(div.firstChild).toBe(span);
|
|
720
|
+
expect(div.lastChild).toBe(span);
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
it("should prepend multiple nodes", () => {
|
|
724
|
+
const doc = parseHTML("<div><span>Third</span></div>");
|
|
725
|
+
const div = doc.querySelector("div");
|
|
726
|
+
|
|
727
|
+
const first = doc.createElement("span");
|
|
728
|
+
first.textContent = "First";
|
|
729
|
+
const second = doc.createElement("span");
|
|
730
|
+
second.textContent = "Second";
|
|
731
|
+
|
|
732
|
+
div.prepend(first, second);
|
|
733
|
+
|
|
734
|
+
expect(div.childNodes.length).toBe(3);
|
|
735
|
+
expect(div.childNodes[0]).toBe(first);
|
|
736
|
+
expect(div.childNodes[1]).toBe(second);
|
|
737
|
+
expect(div.childNodes[2].textContent).toBe("Third");
|
|
738
|
+
expect(div.firstChild).toBe(first);
|
|
739
|
+
});
|
|
740
|
+
|
|
741
|
+
it("should prepend with string argument", () => {
|
|
742
|
+
const doc = parseHTML("<div><span>Element</span></div>");
|
|
743
|
+
const div = doc.querySelector("div");
|
|
744
|
+
|
|
745
|
+
div.prepend("Text before ");
|
|
746
|
+
|
|
747
|
+
expect(div.childNodes.length).toBe(2);
|
|
748
|
+
expect(div.childNodes[0].nodeType).toBe(NodeType.TEXT_NODE);
|
|
749
|
+
expect(div.childNodes[0].textContent).toBe("Text before ");
|
|
750
|
+
expect(div.firstChild.nodeValue).toBe("Text before ");
|
|
751
|
+
});
|
|
752
|
+
|
|
753
|
+
it("should prepend mixed nodes and strings", () => {
|
|
754
|
+
const doc = parseHTML("<div><span>Last</span></div>");
|
|
755
|
+
const div = doc.querySelector("div");
|
|
756
|
+
|
|
757
|
+
const span = doc.createElement("span");
|
|
758
|
+
span.textContent = "Second";
|
|
759
|
+
|
|
760
|
+
div.prepend("First ", span);
|
|
761
|
+
|
|
762
|
+
expect(div.childNodes.length).toBe(3);
|
|
763
|
+
expect(div.childNodes[0].nodeType).toBe(NodeType.TEXT_NODE);
|
|
764
|
+
expect(div.childNodes[0].textContent).toBe("First ");
|
|
765
|
+
expect(div.childNodes[1]).toBe(span);
|
|
766
|
+
expect(div.childNodes[2].textContent).toBe("Last");
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
it("should do nothing when called with no arguments", () => {
|
|
770
|
+
const doc = parseHTML("<div><span>Only</span></div>");
|
|
771
|
+
const div = doc.querySelector("div");
|
|
772
|
+
const originalLength = div.childNodes.length;
|
|
773
|
+
|
|
774
|
+
div.prepend();
|
|
775
|
+
|
|
776
|
+
expect(div.childNodes.length).toBe(originalLength);
|
|
777
|
+
});
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
describe("prepend with text nodes", () => {
|
|
781
|
+
it("should prepend text node before elements", () => {
|
|
782
|
+
const doc = parseHTML("<div><span>Element</span></div>");
|
|
783
|
+
const div = doc.querySelector("div");
|
|
784
|
+
const span = div.childNodes[0];
|
|
785
|
+
|
|
786
|
+
const textNode = doc.createTextNode("Text ");
|
|
787
|
+
|
|
788
|
+
div.prepend(textNode);
|
|
789
|
+
|
|
790
|
+
expect(div.childNodes.length).toBe(2);
|
|
791
|
+
expect(div.childNodes[0]).toBe(textNode);
|
|
792
|
+
expect(div.childNodes[1]).toBe(span);
|
|
793
|
+
expect(div.firstChild).toBe(textNode);
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
describe("prepend sibling relationships", () => {
|
|
798
|
+
it("should update nextSibling and previousSibling correctly", () => {
|
|
799
|
+
const doc = parseHTML("<div><span>B</span><span>C</span></div>");
|
|
800
|
+
const div = doc.querySelector("div");
|
|
801
|
+
const spanB = div.childNodes[0];
|
|
802
|
+
|
|
803
|
+
const spanA = doc.createElement("span");
|
|
804
|
+
spanA.textContent = "A";
|
|
805
|
+
|
|
806
|
+
div.prepend(spanA);
|
|
807
|
+
|
|
808
|
+
expect(spanA.nextSibling).toBe(spanB);
|
|
809
|
+
expect(spanA.previousSibling).toBeNull();
|
|
810
|
+
expect(spanB.previousSibling).toBe(spanA);
|
|
811
|
+
expect(div.firstChild).toBe(spanA);
|
|
812
|
+
});
|
|
813
|
+
|
|
814
|
+
it("should update element sibling relationships", () => {
|
|
815
|
+
const doc = parseHTML("<div><span>B</span></div>");
|
|
816
|
+
const div = doc.querySelector("div");
|
|
817
|
+
const spanB = div.childNodes[0];
|
|
818
|
+
|
|
819
|
+
const spanA = doc.createElement("span");
|
|
820
|
+
spanA.textContent = "A";
|
|
821
|
+
|
|
822
|
+
div.prepend(spanA);
|
|
823
|
+
|
|
824
|
+
expect(spanA.nextElementSibling).toBe(spanB);
|
|
825
|
+
expect(spanA.previousElementSibling).toBeNull();
|
|
826
|
+
expect(spanB.previousElementSibling).toBe(spanA);
|
|
827
|
+
expect(div.firstElementChild).toBe(spanA);
|
|
828
|
+
});
|
|
829
|
+
});
|
|
830
|
+
|
|
831
|
+
describe("prepend with parent relocation", () => {
|
|
832
|
+
it("should move node from another parent when prepending", () => {
|
|
833
|
+
const doc = parseHTML("<div id='a'><span>Child</span></div><div id='b'><span>Other</span></div>");
|
|
834
|
+
const divA = doc.querySelector("#a");
|
|
835
|
+
const divB = doc.querySelector("#b");
|
|
836
|
+
const child = divA.querySelector("span");
|
|
837
|
+
|
|
838
|
+
divB.prepend(child);
|
|
839
|
+
|
|
840
|
+
expect(divA.childNodes.length).toBe(0);
|
|
841
|
+
expect(divB.childNodes.length).toBe(2);
|
|
842
|
+
expect(divB.firstChild).toBe(child);
|
|
843
|
+
expect(child.parentNode).toBe(divB);
|
|
844
|
+
});
|
|
845
|
+
|
|
846
|
+
it("should remove from old parent before prepending", () => {
|
|
847
|
+
const doc = parseHTML("<div id='a'><span id='1'>1</span><span id='2'>2</span></div><div id='b'></div>");
|
|
848
|
+
const divA = doc.querySelector("#a");
|
|
849
|
+
const divB = doc.querySelector("#b");
|
|
850
|
+
const span1 = doc.querySelector("#1");
|
|
851
|
+
|
|
852
|
+
divB.prepend(span1);
|
|
853
|
+
|
|
854
|
+
expect(divA.childNodes.length).toBe(1);
|
|
855
|
+
expect(divA.childNodes[0].textContent).toBe("2");
|
|
856
|
+
expect(divB.childNodes.length).toBe(1);
|
|
857
|
+
expect(divB.firstChild).toBe(span1);
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
|
|
861
|
+
describe("prepend synchronization", () => {
|
|
862
|
+
it("should update innerHTML correctly", () => {
|
|
863
|
+
const doc = parseHTML("<div><span>B</span><span>C</span></div>");
|
|
864
|
+
const div = doc.querySelector("div");
|
|
865
|
+
|
|
866
|
+
const spanA = doc.createElement("span");
|
|
867
|
+
spanA.textContent = "A";
|
|
868
|
+
|
|
869
|
+
div.prepend(spanA);
|
|
870
|
+
|
|
871
|
+
expect(div.innerHTML).toBe("<span>A</span><span>B</span><span>C</span>");
|
|
872
|
+
});
|
|
873
|
+
|
|
874
|
+
it("should update innerHTML with multiple prepends", () => {
|
|
875
|
+
const doc = parseHTML("<div><span>C</span></div>");
|
|
876
|
+
const div = doc.querySelector("div");
|
|
877
|
+
|
|
878
|
+
const spanA = doc.createElement("span");
|
|
879
|
+
spanA.textContent = "A";
|
|
880
|
+
const spanB = doc.createElement("span");
|
|
881
|
+
spanB.textContent = "B";
|
|
882
|
+
|
|
883
|
+
div.prepend(spanA, spanB);
|
|
884
|
+
|
|
885
|
+
expect(div.innerHTML).toBe("<span>A</span><span>B</span><span>C</span>");
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
it("should update textContent correctly", () => {
|
|
889
|
+
const doc = parseHTML("<div><span>Second</span></div>");
|
|
890
|
+
const div = doc.querySelector("div");
|
|
891
|
+
|
|
892
|
+
div.prepend("First ");
|
|
893
|
+
|
|
894
|
+
expect(div.textContent).toBe("First Second");
|
|
895
|
+
});
|
|
896
|
+
});
|
|
897
|
+
|
|
898
|
+
describe("prepend on document", () => {
|
|
899
|
+
it("should prepend to document", () => {
|
|
900
|
+
const doc = parseHTML("<!-- Comment --><div>Content</div>");
|
|
901
|
+
const comment = doc.childNodes[0];
|
|
902
|
+
const div = doc.childNodes[1];
|
|
903
|
+
|
|
904
|
+
const newDiv = doc.createElement("div");
|
|
905
|
+
newDiv.textContent = "First";
|
|
906
|
+
|
|
907
|
+
doc.prepend(newDiv);
|
|
908
|
+
|
|
909
|
+
expect(doc.firstChild).toBe(newDiv);
|
|
910
|
+
expect(newDiv.nextSibling).toBe(comment);
|
|
911
|
+
expect(doc.childNodes.length).toBe(3);
|
|
912
|
+
});
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
|
|
916
|
+
describe("DOM Manipulation - append", () => {
|
|
917
|
+
describe("Basic append functionality", () => {
|
|
918
|
+
it("should append a single node to an element with children", () => {
|
|
919
|
+
const doc = parseHTML("<div><span>First</span><span>Second</span></div>");
|
|
920
|
+
const div = doc.querySelector("div");
|
|
921
|
+
const secondSpan = div.childNodes[1];
|
|
922
|
+
|
|
923
|
+
const newSpan = doc.createElement("span");
|
|
924
|
+
newSpan.textContent = "Third";
|
|
925
|
+
|
|
926
|
+
div.append(newSpan);
|
|
927
|
+
|
|
928
|
+
expect(div.childNodes.length).toBe(3);
|
|
929
|
+
expect(div.childNodes[2]).toBe(newSpan);
|
|
930
|
+
expect(div.lastChild).toBe(newSpan);
|
|
931
|
+
expect(secondSpan.nextSibling).toBe(newSpan);
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
it("should append a node to an empty element", () => {
|
|
935
|
+
const doc = parseHTML("<div></div>");
|
|
936
|
+
const div = doc.querySelector("div");
|
|
937
|
+
|
|
938
|
+
const span = doc.createElement("span");
|
|
939
|
+
span.textContent = "First";
|
|
940
|
+
|
|
941
|
+
div.append(span);
|
|
942
|
+
|
|
943
|
+
expect(div.childNodes.length).toBe(1);
|
|
944
|
+
expect(div.childNodes[0]).toBe(span);
|
|
945
|
+
expect(div.firstChild).toBe(span);
|
|
946
|
+
expect(div.lastChild).toBe(span);
|
|
947
|
+
});
|
|
948
|
+
|
|
949
|
+
it("should append multiple nodes", () => {
|
|
950
|
+
const doc = parseHTML("<div><span>First</span></div>");
|
|
951
|
+
const div = doc.querySelector("div");
|
|
952
|
+
|
|
953
|
+
const second = doc.createElement("span");
|
|
954
|
+
second.textContent = "Second";
|
|
955
|
+
const third = doc.createElement("span");
|
|
956
|
+
third.textContent = "Third";
|
|
957
|
+
|
|
958
|
+
div.append(second, third);
|
|
959
|
+
|
|
960
|
+
expect(div.childNodes.length).toBe(3);
|
|
961
|
+
expect(div.childNodes[1]).toBe(second);
|
|
962
|
+
expect(div.childNodes[2]).toBe(third);
|
|
963
|
+
expect(div.lastChild).toBe(third);
|
|
964
|
+
});
|
|
965
|
+
|
|
966
|
+
it("should append with string argument", () => {
|
|
967
|
+
const doc = parseHTML("<div><span>Element</span></div>");
|
|
968
|
+
const div = doc.querySelector("div");
|
|
969
|
+
|
|
970
|
+
div.append(" text after");
|
|
971
|
+
|
|
972
|
+
expect(div.childNodes.length).toBe(2);
|
|
973
|
+
expect(div.childNodes[1].nodeType).toBe(NodeType.TEXT_NODE);
|
|
974
|
+
expect(div.childNodes[1].textContent).toBe(" text after");
|
|
975
|
+
expect(div.lastChild.nodeValue).toBe(" text after");
|
|
976
|
+
});
|
|
977
|
+
|
|
978
|
+
it("should append mixed nodes and strings", () => {
|
|
979
|
+
const doc = parseHTML("<div><span>First</span></div>");
|
|
980
|
+
const div = doc.querySelector("div");
|
|
981
|
+
|
|
982
|
+
const span = doc.createElement("span");
|
|
983
|
+
span.textContent = "Second";
|
|
984
|
+
|
|
985
|
+
div.append(span, " and text");
|
|
986
|
+
|
|
987
|
+
expect(div.childNodes.length).toBe(3);
|
|
988
|
+
expect(div.childNodes[1]).toBe(span);
|
|
989
|
+
expect(div.childNodes[2].nodeType).toBe(NodeType.TEXT_NODE);
|
|
990
|
+
expect(div.childNodes[2].textContent).toBe(" and text");
|
|
991
|
+
});
|
|
992
|
+
|
|
993
|
+
it("should do nothing when called with no arguments", () => {
|
|
994
|
+
const doc = parseHTML("<div><span>Only</span></div>");
|
|
995
|
+
const div = doc.querySelector("div");
|
|
996
|
+
const originalLength = div.childNodes.length;
|
|
997
|
+
|
|
998
|
+
div.append();
|
|
999
|
+
|
|
1000
|
+
expect(div.childNodes.length).toBe(originalLength);
|
|
1001
|
+
});
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
describe("append with text nodes", () => {
|
|
1005
|
+
it("should append text node after elements", () => {
|
|
1006
|
+
const doc = parseHTML("<div><span>Element</span></div>");
|
|
1007
|
+
const div = doc.querySelector("div");
|
|
1008
|
+
const span = div.childNodes[0];
|
|
1009
|
+
|
|
1010
|
+
const textNode = doc.createTextNode(" Text");
|
|
1011
|
+
|
|
1012
|
+
div.append(textNode);
|
|
1013
|
+
|
|
1014
|
+
expect(div.childNodes.length).toBe(2);
|
|
1015
|
+
expect(div.childNodes[1]).toBe(textNode);
|
|
1016
|
+
expect(div.lastChild).toBe(textNode);
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
|
|
1020
|
+
describe("append sibling relationships", () => {
|
|
1021
|
+
it("should update nextSibling and previousSibling correctly", () => {
|
|
1022
|
+
const doc = parseHTML("<div><span>A</span><span>B</span></div>");
|
|
1023
|
+
const div = doc.querySelector("div");
|
|
1024
|
+
const spanB = div.childNodes[1];
|
|
1025
|
+
|
|
1026
|
+
const spanC = doc.createElement("span");
|
|
1027
|
+
spanC.textContent = "C";
|
|
1028
|
+
|
|
1029
|
+
div.append(spanC);
|
|
1030
|
+
|
|
1031
|
+
expect(spanB.nextSibling).toBe(spanC);
|
|
1032
|
+
expect(spanC.previousSibling).toBe(spanB);
|
|
1033
|
+
expect(spanC.nextSibling).toBeNull();
|
|
1034
|
+
expect(div.lastChild).toBe(spanC);
|
|
1035
|
+
});
|
|
1036
|
+
|
|
1037
|
+
it("should update element sibling relationships", () => {
|
|
1038
|
+
const doc = parseHTML("<div><span>A</span></div>");
|
|
1039
|
+
const div = doc.querySelector("div");
|
|
1040
|
+
const spanA = div.childNodes[0];
|
|
1041
|
+
|
|
1042
|
+
const spanB = doc.createElement("span");
|
|
1043
|
+
spanB.textContent = "B";
|
|
1044
|
+
|
|
1045
|
+
div.append(spanB);
|
|
1046
|
+
|
|
1047
|
+
expect(spanA.nextElementSibling).toBe(spanB);
|
|
1048
|
+
expect(spanB.previousElementSibling).toBe(spanA);
|
|
1049
|
+
expect(spanB.nextElementSibling).toBeNull();
|
|
1050
|
+
expect(div.lastElementChild).toBe(spanB);
|
|
1051
|
+
});
|
|
1052
|
+
});
|
|
1053
|
+
|
|
1054
|
+
describe("append with parent relocation", () => {
|
|
1055
|
+
it("should move node from another parent when appending", () => {
|
|
1056
|
+
const doc = parseHTML("<div id='a'><span>Child</span></div><div id='b'><span>Other</span></div>");
|
|
1057
|
+
const divA = doc.querySelector("#a");
|
|
1058
|
+
const divB = doc.querySelector("#b");
|
|
1059
|
+
const child = divA.querySelector("span");
|
|
1060
|
+
|
|
1061
|
+
divB.append(child);
|
|
1062
|
+
|
|
1063
|
+
expect(divA.childNodes.length).toBe(0);
|
|
1064
|
+
expect(divB.childNodes.length).toBe(2);
|
|
1065
|
+
expect(divB.lastChild).toBe(child);
|
|
1066
|
+
expect(child.parentNode).toBe(divB);
|
|
1067
|
+
});
|
|
1068
|
+
|
|
1069
|
+
it("should remove from old parent before appending", () => {
|
|
1070
|
+
const doc = parseHTML("<div id='a'><span id='1'>1</span><span id='2'>2</span></div><div id='b'></div>");
|
|
1071
|
+
const divA = doc.querySelector("#a");
|
|
1072
|
+
const divB = doc.querySelector("#b");
|
|
1073
|
+
const span2 = doc.querySelector("#2");
|
|
1074
|
+
|
|
1075
|
+
divB.append(span2);
|
|
1076
|
+
|
|
1077
|
+
expect(divA.childNodes.length).toBe(1);
|
|
1078
|
+
expect(divA.childNodes[0].textContent).toBe("1");
|
|
1079
|
+
expect(divB.childNodes.length).toBe(1);
|
|
1080
|
+
expect(divB.lastChild).toBe(span2);
|
|
1081
|
+
});
|
|
1082
|
+
});
|
|
1083
|
+
|
|
1084
|
+
describe("append synchronization", () => {
|
|
1085
|
+
it("should update innerHTML correctly", () => {
|
|
1086
|
+
const doc = parseHTML("<div><span>A</span><span>B</span></div>");
|
|
1087
|
+
const div = doc.querySelector("div");
|
|
1088
|
+
|
|
1089
|
+
const spanC = doc.createElement("span");
|
|
1090
|
+
spanC.textContent = "C";
|
|
1091
|
+
|
|
1092
|
+
div.append(spanC);
|
|
1093
|
+
|
|
1094
|
+
expect(div.innerHTML).toBe("<span>A</span><span>B</span><span>C</span>");
|
|
1095
|
+
});
|
|
1096
|
+
|
|
1097
|
+
it("should update innerHTML with multiple appends", () => {
|
|
1098
|
+
const doc = parseHTML("<div><span>A</span></div>");
|
|
1099
|
+
const div = doc.querySelector("div");
|
|
1100
|
+
|
|
1101
|
+
const spanB = doc.createElement("span");
|
|
1102
|
+
spanB.textContent = "B";
|
|
1103
|
+
const spanC = doc.createElement("span");
|
|
1104
|
+
spanC.textContent = "C";
|
|
1105
|
+
|
|
1106
|
+
div.append(spanB, spanC);
|
|
1107
|
+
|
|
1108
|
+
expect(div.innerHTML).toBe("<span>A</span><span>B</span><span>C</span>");
|
|
1109
|
+
});
|
|
1110
|
+
|
|
1111
|
+
it("should update textContent correctly", () => {
|
|
1112
|
+
const doc = parseHTML("<div><span>First</span></div>");
|
|
1113
|
+
const div = doc.querySelector("div");
|
|
1114
|
+
|
|
1115
|
+
div.append(" Second");
|
|
1116
|
+
|
|
1117
|
+
expect(div.textContent).toBe("First Second");
|
|
1118
|
+
});
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
describe("append on document", () => {
|
|
1122
|
+
it("should append to document", () => {
|
|
1123
|
+
const doc = parseHTML("<div>Content</div>");
|
|
1124
|
+
const div = doc.childNodes[0];
|
|
1125
|
+
|
|
1126
|
+
const newDiv = doc.createElement("div");
|
|
1127
|
+
newDiv.textContent = "Last";
|
|
1128
|
+
|
|
1129
|
+
doc.append(newDiv);
|
|
1130
|
+
|
|
1131
|
+
expect(doc.lastChild).toBe(newDiv);
|
|
1132
|
+
expect(div.nextSibling).toBe(newDiv);
|
|
1133
|
+
expect(doc.childNodes.length).toBe(2);
|
|
1134
|
+
});
|
|
1135
|
+
});
|
|
1136
|
+
});
|
|
1137
|
+
|
|
1138
|
+
describe("DOM Manipulation - remove", () => {
|
|
1139
|
+
describe("Basic remove functionality", () => {
|
|
1140
|
+
it("should remove an element from its parent", () => {
|
|
1141
|
+
const doc = parseHTML("<div><span id='1'>First</span><span id='2'>Second</span></div>");
|
|
1142
|
+
const div = doc.querySelector("div");
|
|
1143
|
+
const span1 = doc.querySelector("#1");
|
|
1144
|
+
|
|
1145
|
+
span1.remove();
|
|
1146
|
+
|
|
1147
|
+
expect(div.childNodes.length).toBe(1);
|
|
1148
|
+
expect(div.childNodes[0].textContent).toBe("Second");
|
|
1149
|
+
expect(span1.parentNode).toBeNull();
|
|
1150
|
+
});
|
|
1151
|
+
|
|
1152
|
+
it("should remove the only child", () => {
|
|
1153
|
+
const doc = parseHTML("<div><span>Only</span></div>");
|
|
1154
|
+
const div = doc.querySelector("div");
|
|
1155
|
+
const span = div.querySelector("span");
|
|
1156
|
+
|
|
1157
|
+
span.remove();
|
|
1158
|
+
|
|
1159
|
+
expect(div.childNodes.length).toBe(0);
|
|
1160
|
+
expect(div.firstChild).toBeNull();
|
|
1161
|
+
expect(div.lastChild).toBeNull();
|
|
1162
|
+
expect(span.parentNode).toBeNull();
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
it("should do nothing when element has no parent", () => {
|
|
1166
|
+
const doc = parseHTML("<div></div>");
|
|
1167
|
+
const orphan = doc.createElement("span");
|
|
1168
|
+
|
|
1169
|
+
expect(() => orphan.remove()).not.toThrow();
|
|
1170
|
+
expect(orphan.parentNode).toBeNull();
|
|
1171
|
+
});
|
|
1172
|
+
|
|
1173
|
+
it("should remove first child", () => {
|
|
1174
|
+
const doc = parseHTML("<div><span>First</span><span>Second</span><span>Third</span></div>");
|
|
1175
|
+
const div = doc.querySelector("div");
|
|
1176
|
+
const first = div.childNodes[0];
|
|
1177
|
+
|
|
1178
|
+
first.remove();
|
|
1179
|
+
|
|
1180
|
+
expect(div.childNodes.length).toBe(2);
|
|
1181
|
+
expect(div.firstChild.textContent).toBe("Second");
|
|
1182
|
+
expect(div.childNodes[0].previousSibling).toBeNull();
|
|
1183
|
+
});
|
|
1184
|
+
|
|
1185
|
+
it("should remove last child", () => {
|
|
1186
|
+
const doc = parseHTML("<div><span>First</span><span>Second</span><span>Third</span></div>");
|
|
1187
|
+
const div = doc.querySelector("div");
|
|
1188
|
+
const last = div.childNodes[2];
|
|
1189
|
+
|
|
1190
|
+
last.remove();
|
|
1191
|
+
|
|
1192
|
+
expect(div.childNodes.length).toBe(2);
|
|
1193
|
+
expect(div.lastChild.textContent).toBe("Second");
|
|
1194
|
+
expect(div.childNodes[1].nextSibling).toBeNull();
|
|
1195
|
+
});
|
|
1196
|
+
|
|
1197
|
+
it("should remove middle child", () => {
|
|
1198
|
+
const doc = parseHTML("<div><span>First</span><span>Second</span><span>Third</span></div>");
|
|
1199
|
+
const div = doc.querySelector("div");
|
|
1200
|
+
const middle = div.childNodes[1];
|
|
1201
|
+
|
|
1202
|
+
middle.remove();
|
|
1203
|
+
|
|
1204
|
+
expect(div.childNodes.length).toBe(2);
|
|
1205
|
+
expect(div.childNodes[0].textContent).toBe("First");
|
|
1206
|
+
expect(div.childNodes[1].textContent).toBe("Third");
|
|
1207
|
+
});
|
|
1208
|
+
});
|
|
1209
|
+
|
|
1210
|
+
describe("remove sibling relationships", () => {
|
|
1211
|
+
it("should update nextSibling and previousSibling correctly", () => {
|
|
1212
|
+
const doc = parseHTML("<div><span>A</span><span>B</span><span>C</span></div>");
|
|
1213
|
+
const div = doc.querySelector("div");
|
|
1214
|
+
const spanA = div.childNodes[0];
|
|
1215
|
+
const spanB = div.childNodes[1];
|
|
1216
|
+
const spanC = div.childNodes[2];
|
|
1217
|
+
|
|
1218
|
+
spanB.remove();
|
|
1219
|
+
|
|
1220
|
+
expect(spanA.nextSibling).toBe(spanC);
|
|
1221
|
+
expect(spanC.previousSibling).toBe(spanA);
|
|
1222
|
+
expect(spanB.nextSibling).toBeNull();
|
|
1223
|
+
expect(spanB.previousSibling).toBeNull();
|
|
1224
|
+
});
|
|
1225
|
+
|
|
1226
|
+
it("should update element sibling relationships", () => {
|
|
1227
|
+
const doc = parseHTML("<div><span>A</span><span>B</span><span>C</span></div>");
|
|
1228
|
+
const div = doc.querySelector("div");
|
|
1229
|
+
const spanA = div.childNodes[0];
|
|
1230
|
+
const spanB = div.childNodes[1];
|
|
1231
|
+
const spanC = div.childNodes[2];
|
|
1232
|
+
|
|
1233
|
+
spanB.remove();
|
|
1234
|
+
|
|
1235
|
+
expect(spanA.nextElementSibling).toBe(spanC);
|
|
1236
|
+
expect(spanC.previousElementSibling).toBe(spanA);
|
|
1237
|
+
expect(spanB.nextElementSibling).toBeNull();
|
|
1238
|
+
expect(spanB.previousElementSibling).toBeNull();
|
|
1239
|
+
});
|
|
1240
|
+
|
|
1241
|
+
it("should update parent references", () => {
|
|
1242
|
+
const doc = parseHTML("<div><span>Child</span></div>");
|
|
1243
|
+
const div = doc.querySelector("div");
|
|
1244
|
+
const span = div.querySelector("span");
|
|
1245
|
+
|
|
1246
|
+
span.remove();
|
|
1247
|
+
|
|
1248
|
+
expect(span.parentNode).toBeNull();
|
|
1249
|
+
expect(span.parentElement).toBeNull();
|
|
1250
|
+
});
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
describe("remove text nodes", () => {
|
|
1254
|
+
it("should remove text node", () => {
|
|
1255
|
+
const doc = parseHTML("<div>Text content</div>");
|
|
1256
|
+
const div = doc.querySelector("div");
|
|
1257
|
+
const textNode = div.childNodes[0];
|
|
1258
|
+
|
|
1259
|
+
textNode.remove();
|
|
1260
|
+
|
|
1261
|
+
expect(div.childNodes.length).toBe(0);
|
|
1262
|
+
expect(div.textContent).toBe("");
|
|
1263
|
+
expect(textNode.parentNode).toBeNull();
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
it("should remove text node between elements", () => {
|
|
1267
|
+
const doc = parseHTML("<div><span>A</span> text <span>B</span></div>");
|
|
1268
|
+
const div = doc.querySelector("div");
|
|
1269
|
+
const textNode = div.childNodes[1];
|
|
1270
|
+
|
|
1271
|
+
textNode.remove();
|
|
1272
|
+
|
|
1273
|
+
expect(div.childNodes.length).toBe(2);
|
|
1274
|
+
expect(div.childNodes[0].nextSibling).toBe(div.childNodes[1]);
|
|
1275
|
+
});
|
|
1276
|
+
});
|
|
1277
|
+
|
|
1278
|
+
describe("remove synchronization", () => {
|
|
1279
|
+
it("should update innerHTML correctly", () => {
|
|
1280
|
+
const doc = parseHTML("<div><span>A</span><span>B</span><span>C</span></div>");
|
|
1281
|
+
const div = doc.querySelector("div");
|
|
1282
|
+
const spanB = div.childNodes[1];
|
|
1283
|
+
|
|
1284
|
+
spanB.remove();
|
|
1285
|
+
|
|
1286
|
+
expect(div.innerHTML).toBe("<span>A</span><span>C</span>");
|
|
1287
|
+
});
|
|
1288
|
+
|
|
1289
|
+
it("should update textContent correctly", () => {
|
|
1290
|
+
const doc = parseHTML("<div><span>First</span><span>Second</span></div>");
|
|
1291
|
+
const div = doc.querySelector("div");
|
|
1292
|
+
const second = div.childNodes[1];
|
|
1293
|
+
|
|
1294
|
+
second.remove();
|
|
1295
|
+
|
|
1296
|
+
expect(div.textContent).toBe("First");
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
it("should update children array correctly", () => {
|
|
1300
|
+
const doc = parseHTML("<div><span>A</span><span>B</span><span>C</span></div>");
|
|
1301
|
+
const div = doc.querySelector("div");
|
|
1302
|
+
const spanB = div.children[1];
|
|
1303
|
+
|
|
1304
|
+
spanB.remove();
|
|
1305
|
+
|
|
1306
|
+
expect(div.children.length).toBe(2);
|
|
1307
|
+
expect(div.children[0].textContent).toBe("A");
|
|
1308
|
+
expect(div.children[1].textContent).toBe("C");
|
|
1309
|
+
});
|
|
1310
|
+
});
|
|
1311
|
+
|
|
1312
|
+
describe("remove multiple elements", () => {
|
|
1313
|
+
it("should remove multiple elements sequentially", () => {
|
|
1314
|
+
const doc = parseHTML("<div><span>A</span><span>B</span><span>C</span></div>");
|
|
1315
|
+
const div = doc.querySelector("div");
|
|
1316
|
+
const spanA = div.childNodes[0];
|
|
1317
|
+
const spanB = div.childNodes[1];
|
|
1318
|
+
|
|
1319
|
+
spanA.remove();
|
|
1320
|
+
spanB.remove();
|
|
1321
|
+
|
|
1322
|
+
expect(div.childNodes.length).toBe(1);
|
|
1323
|
+
expect(div.childNodes[0].textContent).toBe("C");
|
|
1324
|
+
});
|
|
1325
|
+
});
|
|
1326
|
+
});
|
|
@@ -233,7 +233,7 @@ describe('Performance Benchmarks', () => {
|
|
|
233
233
|
const end = performance.now();
|
|
234
234
|
|
|
235
235
|
expect(ast).toBeDefined();
|
|
236
|
-
expect(end - start).toBeLessThan(10);
|
|
236
|
+
expect(end - start).toBeLessThan(10);
|
|
237
237
|
});
|
|
238
238
|
|
|
239
239
|
it('should handle medium-sized HTML', () => {
|
|
@@ -245,7 +245,7 @@ describe('Performance Benchmarks', () => {
|
|
|
245
245
|
const end = performance.now();
|
|
246
246
|
|
|
247
247
|
expect(ast).toBeDefined();
|
|
248
|
-
expect(end - start).toBeLessThan(100);
|
|
248
|
+
expect(end - start).toBeLessThan(100);
|
|
249
249
|
});
|
|
250
250
|
|
|
251
251
|
it('should handle large HTML documents', () => {
|
|
@@ -257,7 +257,7 @@ describe('Performance Benchmarks', () => {
|
|
|
257
257
|
const end = performance.now();
|
|
258
258
|
|
|
259
259
|
expect(ast).toBeDefined();
|
|
260
|
-
expect(end - start).toBeLessThan(1000);
|
|
260
|
+
expect(end - start).toBeLessThan(1000);
|
|
261
261
|
});
|
|
262
262
|
|
|
263
263
|
it('should handle deeply nested HTML', () => {
|
|
@@ -276,7 +276,7 @@ describe('Performance Benchmarks', () => {
|
|
|
276
276
|
const end = performance.now();
|
|
277
277
|
|
|
278
278
|
expect(ast).toBeDefined();
|
|
279
|
-
expect(end - start).toBeLessThan(500);
|
|
279
|
+
expect(end - start).toBeLessThan(500);
|
|
280
280
|
});
|
|
281
281
|
});
|
|
282
282
|
|
|
@@ -284,14 +284,14 @@ describe('Memory Usage Tests', () => {
|
|
|
284
284
|
it('should not leak memory on repeated parsing', () => {
|
|
285
285
|
const testHtml = '<div><p>Memory test</p></div>';
|
|
286
286
|
|
|
287
|
-
|
|
287
|
+
|
|
288
288
|
for (let i = 0; i < 1000; i++) {
|
|
289
289
|
const tokens = tokenize(testHtml);
|
|
290
290
|
const ast = parse(tokens);
|
|
291
291
|
expect(ast).toBeDefined();
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
-
|
|
294
|
+
|
|
295
295
|
expect(true).toBe(true);
|
|
296
296
|
});
|
|
297
297
|
|