newtests/jsx_pragma/test.js (442 lines of code) (raw):
/*
* @flow
*/
import type {Suite} from "flow-dev-tools/src/test/Suite";
const {suite, test} = require('flow-dev-tools/src/test/Tester');
module.exports = (suite(({addFile, addFiles, addCode}) => [
test('@jsx pragma without expression is disallowed', [
addCode('// @jsx')
.newErrors(
`
test.js:3
3: // @jsx
^^^^ Invalid \`@jsx\` declaration. Should have the form \`@jsx LeftHandSideExpression\` with no spaces. [invalid-jsx-decl]
`,
),
]),
test('@jsx pragma with a non-left-hand-side expression is disallowed', [
addCode('// @jsx (x)=>x')
.newErrors(
`
test.js:3
3: // @jsx (x)=>x
^^^^^^ Invalid \`@jsx\` declaration. Should have the form \`@jsx LeftHandSideExpression\` with no spaces. Parse error: Unexpected token \`=>\`, expected the end of input. [invalid-jsx-decl]
`,
),
]),
test('@jsx pragma with a newline should have the right error location', [
addCode(`
/* @jsx
(x)=>x
*/
`)
.newErrors(
`
test.js:5
5: (x)=>x
^^^^^^ Invalid \`@jsx\` declaration. Should have the form \`@jsx LeftHandSideExpression\` with no spaces. Parse error: Unexpected token \`=>\`, expected the end of input. [invalid-jsx-decl]
`,
),
]),
test('Line comment complex @jsx with unknown identifier points to pragma', [
addCode(`
// @jsx Foo['Bar']
var Bar = 123;
<Bar />;
`)
.newErrors(
`
test.js:4
4: // @jsx Foo['Bar']
^^^ Cannot resolve name \`Foo\`. [cannot-resolve-name]
`,
),
]),
test('Block comment complex @jsx with unknown identifier points to pragma', [
addCode(`
/*
* @jsx Foo['Bar']
*/
var Bar = 123;
<Bar />;
`)
.newErrors(
`
test.js:5
5: * @jsx Foo['Bar']
^^^ Cannot resolve name \`Foo\`. [cannot-resolve-name]
`,
),
]),
test('Simple identifier @jsx with unknown identifier has better location', [
addCode(`
// @jsx Foo
var Bar = 123;
<Bar />;
`)
.newErrors(
`
test.js:6
6: <Bar />;
^^^^^^^ Cannot resolve name \`Foo\`. [cannot-resolve-name]
`,
),
]),
test('Simple member @jsx with unknown identifier has better location', [
addCode(`
// @jsx Foo.baz
var Bar = 123;
<Bar />;
`)
.newErrors(
`
test.js:6
6: <Bar />;
^^^^^^^ Cannot resolve name \`Foo\`. [cannot-resolve-name]
`,
),
]),
test('Should respect local scope', [
addCode(`
// @jsx Foo
const Bar = 123;
function Foo(x: string) {}
<Bar />;
{
const Foo = (y: boolean) => {};
<Bar />;
}
`)
.newErrors(
`
test.js:8
8: <Bar />;
^^^ Cannot create \`Bar\` element because number [1] is incompatible with string [2]. [incompatible-type]
References:
5: const Bar = 123;
^^^ [1]
6: function Foo(x: string) {}
^^^^^^ [2]
test.js:12
12: <Bar />;
^^^ Cannot create \`Bar\` element because number [1] is incompatible with boolean [2]. [incompatible-type]
References:
5: const Bar = 123;
^^^ [1]
11: const Foo = (y: boolean) => {};
^^^^^^^ [2]
`,
),
]),
test('Second arg to jsx function should be props', [
addCode(`
// @jsx Foo
function Foo(elem: number, props: { x: string }) {}
const Bar = 123;
<Bar x={123} />;
`).newErrors(
`
test.js:8
8: <Bar x={123} />;
^^^ Cannot create \`Bar\` element because number [1] is incompatible with string [2] in property \`x\`. [incompatible-type]
References:
8: <Bar x={123} />;
^^^ [1]
5: function Foo(elem: number, props: { x: string }) {}
^^^^^^ [2]
`,
),
]),
test('Second arg to jsx function is null when there are no attributes', [
addCode(`
// @jsx Foo
function Foo(elem: number, props: { x: string }) {}
const Bar = 123;
<Bar />;
`).newErrors(
`
test.js:8
8: <Bar />;
^^^ Cannot create \`Bar\` element because null [1] is incompatible with object type [2]. [incompatible-type]
References:
8: <Bar />;
^^^^^^^ [1]
5: function Foo(elem: number, props: { x: string }) {}
^^^^^^^^^^^^^ [2]
`,
),
]),
test('Children are passed after the element and props', [
addCode(`
// @jsx Foo
function Foo(elem: number, props: null, child1: number, child2: string) {}
const Bar = 123;
<Bar>{true}{/regex/}</Bar>
`).newErrors(
`
test.js:8
8: <Bar>{true}{/regex/}</Bar>
^^^^ Cannot create \`Bar\` element because boolean [1] is incompatible with number [2]. [incompatible-type]
References:
8: <Bar>{true}{/regex/}</Bar>
^^^^ [1]
5: function Foo(elem: number, props: null, child1: number, child2: string) {}
^^^^^^ [2]
test.js:8
8: <Bar>{true}{/regex/}</Bar>
^^^^^^^ Cannot create \`Bar\` element because \`RegExp\` [1] is incompatible with string [2]. [incompatible-type]
References:
8: <Bar>{true}{/regex/}</Bar>
^^^^^^^ [1]
5: function Foo(elem: number, props: null, child1: number, child2: string) {}
^^^^^^ [2]
`,
),
]).flowConfig("_flowconfig_with_flowlib"),
test('React ignores certain props, but @jsx shouldnt', [
addCode(`
// @jsx Foo
function Foo(elem: number, props: {key: boolean, ref: number}) {}
const Bar = 123;
<Bar key="hi" ref="bye" />;
`)
.newErrors(
`
test.js:7
7: <Bar key="hi" ref="bye" />;
^^^^ Cannot create \`Bar\` element because string [1] is incompatible with boolean [2] in property \`key\`. [incompatible-type]
References:
7: <Bar key="hi" ref="bye" />;
^^^^ [1]
5: function Foo(elem: number, props: {key: boolean, ref: number}) {}
^^^^^^^ [2]
test.js:7
7: <Bar key="hi" ref="bye" />;
^^^^^ Cannot create \`Bar\` element because string [1] is incompatible with number [2] in property \`ref\`. [incompatible-type]
References:
7: <Bar key="hi" ref="bye" />;
^^^^^ [1]
5: function Foo(elem: number, props: {key: boolean, ref: number}) {}
^^^^^^ [2]
`,
),
]),
test('jsx intrinsics should pass through a string', [
addCode(`
// @jsx Foo
function Foo(elem: "bar") {}
<baz />;
`)
.newErrors(
`
test.js:7
7: <baz />;
^^^ Cannot create \`baz\` element because \`baz\` [1] is incompatible with string literal \`bar\` [2]. [incompatible-type]
References:
7: <baz />;
^^^ [1]
5: function Foo(elem: "bar") {}
^^^^^ [2]
`,
),
]).flowConfig("_flowconfig_with_flowlib"),
test('JSX element missing property should error', [
addCode(`
// @jsx Foo
function Foo(elem: number, props: {x: string}) {}
const Bar = 123;
<Bar y="hi" />;
`)
.newErrors(
`
test.js:8
8: <Bar y="hi" />;
^^^ Cannot create \`Bar\` element because property \`x\` is missing in props [1] but exists in object type [2]. [prop-missing]
References:
8: <Bar y="hi" />;
^^^^^^^^^^^^^^ [1]
5: function Foo(elem: number, props: {x: string}) {}
^^^^^^^^^^^ [2]
`,
),
]),
test('Missing JSX element', [
addCode(`
// @jsx Foo
function Foo(elem: number) {}
<Bar y="hi" />;
`)
.newErrors(
`
test.js:7
7: <Bar y="hi" />;
^^^ Cannot resolve name \`Bar\`. [cannot-resolve-name]
`,
),
]),
test('Exact prop type without spread should work', [
addCode(`
// @jsx Foo
function Foo(elem: number, props: {| x: string |}) {}
const Bar = 123;
<Bar x="hi" />;
`).noNewErrors(),
]),
test('Spread syntax in children should work', [
addCode(`
// @jsx Foo
function Foo(elem: number, props: null, child1: 'a', child2: 'b', child3: 'c') {}
const Bar = 123;
<Bar>{...["a", "b", "c"]}</Bar>;
`).noNewErrors(),
]),
test('Exact prop type with spread should work', [
addCode(`
// @jsx Foo
function Foo(elem: number, props: {| x: string |}) {}
const Bar = 123;
const props = {x: "hi"};
<Bar {...props} />;
`).noNewErrors(),
]),
test('Whitespace trimming', [
addCode(`
// @jsx Foo
function Foo(
elem: number,
props: null,
child1: 'hello',
child2: boolean,
child3: 'bye',
...rest: Array<void>
) {}
const Bar = 123;
<Bar>
hi
{true}
bye
there
</Bar>;
`).newErrors(
`
test.js:16
16: hi
^^ Cannot create \`Bar\` element because JSX text [1] is incompatible with string literal \`hello\` [2]. [incompatible-type]
References:
16: hi
^^ [1]
8: child1: 'hello',
^^^^^^^ [2]
test.js:18
18: bye
^ Cannot create \`Bar\` element because JSX text [1] is incompatible with string literal \`bye\` [2]. [incompatible-type]
References:
18: bye
^ [1]
10: child3: 'bye',
^^^^^ [2]
`,
),
]),
test('Empty JSXText children are stripped out', [
addCode(`
// @jsx Foo
function Foo(
elem: number,
props: null,
child1: "should be single space",
child2: "should be true",
child3: "should be empty string",
child4: "should be single space",
...rest: Array<void>
) {}
const Bar = 123;
<Bar> {true}
{''} </Bar>;
`)
.newErrors(
`
test.js:16
16: <Bar> {true}
^ Cannot create \`Bar\` element because JSX text [1] is incompatible with string literal \`should be single space\` [2]. [incompatible-type]
References:
16: <Bar> {true}
^ [1]
8: child1: "should be single space",
^^^^^^^^^^^^^^^^^^^^^^^^ [2]
test.js:16
16: <Bar> {true}
^^^^ Cannot create \`Bar\` element because boolean [1] is incompatible with string literal \`should be true\` [2]. [incompatible-type]
References:
16: <Bar> {true}
^^^^ [1]
9: child2: "should be true",
^^^^^^^^^^^^^^^^ [2]
test.js:17
17: {''} </Bar>;
^^ Cannot create \`Bar\` element because string [1] is incompatible with string literal \`should be empty string\` [2]. [incompatible-type]
References:
17: {''} </Bar>;
^^ [1]
10: child3: "should be empty string",
^^^^^^^^^^^^^^^^^^^^^^^^ [2]
test.js:17
17: {''} </Bar>;
^ Cannot create \`Bar\` element because JSX text [1] is incompatible with string literal \`should be single space\` [2]. [incompatible-type]
References:
17: {''} </Bar>;
^ [1]
11: child4: "should be single space",
^^^^^^^^^^^^^^^^^^^^^^^^ [2]
`,
)
.because('JSXText children with only whitespace or newlines are ignored'),
]),
test('JSXText trimming', [
addCode("// @jsx Foo"),
addCode("const Bar = 123;"),
addCode(`
let Foo = (elem: any, props: any, c1: "First Middle Last") => {};
(<Bar> First${" "}
Middle${" "}
Last </Bar>);
`)
.newErrors(
`
test.js:9
9: (<Bar> First
^ Cannot create \`Bar\` element because JSX text [1] is incompatible with string literal \`First Middle Last\` [2]. [incompatible-type]
References:
9: (<Bar> First
^ [1]
8: let Foo = (elem: any, props: any, c1: "First Middle Last") => {};
^^^^^^^^^^^^^^^^^^^ [2]
`,
)
.because(
"Leading whitespace on the first line and trailing whiteline on the "+
"last line is not trimmed",
),
addCode(`
(<Bar>First
Middle
Last</Bar>);
`)
.noNewErrors()
.because('Empty lines are filtered out'),
addCode("(<Bar>First\tMiddle\tLast</Bar>);")
.noNewErrors()
.because("Tabs are turned into spaces"),
addCode("(<Bar>First Middle\t \t Last</Bar>)")
.newErrors(
`
test.js:24
24: (<Bar>First Middle Last</Bar>)
^^^^^^^^^^^^^^^^^^^^^^^ Cannot create \`Bar\` element because JSX text [1] is incompatible with string literal \`First Middle Last\` [2]. [incompatible-type]
References:
24: (<Bar>First Middle Last</Bar>)
^^^^^^^^^^^^^^^^^^^^^^^ [1]
8: let Foo = (elem: any, props: any, c1: "First Middle Last") => {};
^^^^^^^^^^^^^^^^^^^ [2]
`,
)
.because("Multiple spaces midline stay as multiple spaces"),
]),
]): Suite);