package bundler import "testing" // parseOne parses a single JSX element starting at the first '<'. func parseOne(t *testing.T, src string) jsxNode { t.Helper() i := 0 for i < len(src) && src[i] != '<' { i++ } node, next, err := parseJSX(src, i) if err != nil { t.Fatalf("parseJSX(%q): %v", src, err) } if next != len(src) { t.Fatalf("parseJSX(%q): consumed to %d, want %d (trailing %q)", src, next, len(src), src[next:]) } return node } func TestParseStaticElement(t *testing.T) { n := parseOne(t, `
hi
`) if n.kind != jsxElement || n.tag != "div" { t.Fatalf("kind/tag = %d/%q", n.kind, n.tag) } if len(n.attrs) != 1 || n.attrs[0].kind != attrStatic || n.attrs[0].name != "class" || n.attrs[0].value != "x" { t.Fatalf("attrs = %+v", n.attrs) } if len(n.children) != 1 || n.children[0].kind != jsxText || n.children[0].text != "hi" { t.Fatalf("children = %+v", n.children) } } func TestParseComponent(t *testing.T) { n := parseOne(t, `child`) if n.kind != jsxComponent || n.tag != "Foo" { t.Fatalf("kind/tag = %d/%q", n.kind, n.tag) } if len(n.attrs) != 1 || n.attrs[0].kind != attrExpr || n.attrs[0].name != "bar" || n.attrs[0].expr != "1" { t.Fatalf("attrs = %+v", n.attrs) } } func TestParseNested(t *testing.T) { n := parseOne(t, `
a{x()}
`) if len(n.children) != 2 { t.Fatalf("want 2 element children, got %d: %+v", len(n.children), n.children) } if n.children[0].tag != "span" || n.children[1].tag != "b" { t.Fatalf("child tags = %q, %q", n.children[0].tag, n.children[1].tag) } b := n.children[1] if len(b.children) != 1 || b.children[0].kind != jsxExpr || b.children[0].expr != "x()" { t.Fatalf("b children = %+v", b.children) } } func TestParseFragment(t *testing.T) { n := parseOne(t, `<>{a()}{b()}`) if n.kind != jsxFragment || len(n.children) != 2 { t.Fatalf("fragment: kind=%d children=%d", n.kind, len(n.children)) } if n.children[0].expr != "a()" || n.children[1].expr != "b()" { t.Fatalf("fragment children = %+v", n.children) } } func TestParseSelfCloseAndBool(t *testing.T) { n := parseOne(t, ``) if n.tag != "input" || len(n.children) != 0 { t.Fatalf("input: tag=%q children=%d", n.tag, len(n.children)) } if len(n.attrs) != 2 || !n.attrs[0].boolt || n.attrs[0].name != "disabled" { t.Fatalf("attrs = %+v", n.attrs) } if n.attrs[1].name != "type" || n.attrs[1].value != "text" { t.Fatalf("type attr = %+v", n.attrs[1]) } } func TestParseSpread(t *testing.T) { n := parseOne(t, `
hi
`) if len(n.attrs) != 2 || n.attrs[0].kind != attrSpread || n.attrs[0].expr != "p" { t.Fatalf("attrs = %+v", n.attrs) } } // The hardest case: an expression child containing nested JSX AND braces inside // strings/templates. The parser must capture the whole expression opaquely. func TestParseExprWithNestedJSX(t *testing.T) { n := parseOne(t, "") if len(n.children) != 1 || n.children[0].kind != jsxExpr { t.Fatalf("children = %+v", n.children) } if n.children[0].expr != "items.map((x) =>
  • {x}
  • )" { t.Fatalf("expr = %q", n.children[0].expr) } } func TestCaptureBracesLexer(t *testing.T) { cases := []struct{ in, want string }{ {`{a + "}" + b}`, `a + "}" + b`}, {"{`a${nested}b`}", "`a${nested}b`"}, {`{ {k: 1} }`, ` {k: 1} `}, {`{f(/[}]/)}`, `f(/[}]/)`}, {`{x} rest`, `x`}, } for _, c := range cases { got, next := captureBraces(c.in, 0) if got != c.want { t.Errorf("captureBraces(%q) = %q, want %q", c.in, got, c.want) } _ = next } }