function test_parse(input, args... ; broken=false)
    arg = (:line, args...)
    if broken
        @test_broken texparse(input) == manual_texexpr(arg)
    else
        @test texparse(input) == manual_texexpr(arg)
    end
end

@testset "Parser" begin
    @testset "Accent" begin
        # First char is the combining accent
        test_parse(
            raw"\dot{a}",
            (:combining_accent, '̇', 'a')
        )
        test_parse(
            raw"\vec{x}",
            (:combining_accent, '⃗', 'x')
        )
    end

    @testset "Showing errors" begin
        @test_throws "tokenized as" texparse("{")
    end

    @testset "Delimiter" begin
        expr = texparse(L"\left(\frac{x}{y}\right)")
        delimited = expr.args[1].args[1]
        @test delimited.args[1].head == :delimiter
        @test delimited.args[2].head == :frac
        @test delimited.args[3].head == :delimiter

        test_parse(
            raw"\left(x\right)",
            (:delimited, '(', 'x', ')')
        )

        test_parse(
            raw"\left[y\right)",
            (:delimited, '[', 'y', ')')
        )

        test_parse(
            raw"\left(a+b\right)",
            (:delimited,
                '(',
                (:group, 'a', (:spaced, '+'), 'b'),
                ')'
            )
        )

        test_parse(raw"\{", (:delimiter, '{'))
        test_parse(raw"\}", (:delimiter, '}'))

        @test_throws TeXParseError texparse(raw"\left( x")
        @test_throws TeXParseError texparse(raw"x \right)")

        ## all `delimiter_symbols`
        test_parse(raw"|", (:delimiter, '|'))
        test_parse(raw"/", (:delimiter, '/'))
        test_parse(raw"\\", (:delimiter, '\\'))
        test_parse(raw"(", (:delimiter, '('))
        test_parse(raw")", (:delimiter, ')'))
        test_parse(raw"[", (:delimiter, '['))
        test_parse(raw"]", (:delimiter, ']'))
        test_parse(raw"⟨", (:delimiter, '⟨'))
        test_parse(raw"⟩", (:delimiter, '⟩'))
        test_parse(raw"‖", (:delimiter, '‖'))
        test_parse(raw"⌈", (:delimiter, '⌈'))
        test_parse(raw"⌉", (:delimiter, '⌉'))
        test_parse(raw"⌊", (:delimiter, '⌊'))
        test_parse(raw"⌋", (:delimiter, '⌋'))
        test_parse(raw"⌜", (:delimiter, '⌜'))
        test_parse(raw"⌝", (:delimiter, '⌝'))
        test_parse(raw"⌞", (:delimiter, '⌞'))
        test_parse(raw"⌟", (:delimiter, '⌟'))

        test_parse(raw"<", (:space, '<'))   # formerly, this was a delimiter
        test_parse(raw">", (:space, '>'))

        ## all `delimiter_commands`
        test_parse(raw"\vert", (:delimiter, '|'))
        test_parse(raw"\slash", (:delimiter, '/'))
        test_parse(raw"\backslash", (:delimiter, '\\'))
        test_parse(raw"\lbrack", (:delimiter, '['))
        test_parse(raw"\rbrack", (:delimiter, ']'))
        test_parse(raw"\langle", (:delimiter, '⟨'))
        test_parse(raw"\rangle", (:delimiter, '⟩'))
        test_parse(raw"\|", (:delimiter, '‖'))
        test_parse(raw"\Vert", (:delimiter, '‖'))
        test_parse(raw"\lceil", (:delimiter, '⌈'))
        test_parse(raw"\rceil", (:delimiter, '⌉'))
        test_parse(raw"\lfloor", (:delimiter, '⌊'))
        test_parse(raw"\lfloor", (:delimiter, '⌊'))
        test_parse(raw"\ulcorner", (:delimiter, '⌜'))
        test_parse(raw"\urcorner", (:delimiter, '⌝'))
        test_parse(raw"\llcorner", (:delimiter, '⌞'))
        test_parse(raw"\lrcorner", (:delimiter, '⌟'))
        test_parse(raw"\{", (:delimiter, '{'))
        test_parse(raw"\}", (:delimiter, '}'))
        test_parse(raw"\lbrace", (:delimiter, '{'))
        test_parse(raw"\rbrace", (:delimiter, '}'))
     
        ### test commands as arguments to a delimited group
        for (cmd_str, delim_symb) in pairs(MathTeXEngine.delimiter_commands)
            ## NOTE this does not check for "correct" left right pairs like `\lbrack` and `\rbrack`
            test_parse(
                "\\left$(cmd_str)\\right$(cmd_str)", 
                (:delimited,
                    (:delimiter, delim_symb),
                    (:group,),
                    (:delimiter, delim_symb)
                )
            )
        end
    end

    @testset "Fonts" begin
        test_parse(raw"\mathrm{u}", (:font, :rm, (:char, 'u')))
        test_parse(raw"\text{u}", (:text, :rm, (:char, 'u')))

        test_parse(raw"\mathrm{u v}", (:text, :rm,
            (:group,
                (:char, 'u'),
                (:char, ' '),
                (:char, 'v')
            )))
        test_parse(raw"\text{u v}", (:text, :rm,
            (:group,
                (:char, 'u'),
                (:char, ' '),
                (:char, 'v')
            )))

        @test texparse(raw"ℝ") == texparse(raw"\mathbb{R}")
    end

    @testset "Fraction" begin
        test_parse(raw"\frac{1}{n}", (:frac, (:digit, '1'), 'n'))
    end

    @testset "Function" begin
        # Check that braces are not added to the function name
        test_parse(raw"\sin{x}", (:function, "sin"), 'x')
        test_parse(
            raw"\exp(x)",
            (:function, "exp"), '(', 'x', ')'
        )
        test_parse(
            raw"\tan\alpha",
            (:function, "tan"), (:symbol, 'α')
        )
    end

    @testset "Group" begin
        test_parse(raw"{x}", 'x')
        test_parse(raw"{fgh}", (:group, 'f', 'g', 'h'))
        test_parse(
            raw"{a{b{cd}}}",
            (:group, 'a', (:group, 'b', (:group, 'c', 'd')))
        )
        test_parse(raw"{}", (:space, 0.0))

        @test_throws TeXParseError texparse(raw"{v")
        @test_throws TeXParseError texparse(raw"w}")
    end

    @testset "Integral" begin
        test_parse(
            raw"\int",
            (:integral, (:symbol, '∫'), nothing, nothing)
        )
        test_parse(
            raw"\int_a^b",
            (:integral, (:symbol, '∫'), 'a', 'b')
        )
    end

    @testset "Linebreak" begin
        expr = texparse(L"$A$\\$B$\\$C$")
        @test expr.head == :lines
        @test length(expr.args) == 3
    end

    @testset "LaTeXString input" begin
        @test texparse(raw"$\gamma$") == texparse(L"\gamma")
    end

    @testset "Overunder" begin
        test_parse(
            raw"\sum",
            (:underover, (:symbol, '∑'), nothing, nothing)
        )
        test_parse(
            raw"\sum_{k=0}^n",
            (:underover,
                (:symbol, '∑'),
                (:group, 'k', (:spaced, (:symbol, '=')), (:digit, '0')),
                'n'
            )
        )
        test_parse(
            raw"\lim_x",
            (:underover,
                (:function, "lim"),
                'x',
                nothing
            )
        )
    end

    @testset "Square root" begin
        test_parse(raw"\sqrt{x}", (:sqrt, 'x'))
        test_parse(
            raw"\sqrt{abc}",
            (:sqrt, (:group, 'a', 'b', 'c'))
        )
    end

    @testset "Overline" begin
        test_parse(raw"\overline{x}", (:overline, 'x'))
        test_parse(
            raw"\overline{abc}",
            (:overline, (:group, 'a', 'b', 'c'))
        )
    end

    @testset "Space" begin
        test_parse(raw"\quad", (:space, 1))
        test_parse(raw"\qquad", (:space, 2))
        test_parse(raw"~", (:space, 0.33333))
    end

    @testset "Spaced symbol" begin
        test_parse(raw"=", (:spaced, (:symbol, '=')))
        test_parse(
            raw"\rightarrow",
            (:spaced, (:symbol, '→'))
        )
        # Hyphen must be replaced by a minus sign
        test_parse(raw"-", (:spaced, (:symbol, '−')))

        test_parse(raw"a-b $c-d$",
                   (:char, 'a'), (:char, '-'), (:char, 'b'),
                   (:char, ' '),
                   (:inline_math,
                    (:char, 'c'),
                    (:spaced, (:symbol, '−')),
                    (:char, 'd')))
    end

    @testset "Unary operator spacing heuristic" begin
        old = MathTeXEngine.unspace_binary_operators_heuristic_enabled[]
        try
            MathTeXEngine.unspace_binary_operators_heuristic_enabled[] = true

            test_parse(raw"$-1$", (:inline_math, (:symbol, '−'), (:digit, '1')))
            test_parse(raw"$2-1$",
                       (:inline_math,
                        (:digit, '2'),
                        (:spaced, (:symbol, '−')),
                        (:digit, '1')))
            test_parse(raw"$\alpha^*$",
                       (:inline_math,
                        (:decorated, (:symbol, 'α'), nothing, (:symbol, '*'))))
            test_parse(raw"$\frac{1}{2}\pm\sqrt{3}$",
                       (:inline_math,
                        (:frac, (:digit, '1'), (:digit, '2')),
                        (:spaced, (:symbol, '±')),
                        (:sqrt, (:digit, '3'))))
            test_parse(raw"$\frac{1}{2}{}\pm\sqrt{3}$",
                       (:inline_math,
                        (:frac, (:digit, '1'), (:digit, '2')),
                        (:space, 0.0),
                        (:symbol, '±'),
                        (:sqrt, (:digit, '3'))))

            MathTeXEngine.unspace_binary_operators_heuristic_enabled[] = false
            test_parse(raw"$-1$", (:inline_math, (:spaced, (:symbol, '−')), (:digit, '1')))
            test_parse(raw"$\alpha^*$",
                       (:inline_math,
                        (:decorated, (:symbol, 'α'), nothing, (:spaced, (:symbol, '*')))))
        finally
            MathTeXEngine.unspace_binary_operators_heuristic_enabled[] = old
        end
    end

    @testset "Subscript and superscript" begin
        @test texparse(raw"a^2_3") == texparse(raw"a_3^2")
        @test texparse(raw"^7_b") == texparse(raw"{}^7_b")
    end

    @testset "Symbol" begin
        test_symbols = [
            ("ϕ", raw"\phi"),
            ("φ", raw"\varphi"),
            ("Φ", raw"\Phi"),
            # The following test symbols with multiple commands
            ("ε", raw"\varepsilon"),
            ("ε", raw"\upepsilon")
        ]

        for (char, sym) in test_symbols
            test_parse(sym, (:symbol, first(char)))
            @test texparse(char) == texparse(sym)
        end

        # Check interaction with decoration
        test_parse(
            raw"ω_k",
            (:decorated, (:symbol, 'ω'), 'k', nothing)
        )
    end
end
