Skip to main content

stylex_transform/shared/utils/ast/tests/
convertors_tests.rs

1#[cfg(test)]
2mod tests {
3  use crate::shared::structures::functions::FunctionMap;
4  use crate::shared::structures::state::EvaluationState;
5  use crate::shared::structures::state_manager::StateManager;
6  use crate::shared::utils::ast::convertors::{
7    binary_expr_to_num, binary_expr_to_string, convert_string_to_prop_name,
8  };
9  use stylex_enums::misc::BinaryExprType;
10  use swc_core::{
11    common::SyntaxContext,
12    ecma::ast::{BinExpr, BinaryOp, Expr, Ident, IdentName, Lit, Str},
13  };
14
15  fn make_num_expr(val: f64) -> Expr {
16    Expr::Lit(Lit::Num(swc_core::ecma::ast::Number {
17      value: val,
18      span: Default::default(),
19      raw: None,
20    }))
21  }
22  fn make_str_expr(val: &str) -> Expr {
23    Expr::Lit(Lit::Str(Str {
24      value: val.into(),
25      span: Default::default(),
26      raw: None,
27    }))
28  }
29  fn make_ident_expr(name: &str) -> Expr {
30    Expr::Ident(Ident {
31      span: Default::default(),
32      sym: name.into(),
33      optional: false,
34      ctxt: SyntaxContext::empty(),
35    })
36  }
37
38  #[test]
39  fn string_to_prop_name_with_quotes() {
40    let keys_with_quotes = vec!["2ip", "123", "1b3", "1bc", "2xl", "x*x", "x-x", "x,x"];
41
42    for key in keys_with_quotes {
43      assert!(
44        convert_string_to_prop_name(key).unwrap().is_str(),
45        "Key '{}' should be wrapped in quotes",
46        key
47      );
48    }
49  }
50
51  #[test]
52  fn string_to_prop_name_without_quotes() {
53    let keys_without_quotes = vec![
54      "_abc_",
55      "_ABC_",
56      "$123AB",
57      "$abc_",
58      "$abc$",
59      "$ABC$",
60      "$ABC123",
61      "abc_",
62      "abc",
63      "ABC",
64      "abc$",
65      "break",
66      "case",
67      "catch",
68      "class",
69      "const",
70      "continue",
71      "debugger",
72      "default",
73      "delete",
74      "do",
75      "else",
76      "export",
77      "extends",
78      "false",
79      "finally",
80      "for",
81      "function",
82      "if",
83      "import",
84      "in",
85      "instanceof",
86      "new",
87      "null",
88      "return",
89      "super",
90      "switch",
91      "this",
92      "throw",
93      "true",
94      "try",
95      "typeof",
96      "var",
97      "void",
98      "while",
99      "with",
100      "x_x",
101      "x$x",
102      "xl",
103    ];
104
105    for key in keys_without_quotes {
106      assert!(
107        convert_string_to_prop_name(key).unwrap().is_ident(),
108        "Key '{}' should not be wrapped in quotes",
109        key
110      );
111    }
112  }
113
114  #[test]
115  fn test_binary_expr_to_num_arithmetic() {
116    let mut state = EvaluationState::new();
117    let mut traversal_state = StateManager::default();
118    let fns = FunctionMap::default();
119    let left = Box::new(make_num_expr(10.0));
120    let right = Box::new(make_num_expr(2.0));
121    let ops = [
122      BinaryOp::Add,
123      BinaryOp::Sub,
124      BinaryOp::Mul,
125      BinaryOp::Div,
126      BinaryOp::Mod,
127      BinaryOp::Exp,
128    ];
129    let expected = [12.0, 8.0, 20.0, 5.0, 0.0, 100.0];
130    for (op, exp) in ops.iter().zip(expected.iter()) {
131      let bin = BinExpr {
132        op: *op,
133        left: left.clone(),
134        right: right.clone(),
135        span: Default::default(),
136      };
137      let res = binary_expr_to_num(&bin, &mut state, &mut traversal_state, &fns).unwrap();
138      match res {
139        BinaryExprType::Number(n) => assert_eq!(n, *exp),
140        _ => panic!("Expected number result"),
141      }
142    }
143  }
144
145  #[test]
146  fn test_binary_expr_to_num_comparison() {
147    let mut state = EvaluationState::new();
148    let mut traversal_state = StateManager::default();
149    let fns = FunctionMap::default();
150    let left = Box::new(make_num_expr(10.0));
151    let right = Box::new(make_num_expr(2.0));
152    let cases = [
153      (BinaryOp::Lt, 0.0),
154      (BinaryOp::LtEq, 0.0),
155      (BinaryOp::Gt, 1.0),
156      (BinaryOp::GtEq, 1.0),
157      (BinaryOp::EqEq, 0.0),
158      (BinaryOp::NotEq, 1.0),
159      (BinaryOp::EqEqEq, 0.0),
160      (BinaryOp::NotEqEq, 1.0),
161    ];
162    for (op, exp) in cases.iter() {
163      let bin = BinExpr {
164        op: *op,
165        left: left.clone(),
166        right: right.clone(),
167        span: Default::default(),
168      };
169      let res = binary_expr_to_num(&bin, &mut state, &mut traversal_state, &fns).unwrap();
170      match res {
171        BinaryExprType::Number(n) => assert_eq!(n, *exp),
172        _ => panic!("Expected number result"),
173      }
174    }
175  }
176
177  #[test]
178  fn test_binary_expr_to_num_bitwise() {
179    let mut state = EvaluationState::new();
180    let mut traversal_state = StateManager::default();
181    let fns = FunctionMap::default();
182    let left = Box::new(make_num_expr(6.0));
183    let right = Box::new(make_num_expr(3.0));
184    let cases = [
185      (BinaryOp::BitAnd, 2.0),
186      (BinaryOp::BitOr, 7.0),
187      (BinaryOp::BitXor, 5.0),
188      (BinaryOp::RShift, 0.0),
189      (BinaryOp::LShift, 48.0),
190      (BinaryOp::ZeroFillRShift, 0.0),
191    ];
192    for (op, exp) in cases.iter() {
193      let bin = BinExpr {
194        op: *op,
195        left: left.clone(),
196        right: right.clone(),
197        span: Default::default(),
198      };
199      let res = binary_expr_to_num(&bin, &mut state, &mut traversal_state, &fns).unwrap();
200      match res {
201        BinaryExprType::Number(n) => assert_eq!(n, *exp),
202        _ => panic!("Expected number result"),
203      }
204    }
205  }
206
207  #[test]
208  fn test_binary_expr_to_num_logical() {
209    let mut state = EvaluationState::new();
210    let mut traversal_state = StateManager::default();
211    let fns = FunctionMap::default();
212    let left = Box::new(make_num_expr(0.0));
213    let right = Box::new(make_num_expr(5.0));
214    let bin_or = BinExpr {
215      op: BinaryOp::LogicalOr,
216      left: left.clone(),
217      right: right.clone(),
218      span: Default::default(),
219    };
220    let res_or = binary_expr_to_num(&bin_or, &mut state, &mut traversal_state, &fns).unwrap();
221    match res_or {
222      BinaryExprType::Number(n) => assert_eq!(n, 5.0),
223      _ => panic!("Expected number result"),
224    }
225    let left = Box::new(make_num_expr(2.0));
226    let right = Box::new(make_num_expr(0.0));
227    let bin_and = BinExpr {
228      op: BinaryOp::LogicalAnd,
229      left: left.clone(),
230      right: right.clone(),
231      span: Default::default(),
232    };
233    let res_and = binary_expr_to_num(&bin_and, &mut state, &mut traversal_state, &fns).unwrap();
234    match res_and {
235      BinaryExprType::Number(n) => assert_eq!(n, 0.0),
236      _ => panic!("Expected number result"),
237    }
238    let left = Box::new(make_num_expr(0.0));
239    let right = Box::new(make_num_expr(7.0));
240    let bin_nullish = BinExpr {
241      op: BinaryOp::NullishCoalescing,
242      left: left.clone(),
243      right: right.clone(),
244      span: Default::default(),
245    };
246    let res_nullish =
247      binary_expr_to_num(&bin_nullish, &mut state, &mut traversal_state, &fns).unwrap();
248    match res_nullish {
249      BinaryExprType::Number(n) => assert_eq!(n, 7.0),
250      _ => panic!("Expected number result"),
251    }
252  }
253
254  #[test]
255  fn test_binary_expr_to_string_add() {
256    let mut state = EvaluationState::new();
257    let mut traversal_state = StateManager::default();
258    let fns = FunctionMap::default();
259    let left = Box::new(make_str_expr("foo"));
260    let right = Box::new(make_str_expr("bar"));
261    let bin = BinExpr {
262      op: BinaryOp::Add,
263      left,
264      right,
265      span: Default::default(),
266    };
267    let res = binary_expr_to_string(&bin, &mut state, &mut traversal_state, &fns).unwrap();
268    match res {
269      BinaryExprType::String(s) => assert_eq!(s, "foobar"),
270      _ => panic!("Expected string result"),
271    }
272  }
273
274  #[test]
275  #[should_panic]
276  fn test_binary_expr_to_string_non_add() {
277    let mut state = EvaluationState::new();
278    let mut traversal_state = StateManager::default();
279    let fns = FunctionMap::default();
280    let left = Box::new(make_str_expr("foo"));
281    let right = Box::new(make_str_expr("bar"));
282    let bin = BinExpr {
283      op: BinaryOp::Sub,
284      left,
285      right,
286      span: Default::default(),
287    };
288    let _ = binary_expr_to_string(&bin, &mut state, &mut traversal_state, &fns);
289  }
290
291  #[test]
292  fn test_binary_expr_to_num_in_operator() {
293    let mut state = EvaluationState::new();
294    let mut traversal_state = StateManager::default();
295    let fns = FunctionMap::default();
296    let left = Box::new(make_num_expr(10.0));
297    let right_zero = Box::new(make_num_expr(0.0));
298    let right_non_zero = Box::new(make_num_expr(1.0));
299
300    let bin_zero = BinExpr {
301      op: BinaryOp::In,
302      left: left.clone(),
303      right: right_zero,
304      span: Default::default(),
305    };
306    let res_zero = binary_expr_to_num(&bin_zero, &mut state, &mut traversal_state, &fns).unwrap();
307    match res_zero {
308      BinaryExprType::Number(n) => assert_eq!(n, 1.0),
309      _ => panic!("Expected number result"),
310    }
311
312    let bin_non_zero = BinExpr {
313      op: BinaryOp::In,
314      left,
315      right: right_non_zero,
316      span: Default::default(),
317    };
318    let res_non_zero =
319      binary_expr_to_num(&bin_non_zero, &mut state, &mut traversal_state, &fns).unwrap();
320    match res_non_zero {
321      BinaryExprType::Number(n) => assert_eq!(n, 0.0),
322      _ => panic!("Expected number result"),
323    }
324  }
325
326  #[test]
327  fn test_binary_expr_to_num_instanceof_operator() {
328    let mut state = EvaluationState::new();
329    let mut traversal_state = StateManager::default();
330    let fns = FunctionMap::default();
331    let left = Box::new(make_num_expr(10.0));
332    let right_zero = Box::new(make_num_expr(0.0));
333    let right_non_zero = Box::new(make_num_expr(2.0));
334
335    let bin_zero = BinExpr {
336      op: BinaryOp::InstanceOf,
337      left: left.clone(),
338      right: right_zero,
339      span: Default::default(),
340    };
341    let res_zero = binary_expr_to_num(&bin_zero, &mut state, &mut traversal_state, &fns).unwrap();
342    match res_zero {
343      BinaryExprType::Number(n) => assert_eq!(n, 1.0),
344      _ => panic!("Expected number result"),
345    }
346
347    let bin_non_zero = BinExpr {
348      op: BinaryOp::InstanceOf,
349      left,
350      right: right_non_zero,
351      span: Default::default(),
352    };
353    let res_non_zero =
354      binary_expr_to_num(&bin_non_zero, &mut state, &mut traversal_state, &fns).unwrap();
355    match res_non_zero {
356      BinaryExprType::Number(n) => assert_eq!(n, 0.0),
357      _ => panic!("Expected number result"),
358    }
359  }
360
361  #[test]
362  fn test_binary_expr_add_strings_returns_string() {
363    let mut state = EvaluationState::new();
364    let mut traversal_state = StateManager::default();
365    let fns = FunctionMap::default();
366    let left = Box::new(make_str_expr("foo"));
367    let right = Box::new(make_str_expr("bar"));
368    let bin = BinExpr {
369      op: BinaryOp::Add,
370      left,
371      right,
372      span: Default::default(),
373    };
374    let res = binary_expr_to_string(&bin, &mut state, &mut traversal_state, &fns).unwrap();
375    match res {
376      BinaryExprType::String(s) => assert_eq!(s, "foobar"),
377      _ => panic!("Expected string result from string addition in num evaluator"),
378    }
379  }
380
381  #[test]
382  fn test_binary_expr_to_num_left_unresolved_returns_err() {
383    let mut state = EvaluationState::new();
384    let mut traversal_state = StateManager::default();
385    let fns = FunctionMap::default();
386    let left = Box::new(make_ident_expr("x"));
387    let right = Box::new(make_num_expr(1.0));
388    let bin = BinExpr {
389      op: BinaryOp::Add,
390      left,
391      right,
392      span: Default::default(),
393    };
394    let res = binary_expr_to_num(&bin, &mut state, &mut traversal_state, &fns);
395    assert!(
396      res.is_err(),
397      "Expected error when left side is unresolved and state is not confident"
398    );
399  }
400
401  #[test]
402  fn test_binary_expr_to_num_logical_or_with_unresolved_right_returns_left() {
403    let mut state = EvaluationState::new();
404    let mut traversal_state = StateManager::default();
405    let fns = FunctionMap::default();
406    let left = Box::new(make_num_expr(3.0));
407    let right = Box::new(make_ident_expr("unknown"));
408    let bin = BinExpr {
409      op: BinaryOp::LogicalOr,
410      left,
411      right: right.clone(),
412      span: Default::default(),
413    };
414    let res = binary_expr_to_num(&bin, &mut state, &mut traversal_state, &fns).unwrap();
415
416    match res {
417      BinaryExprType::Number(n) => assert_eq!(n, 3.0),
418      _ => panic!(
419        "Expected number result equal to left operand when right is unresolved for LogicalOr"
420      ),
421    }
422
423    let left = Box::new(make_num_expr(0.0));
424
425    let bin = BinExpr {
426      op: BinaryOp::LogicalOr,
427      left,
428      right,
429      span: Default::default(),
430    };
431
432    let res = binary_expr_to_num(&bin, &mut state, &mut traversal_state, &fns);
433
434    assert!(
435      res.is_err(),
436      "Expected error when left side is unresolved and state is not confident"
437    );
438  }
439
440  #[test]
441  fn test_binary_expr_to_string_right_unresolved_returns_null_on_add() {
442    let mut state = EvaluationState::new();
443    // Force non-confident path on unresolved right
444    state.confident = false;
445    let mut traversal_state = StateManager::default();
446    let fns = FunctionMap::default();
447    let left = Box::new(make_str_expr("foo"));
448    let right = Box::new(make_ident_expr("bar"));
449    let bin = BinExpr {
450      op: BinaryOp::Add,
451      left,
452      right,
453      span: Default::default(),
454    };
455    let res = binary_expr_to_string(&bin, &mut state, &mut traversal_state, &fns);
456    assert!(
457      res.is_err(),
458      "Expected error when right side is unresolved and op is Add in string evaluator"
459    );
460  }
461
462  #[test]
463  fn test_binary_expr_to_string_right_unresolved_logical_or_returns_left() {
464    let mut state = EvaluationState::new();
465    let mut traversal_state = StateManager::default();
466    let fns = FunctionMap::default();
467    let left = Box::new(make_str_expr("foo"));
468    let right = Box::new(make_ident_expr("baz"));
469    let bin = BinExpr {
470      op: BinaryOp::LogicalOr,
471      left,
472      right,
473      span: Default::default(),
474    };
475    let res = binary_expr_to_string(&bin, &mut state, &mut traversal_state, &fns).unwrap();
476    match res {
477      BinaryExprType::String(s) => assert_eq!(s, "foo"),
478      _ => panic!("Expected left string when right is unresolved and op is LogicalOr"),
479    }
480  }
481
482  #[test]
483  fn test_simple_tpl_to_string_without_expressions() {
484    use crate::shared::utils::ast::convertors::convert_tpl_to_string_lit;
485    use swc_core::ecma::ast::{Tpl, TplElement};
486
487    // Create a simple template literal: `hello world`
488    let tpl = Tpl {
489      span: Default::default(),
490      exprs: vec![],
491      quasis: vec![TplElement {
492        span: Default::default(),
493        tail: true,
494        cooked: Some("hello world".into()),
495        raw: "hello world".into(),
496      }],
497    };
498
499    let result = convert_tpl_to_string_lit(&tpl);
500    assert!(result.is_some(), "Should convert simple template to string");
501
502    if let Some(Lit::Str(str_lit)) = result {
503      assert_eq!(
504        str_lit.value.as_str().expect("Failed to get string Value"),
505        "hello world"
506      );
507    } else {
508      panic!("Expected Lit::Str");
509    }
510  }
511
512  #[test]
513  fn test_simple_tpl_to_string_with_expressions() {
514    use crate::shared::utils::ast::convertors::convert_tpl_to_string_lit;
515    use swc_core::ecma::ast::{Tpl, TplElement};
516
517    // Create a template literal with expressions: `hello ${name}`
518    let tpl = Tpl {
519      span: Default::default(),
520      exprs: vec![Box::new(make_ident_expr("name"))],
521      quasis: vec![
522        TplElement {
523          span: Default::default(),
524          tail: false,
525          cooked: Some("hello ".into()),
526          raw: "hello ".into(),
527        },
528        TplElement {
529          span: Default::default(),
530          tail: true,
531          cooked: Some("".into()),
532          raw: "".into(),
533        },
534      ],
535    };
536
537    let result = convert_tpl_to_string_lit(&tpl);
538    assert!(
539      result.is_none(),
540      "Should not convert template with expressions"
541    );
542  }
543
544  #[test]
545  fn test_convert_simple_tpl_to_str_expr() {
546    use crate::shared::utils::ast::convertors::convert_simple_tpl_to_str_expr;
547    use swc_core::ecma::ast::{Tpl, TplElement};
548
549    // Create a simple template literal
550    let tpl = Tpl {
551      span: Default::default(),
552      exprs: vec![],
553      quasis: vec![TplElement {
554        span: Default::default(),
555        tail: true,
556        cooked: Some("var(--font-geist-sans), sans-serif".into()),
557        raw: "var(--font-geist-sans), sans-serif".into(),
558      }],
559    };
560
561    let expr = Expr::Tpl(tpl);
562    let result = convert_simple_tpl_to_str_expr(expr);
563
564    match result {
565      Expr::Lit(Lit::Str(str_lit)) => {
566        assert_eq!(
567          str_lit.value.as_str().expect("Failed to get string Value"),
568          "var(--font-geist-sans), sans-serif"
569        );
570      },
571      _ => panic!("Expected Expr::Lit(Lit::Str)"),
572    }
573  }
574
575  #[test]
576  fn test_convert_simple_tpl_to_str_expr_with_expressions() {
577    use crate::shared::utils::ast::convertors::convert_simple_tpl_to_str_expr;
578    use swc_core::ecma::ast::{Tpl, TplElement};
579
580    // Create a template with expressions
581    let tpl = Tpl {
582      span: Default::default(),
583      exprs: vec![Box::new(make_ident_expr("value"))],
584      quasis: vec![
585        TplElement {
586          span: Default::default(),
587          tail: false,
588          cooked: Some("prefix ".into()),
589          raw: "prefix ".into(),
590        },
591        TplElement {
592          span: Default::default(),
593          tail: true,
594          cooked: Some(" suffix".into()),
595          raw: " suffix".into(),
596        },
597      ],
598    };
599
600    let expr = Expr::Tpl(tpl.clone());
601    let result = convert_simple_tpl_to_str_expr(expr);
602
603    // Should remain as Tpl since it has expressions
604    match result {
605      Expr::Tpl(_) => {
606        // This is expected
607      },
608      _ => panic!("Expected Expr::Tpl to remain unchanged"),
609    }
610  }
611
612  #[test]
613  fn test_convert_concat_to_tpl_expr_simple() {
614    use crate::shared::utils::ast::convertors::convert_concat_to_tpl_expr;
615    use swc_core::ecma::ast::{CallExpr, Callee, ExprOrSpread, MemberExpr, MemberProp};
616
617    // Create: "hello".concat("world")
618    let call_expr = CallExpr {
619      span: Default::default(),
620      callee: Callee::Expr(Box::new(Expr::Member(MemberExpr {
621        span: Default::default(),
622        obj: Box::new(make_str_expr("hello")),
623        prop: MemberProp::Ident(IdentName {
624          span: Default::default(),
625          sym: "concat".into(),
626        }),
627      }))),
628      args: vec![ExprOrSpread {
629        spread: None,
630        expr: Box::new(make_str_expr("world")),
631      }],
632      ..Default::default()
633    };
634
635    let expr = Expr::Call(call_expr);
636    let result = convert_concat_to_tpl_expr(expr);
637
638    // Should be converted to template literal: `hello${world}`
639    match result {
640      Expr::Tpl(tpl) => {
641        assert_eq!(tpl.quasis.len(), 2, "Should have 2 quasis");
642        assert_eq!(tpl.exprs.len(), 1, "Should have 1 expression");
643        assert_eq!(
644          tpl.quasis[0]
645            .cooked
646            .as_ref()
647            .expect("Failed to get string value"),
648          "hello",
649          "First quasi should be 'hello'"
650        );
651      },
652      _ => panic!("Expected Expr::Tpl"),
653    }
654  }
655
656  #[test]
657  fn test_convert_concat_to_tpl_expr_multiple_args() {
658    use crate::shared::utils::ast::convertors::convert_concat_to_tpl_expr;
659    use swc_core::ecma::ast::{CallExpr, Callee, ExprOrSpread, MemberExpr, MemberProp};
660
661    // Create: "prefix".concat(var1, var2, var3)
662    let call_expr = CallExpr {
663      span: Default::default(),
664      callee: Callee::Expr(Box::new(Expr::Member(MemberExpr {
665        span: Default::default(),
666        obj: Box::new(make_str_expr("prefix")),
667        prop: MemberProp::Ident(IdentName {
668          span: Default::default(),
669          sym: "concat".into(),
670        }),
671      }))),
672      args: vec![
673        ExprOrSpread {
674          spread: None,
675          expr: Box::new(make_ident_expr("var1")),
676        },
677        ExprOrSpread {
678          spread: None,
679          expr: Box::new(make_ident_expr("var2")),
680        },
681        ExprOrSpread {
682          spread: None,
683          expr: Box::new(make_ident_expr("var3")),
684        },
685      ],
686      ..Default::default()
687    };
688
689    let expr = Expr::Call(call_expr);
690    let result = convert_concat_to_tpl_expr(expr);
691
692    // Should be converted to template literal: `prefix${var1}${var2}${var3}`
693    match result {
694      Expr::Tpl(tpl) => {
695        assert_eq!(tpl.quasis.len(), 4, "Should have 4 quasis");
696        assert_eq!(tpl.exprs.len(), 3, "Should have 3 expressions");
697        assert_eq!(
698          tpl.quasis[0]
699            .cooked
700            .as_ref()
701            .expect("Failed to get cooked value"),
702          "prefix",
703          "First quasi should be 'prefix'"
704        );
705        assert!(tpl.quasis[3].tail, "Last quasi should have tail=true");
706      },
707      _ => panic!("Expected Expr::Tpl"),
708    }
709  }
710
711  #[test]
712  fn test_convert_concat_to_tpl_expr_not_concat_method() {
713    use crate::shared::utils::ast::convertors::convert_concat_to_tpl_expr;
714    use swc_core::ecma::ast::{CallExpr, Callee, ExprOrSpread, MemberExpr, MemberProp};
715
716    // Create: "hello".split("world") - not a concat call
717    let call_expr = CallExpr {
718      span: Default::default(),
719      callee: Callee::Expr(Box::new(Expr::Member(MemberExpr {
720        span: Default::default(),
721        obj: Box::new(make_str_expr("hello")),
722        prop: MemberProp::Ident(IdentName {
723          span: Default::default(),
724          sym: "split".into(), // Not "concat"
725        }),
726      }))),
727      args: vec![ExprOrSpread {
728        spread: None,
729        expr: Box::new(make_str_expr("world")),
730      }],
731      ..Default::default()
732    };
733
734    let original_expr = Expr::Call(call_expr.clone());
735    let result = convert_concat_to_tpl_expr(original_expr);
736
737    // Should remain as CallExpr since it's not concat
738    match result {
739      Expr::Call(_) => {
740        // This is expected - should remain unchanged
741      },
742      _ => panic!("Expected Expr::Call to remain unchanged"),
743    }
744  }
745
746  #[test]
747  fn test_convert_concat_to_tpl_expr_non_call_expr() {
748    use crate::shared::utils::ast::convertors::convert_concat_to_tpl_expr;
749
750    // Test with a non-call expression (e.g., just a string)
751    let expr = make_str_expr("hello");
752    let result = convert_concat_to_tpl_expr(expr);
753
754    // Should remain as string literal
755    match result {
756      Expr::Lit(Lit::Str(str_lit)) => {
757        assert_eq!(
758          str_lit.value.as_str().expect("Failed to get string value"),
759          "hello"
760        );
761      },
762      _ => panic!("Expected Expr::Lit(Lit::Str) to remain unchanged"),
763    }
764  }
765
766  #[test]
767  fn test_convert_concat_to_tpl_expr_with_spread() {
768    use crate::shared::utils::ast::convertors::convert_concat_to_tpl_expr;
769    use swc_core::ecma::ast::{CallExpr, Callee, ExprOrSpread, MemberExpr, MemberProp};
770
771    // Create: "prefix".concat(...args) - with spread argument
772    let call_expr = CallExpr {
773      span: Default::default(),
774      callee: Callee::Expr(Box::new(Expr::Member(MemberExpr {
775        span: Default::default(),
776        obj: Box::new(make_str_expr("prefix")),
777        prop: MemberProp::Ident(IdentName {
778          span: Default::default(),
779          sym: "concat".into(),
780        }),
781      }))),
782      args: vec![ExprOrSpread {
783        spread: Some(Default::default()),
784        expr: Box::new(make_ident_expr("args")),
785      }],
786      ..Default::default()
787    };
788
789    let expr = Expr::Call(call_expr);
790    let result = convert_concat_to_tpl_expr(expr);
791
792    // Should still convert but skip spread arguments
793    match result {
794      Expr::Tpl(tpl) => {
795        assert_eq!(
796          tpl.quasis.len(),
797          1,
798          "Should have 1 quasi (spread args are skipped)"
799        );
800        assert_eq!(
801          tpl.exprs.len(),
802          0,
803          "Should have 0 expressions (spread args are skipped)"
804        );
805      },
806      _ => panic!("Expected Expr::Tpl"),
807    }
808  }
809}