@@ -1607,3 +1607,125 @@ pub struct R3ParseResult<'a> {
16071607 /// Comment nodes (if collected).
16081608 pub comment_nodes : Option < Vec < ' a , R3Comment < ' a > > > ,
16091609}
1610+
1611+ #[ cfg( test) ]
1612+ mod tests {
1613+ use oxc_allocator:: Allocator ;
1614+
1615+ use crate :: ast:: r3:: { R3Visitor , visit_all} ;
1616+ use crate :: parser:: html:: HtmlParser ;
1617+ use crate :: transform:: html_to_r3:: { TransformOptions , html_ast_to_r3_ast} ;
1618+
1619+ /// A visitor that collects names of visited attributes, inputs, and outputs.
1620+ struct AttributeCollector {
1621+ text_attributes : Vec < String > ,
1622+ bound_attributes : Vec < String > ,
1623+ bound_events : Vec < String > ,
1624+ elements : Vec < String > ,
1625+ }
1626+
1627+ impl AttributeCollector {
1628+ fn new ( ) -> Self {
1629+ Self {
1630+ text_attributes : Vec :: new ( ) ,
1631+ bound_attributes : Vec :: new ( ) ,
1632+ bound_events : Vec :: new ( ) ,
1633+ elements : Vec :: new ( ) ,
1634+ }
1635+ }
1636+ }
1637+
1638+ impl < ' a > R3Visitor < ' a > for AttributeCollector {
1639+ fn visit_element ( & mut self , element : & super :: R3Element < ' a > ) {
1640+ self . elements . push ( element. name . to_string ( ) ) ;
1641+ self . visit_element_children ( element) ;
1642+ }
1643+
1644+ fn visit_text_attribute ( & mut self , attr : & super :: R3TextAttribute < ' a > ) {
1645+ self . text_attributes . push ( attr. name . to_string ( ) ) ;
1646+ }
1647+
1648+ fn visit_bound_attribute ( & mut self , attr : & super :: R3BoundAttribute < ' a > ) {
1649+ self . bound_attributes . push ( attr. name . to_string ( ) ) ;
1650+ }
1651+
1652+ fn visit_bound_event ( & mut self , event : & super :: R3BoundEvent < ' a > ) {
1653+ self . bound_events . push ( event. name . to_string ( ) ) ;
1654+ }
1655+ }
1656+
1657+ #[ test]
1658+ fn test_r3_visitor_visits_attributes_inputs_outputs ( ) {
1659+ let allocator = Allocator :: default ( ) ;
1660+ let template =
1661+ r#"<button type="submit" [disabled]="isDisabled" (click)="onClick()">Save</button>"# ;
1662+
1663+ let html_result = HtmlParser :: new ( & allocator, template, "test.html" ) . parse ( ) ;
1664+ assert ! ( html_result. errors. is_empty( ) ) ;
1665+
1666+ let r3_result = html_ast_to_r3_ast (
1667+ & allocator,
1668+ template,
1669+ & html_result. nodes ,
1670+ TransformOptions :: default ( ) ,
1671+ ) ;
1672+ assert ! ( r3_result. errors. is_empty( ) ) ;
1673+
1674+ let mut collector = AttributeCollector :: new ( ) ;
1675+ visit_all ( & mut collector, & r3_result. nodes ) ;
1676+
1677+ assert_eq ! ( collector. elements, vec![ "button" ] ) ;
1678+ assert_eq ! ( collector. text_attributes, vec![ "type" ] ) ;
1679+ assert_eq ! ( collector. bound_attributes, vec![ "disabled" ] ) ;
1680+ assert_eq ! ( collector. bound_events, vec![ "click" ] ) ;
1681+ }
1682+
1683+ #[ test]
1684+ fn test_r3_visitor_visits_nested_elements ( ) {
1685+ let allocator = Allocator :: default ( ) ;
1686+ let template = r#"<div id="outer"><span class="inner" [title]="t" (mouseenter)="onHover()">text</span></div>"# ;
1687+
1688+ let html_result = HtmlParser :: new ( & allocator, template, "test.html" ) . parse ( ) ;
1689+ assert ! ( html_result. errors. is_empty( ) ) ;
1690+
1691+ let r3_result = html_ast_to_r3_ast (
1692+ & allocator,
1693+ template,
1694+ & html_result. nodes ,
1695+ TransformOptions :: default ( ) ,
1696+ ) ;
1697+ assert ! ( r3_result. errors. is_empty( ) ) ;
1698+
1699+ let mut collector = AttributeCollector :: new ( ) ;
1700+ visit_all ( & mut collector, & r3_result. nodes ) ;
1701+
1702+ assert_eq ! ( collector. elements, vec![ "div" , "span" ] ) ;
1703+ assert_eq ! ( collector. text_attributes, vec![ "id" , "class" ] ) ;
1704+ assert_eq ! ( collector. bound_attributes, vec![ "title" ] ) ;
1705+ assert_eq ! ( collector. bound_events, vec![ "mouseenter" ] ) ;
1706+ }
1707+
1708+ #[ test]
1709+ fn test_r3_visitor_default_noop_does_not_break ( ) {
1710+ let allocator = Allocator :: default ( ) ;
1711+ let template = r#"<input [value]="name" (change)="update()" required />"# ;
1712+
1713+ let html_result = HtmlParser :: new ( & allocator, template, "test.html" ) . parse ( ) ;
1714+ assert ! ( html_result. errors. is_empty( ) ) ;
1715+
1716+ let r3_result = html_ast_to_r3_ast (
1717+ & allocator,
1718+ template,
1719+ & html_result. nodes ,
1720+ TransformOptions :: default ( ) ,
1721+ ) ;
1722+ assert ! ( r3_result. errors. is_empty( ) ) ;
1723+
1724+ // A visitor with all default no-op methods should traverse without panic
1725+ struct NoopVisitor ;
1726+ impl < ' a > R3Visitor < ' a > for NoopVisitor { }
1727+
1728+ let mut visitor = NoopVisitor ;
1729+ visit_all ( & mut visitor, & r3_result. nodes ) ;
1730+ }
1731+ }
0 commit comments