From 24c295493d8f070e03c2cbbe3397a120e1e5d3bf Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:16:55 +0000 Subject: [PATCH 1/4] Initial plan From d9f646cf70fdb711e24abdfa2af40b27db5d4329 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:25:36 +0000 Subject: [PATCH 2/4] Add comprehensive tests for CssCharStream, Property, DescendantSelector, PseudoClassCondition, and MediaQuery Co-authored-by: rbri <2544132+rbri@users.noreply.github.com> --- .../htmlunit/cssparser/dom/PropteryTest.java | 79 ++++ .../cssparser/parser/CssCharStreamTest.java | 385 ++++++++++++++++++ .../condition/PseudoClassConditionTest.java | 60 +++ .../parser/media/MediaQueryTest.java | 140 +++++++ .../selector/DescendantSelectorImplTest.java | 60 +++ 5 files changed, 724 insertions(+) create mode 100644 src/test/java/org/htmlunit/cssparser/parser/CssCharStreamTest.java diff --git a/src/test/java/org/htmlunit/cssparser/dom/PropteryTest.java b/src/test/java/org/htmlunit/cssparser/dom/PropteryTest.java index f321ac0..c261f78 100644 --- a/src/test/java/org/htmlunit/cssparser/dom/PropteryTest.java +++ b/src/test/java/org/htmlunit/cssparser/dom/PropteryTest.java @@ -94,4 +94,83 @@ public void constructWithParams() throws Exception { assertEquals("13.2cm", prop.getValue().toString()); assertFalse(prop.isImportant()); } + + /** + * Test equals method with same object. + * @throws Exception if any error occurs + */ + @Test + public void equalsSameObject() throws Exception { + final LexicalUnit lu = LexicalUnitImpl.createPixel(null, 10); + final Property prop = new Property("color", new CSSValueImpl(lu, true), false); + + assertTrue(prop.equals(prop)); + } + + /** + * Test equals method with different names. + * @throws Exception if any error occurs + */ + @Test + public void equalsDifferentNames() throws Exception { + final LexicalUnit lu1 = LexicalUnitImpl.createPixel(null, 10); + final Property prop1 = new Property("color", new CSSValueImpl(lu1, true), false); + + final LexicalUnit lu2 = LexicalUnitImpl.createPixel(null, 10); + final Property prop2 = new Property("background", new CSSValueImpl(lu2, true), false); + + assertFalse(prop1.equals(prop2)); + } + + /** + * Test equals method with different important flags. + * @throws Exception if any error occurs + */ + @Test + public void equalsDifferentImportant() throws Exception { + final Property prop1 = new Property("color", null, false); + final Property prop2 = new Property("color", null, true); + + assertFalse(prop1.equals(prop2)); + } + + /** + * Test equals method with null. + * @throws Exception if any error occurs + */ + @Test + public void equalsWithNull() throws Exception { + final LexicalUnit lu = LexicalUnitImpl.createPixel(null, 10); + final Property prop = new Property("color", new CSSValueImpl(lu, true), false); + + assertFalse(prop.equals(null)); + } + + /** + * Test equals method with different class. + * @throws Exception if any error occurs + */ + @Test + public void equalsWithDifferentClass() throws Exception { + final LexicalUnit lu = LexicalUnitImpl.createPixel(null, 10); + final Property prop = new Property("color", new CSSValueImpl(lu, true), false); + + assertFalse(prop.equals("not a property")); + } + + /** + * Test hashCode consistency. + * @throws Exception if any error occurs + */ + @Test + public void hashCodeConsistency() throws Exception { + final LexicalUnit lu = LexicalUnitImpl.createPixel(null, 10); + final Property prop = new Property("color", new CSSValueImpl(lu, true), false); + + final int hash1 = prop.hashCode(); + final int hash2 = prop.hashCode(); + + assertEquals(hash1, hash2); + } } + diff --git a/src/test/java/org/htmlunit/cssparser/parser/CssCharStreamTest.java b/src/test/java/org/htmlunit/cssparser/parser/CssCharStreamTest.java new file mode 100644 index 0000000..bc7853a --- /dev/null +++ b/src/test/java/org/htmlunit/cssparser/parser/CssCharStreamTest.java @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2019-2024 Ronald Brill. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.htmlunit.cssparser.parser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.io.StringReader; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link CssCharStream}. + * + * @author Ronald Brill + */ +public class CssCharStreamTest { + + /** + * Test basic character reading. + * @throws Exception if any error occurs + */ + @Test + public void readChar() throws Exception { + final String input = "abc"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + assertEquals('a', stream.readChar()); + assertEquals('b', stream.readChar()); + assertEquals('c', stream.readChar()); + + assertThrows(IOException.class, () -> stream.readChar()); + } + + /** + * Test beginToken functionality. + * @throws Exception if any error occurs + */ + @Test + public void beginToken() throws Exception { + final String input = "test"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + final char firstChar = stream.beginToken(); + assertEquals('t', firstChar); + assertEquals('e', stream.readChar()); + } + + /** + * Test getImage for simple token. + * @throws Exception if any error occurs + */ + @Test + public void getImage() throws Exception { + final String input = "token"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + stream.beginToken(); // reads 't' + stream.readChar(); // 'o' + stream.readChar(); // 'k' + stream.readChar(); // 'e' + stream.readChar(); // 'n' + + assertEquals("token", stream.getImage()); + } + + /** + * Test backup functionality. + * @throws Exception if any error occurs + */ + @Test + public void backup() throws Exception { + final String input = "abcd"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + char c = stream.beginToken(); // reads 'a' + assertEquals('a', c); + assertEquals('b', stream.readChar()); + assertEquals('c', stream.readChar()); + + stream.backup(1); + assertEquals('c', stream.readChar()); + assertEquals('d', stream.readChar()); + } + + /** + * Test line and column tracking. + * @throws Exception if any error occurs + */ + @Test + public void lineColumnTracking() throws Exception { + final String input = "a\nb\nc"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + char c = stream.beginToken(); // reads 'a' + assertEquals('a', c); + assertEquals(1, stream.getBeginLine()); + assertEquals(1, stream.getBeginColumn()); + assertEquals(1, stream.getEndLine()); + assertEquals(1, stream.getEndColumn()); + + stream.readChar(); // '\n' + assertEquals(1, stream.getEndLine()); + assertEquals(2, stream.getEndColumn()); + + stream.readChar(); // 'b' + assertEquals(2, stream.getEndLine()); + assertEquals(1, stream.getEndColumn()); + } + + /** + * Test carriage return handling. + * @throws Exception if any error occurs + */ + @Test + public void carriageReturnHandling() throws Exception { + final String input = "a\rb\r\nc"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + stream.beginToken(); // 'a' + stream.readChar(); // '\r' + assertEquals(1, stream.getEndLine()); + + stream.readChar(); // 'b' + assertEquals(2, stream.getEndLine()); + + stream.readChar(); // '\r' + stream.readChar(); // '\n' + assertEquals(2, stream.getEndLine()); + + stream.readChar(); // 'c' + assertEquals(3, stream.getEndLine()); + } + + /** + * Test getSuffix functionality. + * @throws Exception if any error occurs + */ + @Test + public void getSuffix() throws Exception { + final String input = "testing"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + stream.beginToken(); // 't' + for (int i = 0; i < 6; i++) { + stream.readChar(); + } + + final char[] suffix = stream.getSuffix(3); + assertEquals(3, suffix.length); + assertEquals('i', suffix[0]); + assertEquals('n', suffix[1]); + assertEquals('g', suffix[2]); + } + + /** + * Test custom buffer size constructor. + * @throws Exception if any error occurs + */ + @Test + public void customBufferSize() throws Exception { + final String input = "test"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1, 10); + + char c = stream.beginToken(); // reads 't' + assertEquals('t', c); + assertEquals('e', stream.readChar()); + assertEquals('s', stream.readChar()); + } + + /** + * Test done() method. + * @throws Exception if any error occurs + */ + @Test + public void done() throws Exception { + final String input = "test"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + stream.beginToken(); + stream.readChar(); + + stream.done(); + // After done, internal buffers are cleared + } + + /** + * Test adjustBeginLineColumn. + * @throws Exception if any error occurs + */ + @Test + public void adjustBeginLineColumn() throws Exception { + final String input = "test\nline"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + stream.beginToken(); + stream.readChar(); + stream.readChar(); + + stream.adjustBeginLineColumn(5, 10); + assertEquals(5, stream.getBeginLine()); + assertEquals(10, stream.getBeginColumn()); + } + + /** + * Test tab size get/set. + * @throws Exception if any error occurs + */ + @Test + public void tabSize() throws Exception { + final String input = "test"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + assertEquals(1, stream.getTabSize()); + + stream.setTabSize(8); + assertEquals(8, stream.getTabSize()); + } + + /** + * Test trackLineColumn get/set. + * @throws Exception if any error occurs + */ + @Test + public void trackLineColumn() throws Exception { + final String input = "test"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + assertTrue(stream.isTrackLineColumn()); + + stream.setTrackLineColumn(false); + assertFalse(stream.isTrackLineColumn()); + } + + /** + * Test buffer expansion with large input. + * @throws Exception if any error occurs + */ + @Test + public void bufferExpansion() throws Exception { + final StringBuilder largeInput = new StringBuilder(); + for (int i = 0; i < 5000; i++) { + largeInput.append('x'); + } + + final CssCharStream stream = new CssCharStream(new StringReader(largeInput.toString()), 1, 1, 100); + + char c = stream.beginToken(); // reads first 'x' + assertEquals('x', c); + for (int i = 0; i < 4999; i++) { + assertEquals('x', stream.readChar()); + } + } + + /** + * Test empty input. + * @throws Exception if any error occurs + */ + @Test + public void emptyInput() throws Exception { + final CssCharStream stream = new CssCharStream(new StringReader(""), 1, 1); + + assertThrows(IOException.class, () -> stream.readChar()); + } + + /** + * Test getImage with wrap around buffer. + * @throws Exception if any error occurs + */ + @Test + public void getImageWrapAround() throws Exception { + final StringBuilder input = new StringBuilder(); + for (int i = 0; i < 3000; i++) { + input.append((char) ('a' + (i % 26))); + } + + final CssCharStream stream = new CssCharStream(new StringReader(input.toString()), 1, 1, 100); + + stream.beginToken(); // reads first char + for (int i = 0; i < 49; i++) { + stream.readChar(); + } + + final String image = stream.getImage(); + assertNotNull(image); + assertEquals(50, image.length()); + } + + /** + * Test backup with multiple characters. + * @throws Exception if any error occurs + */ + @Test + public void backupMultiple() throws Exception { + final String input = "abcdefgh"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + stream.beginToken(); // 'a' + for (int i = 0; i < 4; i++) { + stream.readChar(); // b, c, d, e + } + + stream.backup(3); + assertEquals('c', stream.readChar()); + assertEquals('d', stream.readChar()); + assertEquals('e', stream.readChar()); + } + + /** + * Test multiple tokens. + * @throws Exception if any error occurs + */ + @Test + public void multipleTokens() throws Exception { + final String input = "first second third"; + final CssCharStream stream = new CssCharStream(new StringReader(input), 1, 1); + + // First token + stream.beginToken(); // 'f' + for (int i = 0; i < 4; i++) { + stream.readChar(); // i, r, s, t + } + assertEquals("first", stream.getImage()); + + // Skip space + stream.readChar(); + + // Second token + stream.beginToken(); // 's' + for (int i = 0; i < 5; i++) { + stream.readChar(); // e, c, o, n, d + } + assertEquals("second", stream.getImage()); + } + + /** + * Test getSuffix with length greater than buffer position. + * @throws Exception if any error occurs + */ + @Test + public void getSuffixWrapAround() throws Exception { + final StringBuilder input = new StringBuilder(); + for (int i = 0; i < 200; i++) { + input.append('a'); + } + + final CssCharStream stream = new CssCharStream(new StringReader(input.toString()), 1, 1, 50); + + stream.beginToken(); + for (int i = 0; i < 100; i++) { + stream.readChar(); + } + + final char[] suffix = stream.getSuffix(5); + assertEquals(5, suffix.length); + for (int i = 0; i < 5; i++) { + assertEquals('a', suffix[i]); + } + } + + /** + * Test staticFlag constant. + * @throws Exception if any error occurs + */ + @Test + public void staticFlag() throws Exception { + assertFalse(CssCharStream.staticFlag); + } +} diff --git a/src/test/java/org/htmlunit/cssparser/parser/condition/PseudoClassConditionTest.java b/src/test/java/org/htmlunit/cssparser/parser/condition/PseudoClassConditionTest.java index f8fe00b..6a18e24 100644 --- a/src/test/java/org/htmlunit/cssparser/parser/condition/PseudoClassConditionTest.java +++ b/src/test/java/org/htmlunit/cssparser/parser/condition/PseudoClassConditionTest.java @@ -60,4 +60,64 @@ public void withValue() throws Exception { assertEquals(":value", c.toString()); } + + /** + * Test double colon prefix. + * @throws Exception if any error occurs + */ + @Test + public void doubleColonPrefix() throws Exception { + final PseudoClassCondition c = new PseudoClassCondition("hover", null, true); + assertEquals("hover", c.getValue()); + assertEquals("::hover", c.toString()); + } + + /** + * Test single colon prefix. + * @throws Exception if any error occurs + */ + @Test + public void singleColonPrefix() throws Exception { + final PseudoClassCondition c = new PseudoClassCondition("hover", null, false); + assertEquals("hover", c.getValue()); + assertEquals(":hover", c.toString()); + } + + /** + * Test double colon with null value. + * @throws Exception if any error occurs + */ + @Test + public void doubleColonWithNullValue() throws Exception { + final PseudoClassCondition c = new PseudoClassCondition(null, null, true); + assertNull(c.getValue()); + assertNull(c.toString()); + } + + /** + * Test condition type. + * @throws Exception if any error occurs + */ + @Test + public void conditionType() throws Exception { + final PseudoClassCondition c = new PseudoClassCondition("active", null, false); + assertEquals(Condition.ConditionType.PSEUDO_CLASS_CONDITION, c.getConditionType()); + } + + /** + * Test various pseudo-class values with double colon. + * @throws Exception if any error occurs + */ + @Test + public void variousPseudoClassesDoubleColon() throws Exception { + final PseudoClassCondition c1 = new PseudoClassCondition("first-child", null, true); + assertEquals("::first-child", c1.toString()); + + final PseudoClassCondition c2 = new PseudoClassCondition("last-child", null, true); + assertEquals("::last-child", c2.toString()); + + final PseudoClassCondition c3 = new PseudoClassCondition("nth-child(2n)", null, true); + assertEquals("::nth-child(2n)", c3.toString()); + } } + diff --git a/src/test/java/org/htmlunit/cssparser/parser/media/MediaQueryTest.java b/src/test/java/org/htmlunit/cssparser/parser/media/MediaQueryTest.java index fa68bb9..6fb302e 100644 --- a/src/test/java/org/htmlunit/cssparser/parser/media/MediaQueryTest.java +++ b/src/test/java/org/htmlunit/cssparser/parser/media/MediaQueryTest.java @@ -76,4 +76,144 @@ public void media() throws Exception { final MediaQuery mq = new MediaQuery("test"); assertEquals("test", mq.getMedia()); } + + /** + * Test isOnly method. + * @throws Exception if any error occurs + */ + @Test + public void isOnly() throws Exception { + MediaQuery mq = new MediaQuery("screen", false, false); + assertEquals(false, mq.isOnly()); + + mq = new MediaQuery("screen", true, false); + assertEquals(true, mq.isOnly()); + } + + /** + * Test isNot method. + * @throws Exception if any error occurs + */ + @Test + public void isNot() throws Exception { + MediaQuery mq = new MediaQuery("screen", false, false); + assertEquals(false, mq.isNot()); + + mq = new MediaQuery("screen", false, true); + assertEquals(true, mq.isNot()); + } + + /** + * Test with both only and not (edge case). + * @throws Exception if any error occurs + */ + @Test + public void onlyAndNot() throws Exception { + // If both only and not are true, only takes precedence + final MediaQuery mq = new MediaQuery("screen", true, true); + assertEquals("only screen", mq.toString()); + } + + /** + * Test with null media type. + * @throws Exception if any error occurs + */ + @Test + public void nullMediaType() throws Exception { + final MediaQuery mq = new MediaQuery(null); + // null media is converted to "all" but implicitAll is set + // so toString doesn't show it unless there are properties + assertEquals("", mq.toString()); + assertEquals("all", mq.getMedia()); + } + + /** + * Test with empty media type. + * @throws Exception if any error occurs + */ + @Test + public void emptyMediaType() throws Exception { + final MediaQuery mq = new MediaQuery(""); + assertEquals("", mq.toString()); + } + + /** + * Test multiple properties. + * @throws Exception if any error occurs + */ + @Test + public void multipleProperties() throws Exception { + final MediaQuery mq = new MediaQuery("screen"); + + final CSSValueImpl value1 = new CSSValueImpl(null); + value1.setCssText("800px"); + final Property prop1 = new Property("min-width", value1, false); + mq.addMediaProperty(prop1); + + final CSSValueImpl value2 = new CSSValueImpl(null); + value2.setCssText("1200px"); + final Property prop2 = new Property("max-width", value2, false); + mq.addMediaProperty(prop2); + + final CSSValueImpl value3 = new CSSValueImpl(null); + value3.setCssText("landscape"); + final Property prop3 = new Property("orientation", value3, false); + mq.addMediaProperty(prop3); + + assertEquals("screen and (min-width: 800px) and (max-width: 1200px) and (orientation: landscape)", + mq.toString()); + assertEquals(3, mq.getProperties().size()); + } + + /** + * Test common media types. + * @throws Exception if any error occurs + */ + @Test + public void commonMediaTypes() throws Exception { + MediaQuery mq = new MediaQuery("all"); + assertEquals("all", mq.toString()); + + mq = new MediaQuery("screen"); + assertEquals("screen", mq.toString()); + + mq = new MediaQuery("print"); + assertEquals("print", mq.toString()); + + mq = new MediaQuery("speech"); + assertEquals("speech", mq.toString()); + } + + /** + * Test with only and properties. + * @throws Exception if any error occurs + */ + @Test + public void onlyWithProperties() throws Exception { + final MediaQuery mq = new MediaQuery("print", true, false); + + final CSSValueImpl value = new CSSValueImpl(null); + value.setCssText("300dpi"); + final Property prop = new Property("resolution", value, false); + mq.addMediaProperty(prop); + + assertEquals("only print and (resolution: 300dpi)", mq.toString()); + } + + /** + * Test with not and properties. + * @throws Exception if any error occurs + */ + @Test + public void notWithProperties() throws Exception { + final MediaQuery mq = new MediaQuery("screen", false, true); + + final CSSValueImpl value = new CSSValueImpl(null); + value.setCssText("600px"); + final Property prop = new Property("max-width", value, false); + mq.addMediaProperty(prop); + + assertEquals("not screen and (max-width: 600px)", mq.toString()); + } } + diff --git a/src/test/java/org/htmlunit/cssparser/parser/selector/DescendantSelectorImplTest.java b/src/test/java/org/htmlunit/cssparser/parser/selector/DescendantSelectorImplTest.java index ad63cb2..5577284 100644 --- a/src/test/java/org/htmlunit/cssparser/parser/selector/DescendantSelectorImplTest.java +++ b/src/test/java/org/htmlunit/cssparser/parser/selector/DescendantSelectorImplTest.java @@ -86,4 +86,64 @@ public void pseudoElementDescendant() { assertEquals("a:after", selector.toString()); } + + /** + * Test with null ancestor selector. + * @throws Exception on failure + */ + @Test + public void nullAncestorSelector() { + final ElementSelector descendant = new ElementSelector("a", null); + final DescendantSelector selector = new DescendantSelector(null, descendant); + + assertEquals(null, selector.getAncestorSelector()); + assertEquals(descendant, selector.getSimpleSelector()); + assertEquals(" a", selector.toString()); + } + + /** + * Test with null simple selector. + * @throws Exception on failure + */ + @Test + public void nullSimpleSelector() { + final ElementSelector parent = new ElementSelector("p", null); + final DescendantSelector selector = new DescendantSelector(parent, null); + + assertEquals(parent, selector.getAncestorSelector()); + assertEquals(null, selector.getSimpleSelector()); + } + + /** + * Test complex descendant chain. + * @throws Exception on failure + */ + @Test + public void complexDescendantChain() { + final ElementSelector grandParent = new ElementSelector("div", null); + final ElementSelector parent = new ElementSelector("p", null); + final DescendantSelector level1 = new DescendantSelector(grandParent, parent); + final ElementSelector child = new ElementSelector("a", null); + final DescendantSelector level2 = new DescendantSelector(level1, child); + + assertEquals(level1, level2.getAncestorSelector()); + assertEquals(child, level2.getSimpleSelector()); + assertEquals("div p a", level2.toString()); + } + + /** + * Test descendant with multiple levels and pseudo-element. + * @throws Exception on failure + */ + @Test + public void complexDescendantWithPseudoElement() { + final ElementSelector grandParent = new ElementSelector("div", null); + final ElementSelector parent = new ElementSelector("p", null); + final DescendantSelector level1 = new DescendantSelector(grandParent, parent); + final PseudoElementSelector pseudo = new PseudoElementSelector("first-line", null, false); + final DescendantSelector level2 = new DescendantSelector(level1, pseudo); + + assertEquals("div p:first-line", level2.toString()); + } } + From 88efabe497ca44b99c8c563fd3503a9897172fd7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:30:26 +0000 Subject: [PATCH 3/4] Add comprehensive tests for AbstractCSSRuleImpl and CSS error handling Co-authored-by: rbri <2544132+rbri@users.noreply.github.com> --- .../dom/AbstractCSSRuleImplTest.java | 202 ++++++++++++ .../parser/CSSErrorHandlingTest.java | 306 ++++++++++++++++++ 2 files changed, 508 insertions(+) create mode 100644 src/test/java/org/htmlunit/cssparser/dom/AbstractCSSRuleImplTest.java create mode 100644 src/test/java/org/htmlunit/cssparser/parser/CSSErrorHandlingTest.java diff --git a/src/test/java/org/htmlunit/cssparser/dom/AbstractCSSRuleImplTest.java b/src/test/java/org/htmlunit/cssparser/dom/AbstractCSSRuleImplTest.java new file mode 100644 index 0000000..35a98b0 --- /dev/null +++ b/src/test/java/org/htmlunit/cssparser/dom/AbstractCSSRuleImplTest.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2019-2024 Ronald Brill. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.htmlunit.cssparser.dom; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.junit.jupiter.api.Test; + +/** + * Unit tests for {@link AbstractCSSRuleImpl}. + * + * @author Ronald Brill + */ +public class AbstractCSSRuleImplTest { + + /** + * Test parent style sheet getter/setter. + * @throws Exception if any error occurs + */ + @Test + public void parentStyleSheet() throws Exception { + final CSSStyleSheetImpl styleSheet = new CSSStyleSheetImpl(); + final CSSStyleRuleImpl rule = new CSSStyleRuleImpl(styleSheet, null, null); + + assertEquals(styleSheet, rule.getParentStyleSheet()); + + final CSSStyleSheetImpl newStyleSheet = new CSSStyleSheetImpl(); + rule.setParentStyleSheet(newStyleSheet); + + assertEquals(newStyleSheet, rule.getParentStyleSheet()); + } + + /** + * Test parent rule getter/setter. + * @throws Exception if any error occurs + */ + @Test + public void parentRule() throws Exception { + final CSSStyleSheetImpl styleSheet = new CSSStyleSheetImpl(); + final CSSPageRuleImpl parentRule = new CSSPageRuleImpl(styleSheet, null, null); + final CSSStyleRuleImpl rule = new CSSStyleRuleImpl(styleSheet, parentRule, null); + + assertEquals(parentRule, rule.getParentRule()); + + final CSSPageRuleImpl newParentRule = new CSSPageRuleImpl(styleSheet, null, null); + rule.setParentRule(newParentRule); + + assertEquals(newParentRule, rule.getParentRule()); + } + + /** + * Test with null parent style sheet. + * @throws Exception if any error occurs + */ + @Test + public void nullParentStyleSheet() throws Exception { + final CSSStyleRuleImpl rule = new CSSStyleRuleImpl(null, null, null); + + assertNull(rule.getParentStyleSheet()); + } + + /** + * Test with null parent rule. + * @throws Exception if any error occurs + */ + @Test + public void nullParentRule() throws Exception { + final CSSStyleSheetImpl styleSheet = new CSSStyleSheetImpl(); + final CSSStyleRuleImpl rule = new CSSStyleRuleImpl(styleSheet, null, null); + + assertNull(rule.getParentRule()); + } + + /** + * Test equals with same object. + * @throws Exception if any error occurs + */ + @Test + public void equalsSameObject() throws Exception { + final CSSStyleSheetImpl styleSheet = new CSSStyleSheetImpl(); + final CSSStyleRuleImpl rule = new CSSStyleRuleImpl(styleSheet, null, null); + + assertTrue(rule.equals(rule)); + } + + /** + * Test equals with different types. + * @throws Exception if any error occurs + */ + @Test + public void equalsDifferentType() throws Exception { + final CSSStyleSheetImpl styleSheet = new CSSStyleSheetImpl(); + final CSSStyleRuleImpl rule = new CSSStyleRuleImpl(styleSheet, null, null); + + assertFalse(rule.equals("not a rule")); + assertFalse(rule.equals(null)); + } + + /** + * Test that parent relationships don't cause stack overflow in equals. + * @throws Exception if any error occurs + */ + @Test + public void equalsNoStackOverflow() throws Exception { + final CSSStyleSheetImpl styleSheet = new CSSStyleSheetImpl(); + final CSSPageRuleImpl parent1 = new CSSPageRuleImpl(styleSheet, null, null); + final CSSStyleRuleImpl rule1 = new CSSStyleRuleImpl(styleSheet, parent1, null); + + final CSSPageRuleImpl parent2 = new CSSPageRuleImpl(styleSheet, null, null); + final CSSStyleRuleImpl rule2 = new CSSStyleRuleImpl(styleSheet, parent2, null); + + // This should not cause stack overflow even with parent relationships + rule1.equals(rule2); + } + + /** + * Test hashCode consistency. + * @throws Exception if any error occurs + */ + @Test + public void hashCodeConsistency() throws Exception { + final CSSStyleSheetImpl styleSheet = new CSSStyleSheetImpl(); + final CSSStyleRuleImpl rule = new CSSStyleRuleImpl(styleSheet, null, null); + + final int hash1 = rule.hashCode(); + final int hash2 = rule.hashCode(); + + assertEquals(hash1, hash2); + } + + /** + * Test that parent relationships don't cause stack overflow in hashCode. + * @throws Exception if any error occurs + */ + @Test + public void hashCodeNoStackOverflow() throws Exception { + final CSSStyleSheetImpl styleSheet = new CSSStyleSheetImpl(); + final CSSPageRuleImpl parent = new CSSPageRuleImpl(styleSheet, null, null); + final CSSStyleRuleImpl rule = new CSSStyleRuleImpl(styleSheet, parent, null); + + // This should not cause stack overflow + final int hash = rule.hashCode(); + assertNotEquals(0, hash); // Just verify it computed something + } + + /** + * Test changing parent style sheet after construction. + * @throws Exception if any error occurs + */ + @Test + public void changeParentStyleSheet() throws Exception { + final CSSStyleSheetImpl styleSheet1 = new CSSStyleSheetImpl(); + final CSSStyleRuleImpl rule = new CSSStyleRuleImpl(styleSheet1, null, null); + + assertEquals(styleSheet1, rule.getParentStyleSheet()); + + final CSSStyleSheetImpl styleSheet2 = new CSSStyleSheetImpl(); + rule.setParentStyleSheet(styleSheet2); + + assertEquals(styleSheet2, rule.getParentStyleSheet()); + + rule.setParentStyleSheet(null); + assertNull(rule.getParentStyleSheet()); + } + + /** + * Test changing parent rule after construction. + * @throws Exception if any error occurs + */ + @Test + public void changeParentRule() throws Exception { + final CSSStyleSheetImpl styleSheet = new CSSStyleSheetImpl(); + final CSSPageRuleImpl parent1 = new CSSPageRuleImpl(styleSheet, null, null); + final CSSStyleRuleImpl rule = new CSSStyleRuleImpl(styleSheet, parent1, null); + + assertEquals(parent1, rule.getParentRule()); + + final CSSPageRuleImpl parent2 = new CSSPageRuleImpl(styleSheet, null, null); + rule.setParentRule(parent2); + + assertEquals(parent2, rule.getParentRule()); + + rule.setParentRule(null); + assertNull(rule.getParentRule()); + } +} diff --git a/src/test/java/org/htmlunit/cssparser/parser/CSSErrorHandlingTest.java b/src/test/java/org/htmlunit/cssparser/parser/CSSErrorHandlingTest.java new file mode 100644 index 0000000..447bc02 --- /dev/null +++ b/src/test/java/org/htmlunit/cssparser/parser/CSSErrorHandlingTest.java @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2019-2024 Ronald Brill. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.htmlunit.cssparser.parser; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.StringReader; + +import org.htmlunit.cssparser.ErrorHandler; +import org.htmlunit.cssparser.dom.CSSStyleSheetImpl; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for CSS error handling and recovery. + * + * @author Ronald Brill + */ +public class CSSErrorHandlingTest { + + /** + * Test malformed selector recovery. + * @throws Exception if any error occurs + */ + @Test + public void malformedSelector() throws Exception { + final String css = "p { color: red; }\n" + + "div# { font-size: 12px; }\n" // malformed selector + + "span { margin: 10px; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + // Parser should recover and continue parsing + assertTrue(errorHandler.getErrorCount() > 0 || errorHandler.getWarningCount() > 0); + } + + /** + * Test malformed property value recovery. + * @throws Exception if any error occurs + */ + @Test + public void malformedPropertyValue() throws Exception { + final String css = "p { color: #gg; font-size: 12px; }"; // invalid color + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test unclosed brace recovery. + * @throws Exception if any error occurs + */ + @Test + public void unclosedBrace() throws Exception { + final String css = "p { color: red;\n" + + "div { margin: 10px; }"; // missing closing brace for p + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test empty input. + * @throws Exception if any error occurs + */ + @Test + public void emptyInput() throws Exception { + final String css = ""; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(0, errorHandler.getErrorCount()); + assertEquals(0, errorHandler.getFatalErrorCount()); + } + + /** + * Test whitespace only input. + * @throws Exception if any error occurs + */ + @Test + public void whitespaceOnlyInput() throws Exception { + final String css = " \n\t \r\n "; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(0, errorHandler.getErrorCount()); + } + + /** + * Test invalid at-rule recovery. + * @throws Exception if any error occurs + */ + @Test + public void invalidAtRule() throws Exception { + final String css = "@unknown-rule { content };\n" + + "p { color: blue; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test invalid unicode escape sequence. + * @throws Exception if any error occurs + */ + @Test + public void invalidUnicodeEscape() throws Exception { + final String css = "p { content: '\\GGGG'; }"; // invalid hex digits + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test null input source. + * @throws Exception if any error occurs + */ + @Test + public void nullReader() throws Exception { + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader("")), null); + + assertNotNull(sheet); + } + + /** + * Test multiple consecutive errors. + * @throws Exception if any error occurs + */ + @Test + public void multipleConsecutiveErrors() throws Exception { + final String css = "p# { }\n" + + "div## { }\n" + + "span### { }\n" + + "a { color: red; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test invalid character in selector. + * @throws Exception if any error occurs + */ + @Test + public void invalidCharacterInSelector() throws Exception { + final String css = "p@invalid { color: red; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test missing semicolon between properties. + * @throws Exception if any error occurs + */ + @Test + public void missingSemicolon() throws Exception { + final String css = "p { color: red margin: 10px; }"; // missing semicolon after red + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test very long CSS input for buffer handling. + * @throws Exception if any error occurs + */ + @Test + public void veryLongInput() throws Exception { + final StringBuilder css = new StringBuilder(); + for (int i = 0; i < 1000; i++) { + css.append(".class").append(i).append(" { color: red; margin: ").append(i).append("px; }\n"); + } + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css.toString())), null); + + assertNotNull(sheet); + assertTrue(sheet.getCssRules().getLength() > 0); + } + + /** + * Test deeply nested at-rules. + * @throws Exception if any error occurs + */ + @Test + public void deeplyNestedAtRules() throws Exception { + final String css = "@media screen { " + + "@supports (display: flex) { " + + "div { display: flex; } " + + "} " + + "}"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test special characters in strings. + * @throws Exception if any error occurs + */ + @Test + public void specialCharactersInStrings() throws Exception { + final String css = "p::before { content: '\\n\\r\\t\\\\'; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test comments with special content. + * @throws Exception if any error occurs + */ + @Test + public void commentsWithSpecialContent() throws Exception { + final String css = "/* comment with // and /* nested */ */ p { color: red; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } +} From d305d2487c9f250381f76335d779f5b93a4f3fa7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Dec 2025 18:33:03 +0000 Subject: [PATCH 4/4] Add comprehensive selector specificity and complex selector combination tests Co-authored-by: rbri <2544132+rbri@users.noreply.github.com> --- .../parser/selector/ComplexSelectorTest.java | 310 ++++++++++++++++++ .../selector/SelectorSpecificityTest.java | 84 +++++ 2 files changed, 394 insertions(+) create mode 100644 src/test/java/org/htmlunit/cssparser/parser/selector/ComplexSelectorTest.java diff --git a/src/test/java/org/htmlunit/cssparser/parser/selector/ComplexSelectorTest.java b/src/test/java/org/htmlunit/cssparser/parser/selector/ComplexSelectorTest.java new file mode 100644 index 0000000..cc00d15 --- /dev/null +++ b/src/test/java/org/htmlunit/cssparser/parser/selector/ComplexSelectorTest.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2019-2024 Ronald Brill. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.htmlunit.cssparser.parser.selector; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.io.StringReader; + +import org.htmlunit.cssparser.ErrorHandler; +import org.htmlunit.cssparser.dom.CSSRuleListImpl; +import org.htmlunit.cssparser.dom.CSSStyleRuleImpl; +import org.htmlunit.cssparser.dom.CSSStyleSheetImpl; +import org.htmlunit.cssparser.parser.CSSOMParser; +import org.htmlunit.cssparser.parser.InputSource; +import org.junit.jupiter.api.Test; + +/** + * Unit tests for complex selector combinations. + * + * @author Ronald Brill + */ +public class ComplexSelectorTest { + + /** + * Test complex descendant selector chain. + * @throws Exception if any error occurs + */ + @Test + public void complexDescendantChain() throws Exception { + final String css = "div ul li a { color: blue; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(0, errorHandler.getErrorCount()); + + final CSSRuleListImpl rules = sheet.getCssRules(); + assertEquals(1, rules.getLength()); + } + + /** + * Test child combinator selector. + * @throws Exception if any error occurs + */ + @Test + public void childCombinator() throws Exception { + final String css = "div > p { margin: 10px; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(0, errorHandler.getErrorCount()); + } + + /** + * Test adjacent sibling combinator. + * @throws Exception if any error occurs + */ + @Test + public void adjacentSiblingCombinator() throws Exception { + final String css = "h1 + p { font-weight: bold; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(0, errorHandler.getErrorCount()); + } + + /** + * Test general sibling combinator. + * @throws Exception if any error occurs + */ + @Test + public void generalSiblingCombinator() throws Exception { + final String css = "h1 ~ p { color: gray; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(0, errorHandler.getErrorCount()); + } + + /** + * Test complex selector with classes and IDs. + * @throws Exception if any error occurs + */ + @Test + public void classesAndIds() throws Exception { + final String css = "div#main.container.active { display: block; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(0, errorHandler.getErrorCount()); + } + + /** + * Test pseudo-class combinations. + * @throws Exception if any error occurs + */ + @Test + public void pseudoClassCombinations() throws Exception { + final String css = "a:link:hover:not(.external) { text-decoration: underline; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test attribute selector combinations. + * @throws Exception if any error occurs + */ + @Test + public void attributeSelectorCombinations() throws Exception { + final String css = "input[type=\"text\"][required]:focus { border-color: blue; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test nth-child pseudo-class variations. + * @throws Exception if any error occurs + */ + @Test + public void nthChildVariations() throws Exception { + final String css = "li:nth-child(2n) { background: gray; }\n" + + "li:nth-child(odd) { background: white; }\n" + + "li:nth-child(3n+1) { font-weight: bold; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(3, sheet.getCssRules().getLength()); + } + + /** + * Test pseudo-element with pseudo-class. + * @throws Exception if any error occurs + */ + @Test + public void pseudoElementWithPseudoClass() throws Exception { + final String css = "p:first-child::first-letter { font-size: 2em; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test complex selector list. + * @throws Exception if any error occurs + */ + @Test + public void complexSelectorList() throws Exception { + final String css = "h1, h2, h3, .heading, #title { font-family: Arial; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(0, errorHandler.getErrorCount()); + + final CSSStyleRuleImpl rule = (CSSStyleRuleImpl) sheet.getCssRules().getRules().get(0); + assertNotNull(rule.getSelectorText()); + } + + /** + * Test mixed combinators. + * @throws Exception if any error occurs + */ + @Test + public void mixedCombinators() throws Exception { + final String css = "div > ul li + span ~ a { color: red; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test universal selector combinations. + * @throws Exception if any error occurs + */ + @Test + public void universalSelectorCombinations() throws Exception { + final String css = "* { box-sizing: border-box; }\n" + + "div * { margin: 0; }\n" + + "*:hover { cursor: pointer; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(3, sheet.getCssRules().getLength()); + } + + /** + * Test namespace selectors. + * @throws Exception if any error occurs + */ + @Test + public void namespaceSelectorsCombinations() throws Exception { + final String css = "svg|rect { fill: blue; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test case-insensitive attribute selector. + * @throws Exception if any error occurs + */ + @Test + public void caseInsensitiveAttribute() throws Exception { + final String css = "a[href*=\"example\" i] { color: blue; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + } + + /** + * Test deeply nested selectors. + * @throws Exception if any error occurs + */ + @Test + public void deeplyNestedSelectors() throws Exception { + final String css = "html body div.container section article > p:first-of-type { line-height: 1.5; }"; + + final CSSOMParser parser = new CSSOMParser(); + final ErrorHandler errorHandler = new ErrorHandler(); + parser.setErrorHandler(errorHandler); + + final CSSStyleSheetImpl sheet = parser.parseStyleSheet(new InputSource(new StringReader(css)), null); + + assertNotNull(sheet); + assertEquals(0, errorHandler.getErrorCount()); + } +} diff --git a/src/test/java/org/htmlunit/cssparser/parser/selector/SelectorSpecificityTest.java b/src/test/java/org/htmlunit/cssparser/parser/selector/SelectorSpecificityTest.java index de24606..742e47f 100644 --- a/src/test/java/org/htmlunit/cssparser/parser/selector/SelectorSpecificityTest.java +++ b/src/test/java/org/htmlunit/cssparser/parser/selector/SelectorSpecificityTest.java @@ -98,6 +98,90 @@ public void pseudoSelector() throws Exception { selectorSpecifity("h2:nth-last-of-type(n + 2)", "0,0,1,1"); } + /** + * Test specificity with :not() pseudo-class. + * @throws Exception if the test fails + */ + @Test + public void notPseudoClass() throws Exception { + selectorSpecifity(":not(p)", "0,0,0,1"); + selectorSpecifity(":not(.class)", "0,0,1,0"); + selectorSpecifity(":not(#id)", "0,1,0,0"); + } + + /** + * Test specificity with :is() pseudo-class. + * @throws Exception if the test fails + */ + @Test + public void isPseudoClass() throws Exception { + selectorSpecifity(":is(p)", "0,0,0,1"); + selectorSpecifity(":is(.class)", "0,0,1,0"); + selectorSpecifity(":is(#id)", "0,1,0,0"); + } + + /** + * Test specificity with :where() pseudo-class (always 0 specificity). + * @throws Exception if the test fails + */ + @Test + public void wherePseudoClass() throws Exception { + selectorSpecifity(":where(p)", "0,0,0,0"); + selectorSpecifity(":where(.class)", "0,0,0,0"); + selectorSpecifity(":where(#id)", "0,0,0,0"); + } + + /** + * Test specificity with multiple IDs. + * @throws Exception if the test fails + */ + @Test + public void multipleIds() throws Exception { + selectorSpecifity("#id1#id2", "0,2,0,0"); + selectorSpecifity("div#id1#id2", "0,2,0,1"); + } + + /** + * Test specificity with complex combinations. + * @throws Exception if the test fails + */ + @Test + public void complexCombinations() throws Exception { + selectorSpecifity("div.class1.class2#id", "0,1,2,1"); + selectorSpecifity("ul li:first-child a", "0,0,1,3"); + selectorSpecifity("#nav ul li a:hover", "0,1,1,3"); + } + + /** + * Test specificity with attribute selectors. + * @throws Exception if the test fails + */ + @Test + public void attributeSelectors() throws Exception { + selectorSpecifity("[type]", "0,0,1,0"); + selectorSpecifity("input[type=\"text\"]", "0,0,1,1"); + selectorSpecifity("a[href^=\"https\"]", "0,0,1,1"); + } + + /** + * Test compareTo with various combinations. + * @throws Exception if the test fails + */ + @Test + public void compareToVariousCombinations() throws Exception { + final SelectorSpecificity s1 = selectorSpecifity("*", "0,0,0,0"); + final SelectorSpecificity s2 = selectorSpecifity("#id", "0,1,0,0"); + final SelectorSpecificity s3 = selectorSpecifity(".class", "0,0,1,0"); + final SelectorSpecificity s4 = selectorSpecifity("p", "0,0,0,1"); + + assertTrue(s1.compareTo(s2) < 0); + assertTrue(s1.compareTo(s3) < 0); + assertTrue(s1.compareTo(s4) < 0); + assertTrue(s4.compareTo(s3) < 0); + assertTrue(s3.compareTo(s2) < 0); + } + + /** * @throws Exception if the test fails */