import { describe, expect, it } from 'vitest'
import { addSourceToJsx } from './inject-source'
const removeEmptySpace = (str: string) => {
return str.replace(/\s/g, '').trim()
}
describe('inject source', () => {
it("shouldn't augment react fragments", () => {
const output = addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: function() { return <>Hello World> },
})
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it("shouldn't augment react fragments if they start with Fragment ", () => {
const output = addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: function() { return Hello World },
})
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it("shouldn't augment react fragments if they start with React.Fragment ", () => {
const output = addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: function() { return Hello World },
})
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
describe('FunctionExpression', () => {
it('should work with deeply nested custom JSX syntax', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: function() { return
Hello World
},
})
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
export const Route = createFileRoute("/test")({
component: function() { return Hello World
},
})
`),
)
})
it('should work with props not destructured and spread', () => {
const output = addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: function(props) { return Hello World
},
})
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('should work with props destructured and spread', () => {
const output = addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: function({...props}) { return Hello World
},
})
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('should work with props destructured and spread with a different name', () => {
const output = addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: function({...rest}) { return Hello World
},
})
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('should work with props spread and other normal elements', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: function({...rest}) { return }
})
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
export const Route = createFileRoute("/test")({
component: function({...rest}) { return }
})
`),
)
})
})
describe('ArrowFunctionExpression', () => {
it('should work with deeply nested custom JSX syntax', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: () => Hello World
,
})
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
export const Route = createFileRoute("/test")({
component: () => Hello World
,
})
`),
)
})
it('should work with props not destructured and spread', () => {
const output = addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: (props) => Hello World
,
})
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('should work with props destructured and spread', () => {
const output = addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: ({...props}) => Hello World
,
})
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('should work with props destructured and spread with a different name', () => {
const output = addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: ({...rest}) => Hello World
,
})
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('should work with props spread and other normal elements', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
export const Route = createFileRoute("/test")({
component: ({...rest}) => ,
})
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
export const Route = createFileRoute("/test")({
component: ({...rest}) => ,
})
`),
)
})
})
describe('function declarations', () => {
it('should not duplicate the same property if there are nested functions', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
function Parent({ ...props }) {
function Child({ ...props }) {
return
}
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
function Parent({ ...props }) {
function Child({ ...props }) {
return
}
return
}
`),
)
})
it('should apply data-tsd-source from parent props if an external import', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
import Custom from "external";
function test({...props }) {
return
}
`,
'test.tsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
import Custom from "external";
function test({...props }) {
return
}
`),
)
})
it(' props not destructured', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
function test(props){
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
function test(props){
return
}
`),
)
})
it("doesn't transform when props are spread across the element", () => {
const output = addSourceToJsx(
`
function test(props) {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it("doesn't transform when props are spread across the element but applies to other elements without any props", () => {
const output = removeEmptySpace(
addSourceToJsx(
`
function test(props) {
return (
)
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
function test(props) {
return (
)
}
`),
)
})
it("doesn't transform when props are spread across the element but applies to other elements without any props even when props are destructured", () => {
const output = removeEmptySpace(
addSourceToJsx(
`
function test({...props}) {
return (
)
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
function test({...props}) {
return (
)
}
`),
)
})
it("doesn't transform when props are spread across the element but applies to other elements without any props even when props are destructured and name is different", () => {
const output = removeEmptySpace(
addSourceToJsx(
`
function test({...rest}) {
return (
)
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
function test({...rest}) {
return (
)
}
`),
)
})
it(' props destructured and collected with a different name', () => {
const output = addSourceToJsx(
`
function test({ children, ...rest }) {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it(' props destructured and collected', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
function test({ ...props }) {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
function test({ ...props }) {
return
}
`),
)
})
it('props destructured and collected with a different name even on custom components', () => {
const output = addSourceToJsx(
`
function test({ children, ...rest }) {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('props destructured and collected even on custom components', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
function test({ ...props }) {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
function test({ ...props }) {
return
}
`),
)
})
it('props destructured and collected with a different name even on custom components even if exported', () => {
const output = addSourceToJsx(
`
function test({ children, ...rest }) {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('props destructured and collected even on custom components even if exported', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
function test({ ...props }) {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
function test({ ...props }) {
return
}
`),
)
})
})
describe('variable declared functions', () => {
it('works with function and props not destructured', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = function test(props){
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = function test(props){
return
}
`),
)
})
it("doesn't transform when props are spread across the element", () => {
const output = addSourceToJsx(
`
const ButtonWithProps = function test(props) {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it("doesn't transform when props are spread across the element but applies to other elements without any props", () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = function test(props) {
return (
)
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = function test(props) {
return (
)
}
`),
)
})
it("doesn't transform when props are spread across the element but applies to other elements without any props even when props are destructured", () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = function test({...props}) {
return (
)
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = function test({...props}) {
return (
)
}
`),
)
})
it("doesn't transform when props are spread across the element but applies to other elements without any props even when props are destructured and name is different", () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = function test({...rest}) {
return (
)
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = function test({...rest}) {
return (
)
}
`),
)
})
it(' props destructured and collected with a different name', () => {
const output = addSourceToJsx(
`
const ButtonWithProps = function test({ children, ...rest }) {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it(' props destructured and collected', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = function test({ ...props }) {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = function test({ ...props }) {
return
}
`),
)
})
it('props destructured and collected with a different name even on custom components', () => {
const output = addSourceToJsx(
`
const ButtonWithProps = function test({ children, ...rest }) {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('props destructured and collected even on custom components', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = function test({ ...props }) {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = function test({ ...props }) {
return
}
`),
)
})
it('props destructured and collected with a different name even on custom components even if exported', () => {
const output = addSourceToJsx(
`
export const ButtonWithProps = function test({ children, ...rest }) {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('props destructured and collected even on custom components even if exported', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
export const ButtonWithProps = function test({ ...props }) {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
export const ButtonWithProps = function test({ ...props }) {
return
}
`),
)
})
})
describe('arrow functions', () => {
it('works with arrow function and props not destructured', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = (props) => {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = (props) => {
return
}
`),
)
})
it("doesn't transform when props are spread across the element", () => {
const output = addSourceToJsx(
`
const ButtonWithProps = (props) => {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it("doesn't transform when props are spread across the element but applies to other elements without any props", () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = (props) => {
return (
)
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = (props) => {
return (
)
}
`),
)
})
it("doesn't transform when props are spread across the element but applies to other elements without any props even when props are destructured", () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = ({...props}) => {
return (
)
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = ({...props}) => {
return (
)
}
`),
)
})
it("doesn't transform when props are spread across the element but applies to other elements without any props even when props are destructured and name is different", () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = ({...rest}) => {
return (
)
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = ({...rest}) => {
return (
)
}
`),
)
})
it('works with arrow function and props destructured and collected with a different name', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = ({ children, ...rest }) => {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = ({ children, ...rest }) => {
return
}
`),
)
})
it('works with arrow function and props destructured and collected', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = ({ ...props }) => {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = ({ ...props }) => {
return
}
`),
)
})
it('works with arrow function and props destructured and collected with a different name even on custom components', () => {
const output = addSourceToJsx(
`
const ButtonWithProps = ({ children, ...rest }) => {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('works with arrow function and props destructured and collected even on custom components', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
const ButtonWithProps = ({ ...props }) => {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
const ButtonWithProps = ({ ...props }) => {
return
}
`),
)
})
it('works with arrow function and props destructured and collected with a different name even on custom components even if exported', () => {
const output = addSourceToJsx(
`
export const ButtonWithProps = ({ children, ...rest }) => {
return
}
`,
'test.jsx',
)
expect(output).toBe(undefined)
})
it('works with arrow function and props destructured and collected even on custom components even if exported', () => {
const output = removeEmptySpace(
addSourceToJsx(
`
export const ButtonWithProps = ({ ...props }) => {
return
}
`,
'test.jsx',
)!.code,
)
expect(output).toBe(
removeEmptySpace(`
export const ButtonWithProps = ({ ...props }) => {
return
}
`),
)
})
})
describe('ignore patterns', () => {
it('should skip injection for ignored component names (string)', () => {
const output = addSourceToJsx(
`
function test() {
return
}
`,
'test.jsx',
{
components: ['Button'],
},
)
expect(output).toBe(undefined)
})
it('should skip injection for ignored file paths (glob)', () => {
const output = addSourceToJsx(
`
function test() {
return
}
`,
'src/components/ignored-file.jsx',
{
files: ['**/ignored-file.jsx'],
},
)
expect(output).toBe(undefined)
})
})
describe('non-ASCII source positions', () => {
// oxc-parser napi bindings return UTF-16 code-unit indices (matching JS
// string indexing and magic-string's expectations). These tests lock that
// contract in: positions and inserted attributes must remain correct when
// the source contains multi-byte UTF-8 characters.
it('locates JSX correctly after an emoji string literal', () => {
const source = `
const label = "π launch"
function Comp() {
return
}
`
const output = addSourceToJsx(source, 'emoji.jsx')!.code
// line numbers are 1-based; the
is on line 4
expect(output).toMatch(/ {
const source = `
// δΈζ注ι
function Comp() {
return
}
`
const output = addSourceToJsx(source, 'cjk.jsx')!.code
expect(output).toMatch(/ {
it('does not annotate inner-function JSX with the outer propsName', () => {
// The inner arrow forwards its own innerProps via spread. The outer
// function must NOT inject data-tsd-source onto that inner element,
// since it has no relation to the outer `props`.
const output = addSourceToJsx(
`
const Outer = (props) => {
const Inner = (innerProps) =>
return
}
`,
'nested.jsx',
)
expect(output).toBe(undefined)
})
})
})