import * as util from "../util";
import { TestBuilder } from "../util";
import { JsxEmit } from "typescript";
import { unsupportedJsxEmit } from "../../src/transpilation/diagnostics";
import { unsupportedNodeKind } from "../../src/transformation/utils/diagnostics";
// language=TypeScript
const reactLib = `
export class Component {
static isClass = true
}
namespace React {
const isLua = typeof Component === "object"
export function createElement(
type: any,
props?: any,
...children: any[]
) {
let typeStr: string
if (isLua) {
typeStr =
typeof type === "function" ? "<< function >>" :
typeof type === "object" ? \`<< class \${type.name} >>\` :
type
} else {
typeStr = typeof type === "function" ? (type.isClass
? \`<< class \${type.name} >>\`
: "<< function >>")
: type
}
return {
type: typeStr,
props,
children
};
}
export class Fragment extends Component {
}
}
export default React;
`;
// language=TypeScript
const jsxTypings = `declare namespace JSX {
interface IntrinsicElements {
a: any
b: any
foo: any
bar: any
div: any
"with-dash": any
}
}
`;
function testJsx(...args: [string] | [TemplateStringsArray, ...any[]]): TestBuilder {
return util
.testFunction(...args)
.setOptions({
jsx: JsxEmit.React,
})
.setMainFileName("main.tsx")
.addExtraFile("react.ts", reactLib)
.addExtraFile("jsx.d.ts", jsxTypings)
.setTsHeader('import React, { Component } from "./react";');
}
describe("jsx", () => {
test("element", () => {
testJsx`
return
`.expectToMatchJsResult();
});
test("self closing element", () => {
testJsx`
return
`.expectToMatchJsResult();
});
test("element with dash name", () => {
testJsx`
return
`.expectToMatchJsResult();
});
test("custom element", () => {
testJsx`
function Foo() {}
return
`.expectToMatchJsResult();
});
test("fragment", () => {
testJsx`
return <>>
`.expectToMatchJsResult();
});
test("fragment with children", () => {
testJsx`
return <>>
`.expectToMatchJsResult();
});
test("esoteric component names", () => {
testJsx`
class _Foo extends Component {}
return <_Foo />
`.expectToMatchJsResult();
testJsx`
class $ extends Component {}
return <$ />
`.expectToMatchJsResult();
testJsx`
class é extends Component {}
return <é />
`.expectToMatchJsResult();
});
test("nested elements", () => {
testJsx`
return
`.expectToMatchJsResult();
});
test("many nested elements", () => {
testJsx`
return
`.expectToMatchJsResult();
});
test("interpolated children", () => {
testJsx`
const x = 3
return {x}
`.expectToMatchJsResult();
});
test("string prop", () => {
testJsx`
return
`.expectToMatchJsResult();
});
test("value prop", () => {
testJsx`
const x = 5
return
`.expectToMatchJsResult();
});
test("quoted prop", () => {
testJsx`
return
`.expectToMatchJsResult();
});
test("shorthand prop", () => {
testJsx`
return
`.expectToMatchJsResult();
});
test("spaces in jsxText", () => {
testJsx`
return this
is somemultiline
text thing.
`.expectToMatchJsResult();
});
test("multiline string jsxText", () => {
testJsx`
return
foo bar
baz
`.expectToMatchJsResult();
});
test("access tag value", () => {
testJsx`
const a = { b(){} };
return
`.expectToMatchJsResult();
testJsx`
const a = { b: { c: { d(){} } } };
return
`.expectToMatchJsResult();
});
test("spread props", () => {
testJsx`
const x = {c: "d", e: "f"}
return
`.expectToMatchJsResult();
testJsx`
const x = {c: "d", e: "no"}
return
`.expectToMatchJsResult();
});
test("comment children", () => {
testJsx`
return
{/* comment */}
{/* another comment */}
`.expectToMatchJsResult();
testJsx`
return
{/* comment */}
`.expectToMatchJsResult();
});
test("multiline string prop value", () => {
testJsx`
return
`.expectToMatchJsResult();
testJsx`
return
`.expectToMatchJsResult();
});
test("prop strings with entities", () => {
testJsx`
return
`.expectToMatchJsResult();
});
test("jsxText with entities", () => {
testJsx`
return 9+10<21
`.expectToMatchJsResult();
});
test("Spread children", () => {
// doesn't actually "spread" (typescript's current behavior)
testJsx`
const children = [, ]
return {...children}
`.expectToMatchJsResult();
});
test("complex", () => {
testJsx`
const x = 3
const props = {one: "two", three: 4}
return
`.expectToMatchJsResult();
});
// language=TypeScript
const customJsxLib = `export namespace MyLib {
export function myCreate(
type: any,
props: any,
...children: any[]
) {
return { type: typeof type, props, children, myThing: true };
}
export function MyFragment() {
}
}
`;
test("custom JSX factory", () => {
testJsx`
return c
`
.setTsHeader('import { MyLib } from "./myJsx";')
.setOptions({ jsxFactory: "MyLib.myCreate" })
.addExtraFile("myJsx.ts", customJsxLib)
.expectToMatchJsResult();
testJsx`
return c
`
.setTsHeader('import { MyLib } from "./myJsx";const myCreate2 = MyLib.myCreate;')
.setOptions({ jsxFactory: "myCreate2" })
.addExtraFile("myJsx.ts", customJsxLib)
.expectToMatchJsResult();
});
test("custom JSX factory with noImplicitSelf", () => {
testJsx`
return c
`
.setTsHeader(
`function createElement(tag: string | Function, props: { [key: string]: string | boolean }, ...children: any[]) {
return { tag, children };
}`
)
.setOptions({ jsxFactory: "createElement", noImplicitSelf: true })
.expectToMatchJsResult();
});
test("custom fragment factory", () => {
testJsx`
return <>c>
`
.setTsHeader('import { MyLib } from "./myJsx";')
.setOptions({ jsxFactory: "MyLib.myCreate", jsxFragmentFactory: "MyLib.MyFragment" })
.addExtraFile("myJsx.ts", customJsxLib)
.expectToMatchJsResult();
testJsx`
return <>c>
`
.setTsHeader('import { MyLib } from "./myJsx";function MyFragment2(){};')
.setOptions({ jsxFactory: "MyLib.myCreate", jsxFragmentFactory: "MyFragment2" })
.addExtraFile("myJsx.ts", customJsxLib)
.expectToMatchJsResult();
});
test("custom JSX pragma", () => {
testJsx`
return c
`
.setTsHeader('/** @jsx MyLib.myCreate */\nimport { MyLib } from "./myJsx";')
.addExtraFile("myJsx.ts", customJsxLib)
.expectToMatchJsResult();
testJsx`
return c
`
.setTsHeader('/** @jsx myCreate2 */import { MyLib } from "./myJsx";const myCreate2 = MyLib.myCreate;')
.addExtraFile("myJsx.ts", customJsxLib)
.expectToMatchJsResult();
});
test("custom fragment pragma", () => {
testJsx`
return <>c>
`
.setTsHeader(
'/** @jsx MyLib.myCreate */\n/** @jsxFrag MyLib.MyFragment */\nimport { MyLib } from "./myJsx";'
)
.addExtraFile("myJsx.ts", customJsxLib)
.expectToMatchJsResult();
testJsx`
return <>c>
`
.setTsHeader(
"/** @jsx MyLib.myCreate */\n/** @jsxFrag MyFragment2 */\n" +
'import { MyLib } from "./myJsx";function MyFragment2(){};'
)
.setOptions({ jsxFactory: "MyLib.myCreate", jsxFragmentFactory: "MyFragment" })
.addExtraFile("myJsx.ts", customJsxLib)
.expectToMatchJsResult();
});
test("forward declare components", () => {
testJsx`
const foo =
function Foo(){}
return foo
`.expectToMatchJsResult();
});
test("invalid jsx config", () => {
testJsx(`
return
`)
.setOptions({
jsx: JsxEmit.Preserve,
})
.expectToHaveDiagnostics([unsupportedJsxEmit.code, unsupportedNodeKind.code]);
});
});