diff --git a/lib/syntax_tree/css/format.rb b/lib/syntax_tree/css/format.rb index b7b1620..a940f69 100644 --- a/lib/syntax_tree/css/format.rb +++ b/lib/syntax_tree/css/format.rb @@ -47,6 +47,11 @@ def visit_ident_token(node) q.text(node.value) end + # Visit a HashToken node. + def visit_hash_token(node) + q.text(node.value) + end + # Visit a StyleRule node. def visit_style_rule(node) q.group do @@ -77,12 +82,30 @@ def visit_type_selector(node) end end + # Visit a Selectors::IdSelector node. + def visit_id_selector(node) + q.text("#") + node.value.format(q) + end + # Visit a Selectors::ClassSelector node. def visit_class_selector(node) q.text(".") node.value.format(q) end + # Visit a Selectors::PseudoClassSelector node. + def visit_pseudo_class_selector(node) + q.text(":") + node.value.format(q) + end + + # Visit a Selectors::PseudoElementSelector node. + def visit_pseudo_element_selector(node) + q.text(":") + node.value.format(q) + end + # Visit a Selectors::Combinator node. def visit_combinator(node) node.value.format(q) @@ -101,9 +124,8 @@ def visit_complex_selector(node) # Visit a Selectors::CompoundSelector node. def visit_compound_selector(node) q.group do - node.type.format(q) if node.type - node.subclasses.each do |subclass| - subclass.format(q) + node.child_nodes.each do |node_| + node_.format(q) end # TODO: pseudo-elements end diff --git a/lib/syntax_tree/css/selectors.rb b/lib/syntax_tree/css/selectors.rb index 6a05b6f..32bf420 100644 --- a/lib/syntax_tree/css/selectors.rb +++ b/lib/syntax_tree/css/selectors.rb @@ -125,7 +125,7 @@ def accept(visitor) end def child_nodes - [type, subclasses, pseudo_elements].flatten + [type, subclasses, pseudo_elements].compact.flatten end alias deconstruct child_nodes diff --git a/test/selectors_test.rb b/test/selectors_test.rb index 138131e..b7a54e6 100644 --- a/test/selectors_test.rb +++ b/test/selectors_test.rb @@ -27,6 +27,15 @@ class SelectorsTest < Minitest::Spec ] ] end + + assert_pattern do + actual => [ + Selectors::CompoundSelector[ + Selectors::ClassSelector[value: { value: "flex" }], + Selectors::ClassSelector[value: { value: "text-xl" }] + ] + ] + end end it "parses a compound selector" do @@ -41,6 +50,15 @@ class SelectorsTest < Minitest::Spec ] ] end + + assert_pattern do + actual => [ + Selectors::CompoundSelector[ + Selectors::TypeSelector[value: { name: { value: "div" } } ], + Selectors::ClassSelector[value: { value: "flex" }], + ] + ] + end end it "parses a compound selector with a pseudo-element" do @@ -54,9 +72,7 @@ class SelectorsTest < Minitest::Spec pseudo_elements: [ [ Selectors::PseudoElementSelector[ - Selectors::PseudoClassSelector[ - value: { value: "first-line" } - ] + value: { value: { value: "first-line" } } ], [] ] @@ -66,6 +82,51 @@ class SelectorsTest < Minitest::Spec end end + it "parses a compound selector with a pseudo-class" do + actual = parse_selectors("div.flex:hover") + + assert_pattern do + actual => [ + Selectors::CompoundSelector[ + type: { value: { name: { value: "div" } } }, + subclasses: [ + Selectors::ClassSelector[value: { value: "flex" }], + Selectors::PseudoClassSelector[value: { value: "hover" }], + ], + ] + ] + end + end + + it "parses a compound selector with pseudo-elements and pseudo-classes" do + actual = parse_selectors("div.flex:hover::first-line:last-child:active::first-letter") + + assert_pattern do + actual => [ + Selectors::CompoundSelector[ + type: { value: { name: { value: "div" } } }, + subclasses: [ + Selectors::ClassSelector[value: { value: "flex" }], + Selectors::PseudoClassSelector[value: { value: "hover" }], + ], + pseudo_elements: [ + [ + Selectors::PseudoElementSelector[value: { value: { value: "first-line" } }], + [ + Selectors::PseudoClassSelector[value: { value: "last-child" }], + Selectors::PseudoClassSelector[value: { value: "active" }], + ], + ], + [ + Selectors::PseudoElementSelector[value: { value: { value: "first-letter" } }], + [], + ] + ] + ] + ] + end + end + it "parses a complex selector" do actual = parse_selectors("section>table") @@ -151,8 +212,43 @@ class SelectorsTest < Minitest::Spec end describe "formatting" do - it "formats complex selectors" do - assert_selector_format(".outer section.foo>table.bar tr", ".outer section.foo > table.bar tr") + describe Selectors::CompoundSelector do + it "with an id selector" do + assert_selector_format( + "div#foo", + "div#foo", + ) + end + + it "with a pseudo-class selector" do + assert_selector_format( + "div:hover", + "div:hover", + ) + end + + it "with class selectors" do + assert_selector_format( + "div.flex.text-xl", + "div.flex.text-xl", + ) + end + + it "with pseudo-elements" do + assert_selector_format( + "div.flex:hover::first-line:last-child:active::first-letter", + "div.flex:hover::first-line:last-child:active::first-letter", + ) + end + end + + describe Selectors::ComplexSelector do + it "with whitespace" do + assert_selector_format( + ".outer section.foo>table.bar tr", + ".outer section.foo > table.bar tr", + ) + end end private @@ -162,7 +258,7 @@ def assert_selector_format(selectors, expected) io = StringIO.new selectors.each do |selector| - selector.format(::PrettyPrint.new(io)) + selector.format(::PP.new(io)) assert_equal(expected, io.string) end end