import { describe, expect, test } from 'vitest' import { detectDevtoolsFile, findDevtoolsComponentName, transformAndInject, } from './inject-plugin' const removeEmptySpace = (str: string) => { return str.replace(/\s/g, '').trim() } // Helper to test transformation without file I/O const testTransform = ( code: string, packageName: string, pluginName: string, pluginImport: { importName: string type: 'jsx' | 'function' }, ) => { const devtoolsComponentName = findDevtoolsComponentName(code) if (!devtoolsComponentName) { return { transformed: false, code } } const result = transformAndInject( code, { packageName, pluginName, pluginImport }, devtoolsComponentName, ) if (!result?.transformed) { return { transformed: false, code } } return { transformed: true, code: result.code } } describe('inject-plugin', () => { describe('detectDevtoolsFile', () => { test('should detect named import from @tanstack/react-devtools', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' ` expect(detectDevtoolsFile(code)).toBe(true) }) test('should detect named import from @tanstack/solid-devtools', () => { const code = ` import { TanStackDevtools } from '@tanstack/solid-devtools' ` expect(detectDevtoolsFile(code)).toBe(true) }) test('should detect namespace import', () => { const code = ` import * as Devtools from '@tanstack/react-devtools' ` expect(detectDevtoolsFile(code)).toBe(true) }) test('should detect renamed named import', () => { const code = ` import { TanStackDevtools as DevtoolsPanel } from '@tanstack/react-devtools' ` expect(detectDevtoolsFile(code)).toBe(true) }) test('should not detect non-devtools imports', () => { const code = ` import React from 'react' import { useState } from 'react' ` expect(detectDevtoolsFile(code)).toBe(false) }) test('should not detect files without imports', () => { const code = ` function App() { return
Hello
} ` expect(detectDevtoolsFile(code)).toBe(false) }) test('should handle invalid code gracefully', () => { const code = ` this is not valid javascript{{{ ` expect(detectDevtoolsFile(code)).toBe(false) }) }) describe('named import pattern', () => { test('should add plugin to existing empty plugins array', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return }]} /> } `), ) }) test('should add plugin to existing plugins array with other plugins', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' import { OtherPlugin } from '@tanstack/other-plugin' function App() { return } ]} /> } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { OtherPlugin } from '@tanstack/other-plugin' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return }, { name: "TanStack Query", render: } ]} /> } `), ) }) test('should create plugins prop if it does not exist', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return }]} /> } `), ) }) test('should create plugins prop with other existing props', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return }]} /> } `), ) }) test('should not add plugin if it already exists', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools' function App() { return } ]} /> } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(false) }) }) describe('renamed named import pattern', () => { test('should handle renamed named import', () => { const code = ` import { TanStackDevtools as DevtoolsPanel } from '@tanstack/react-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools as DevtoolsPanel } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return }]} /> } `), ) }) test('should handle renamed import without plugins prop', () => { const code = ` import { TanStackDevtools as MyDevtools } from '@tanstack/solid-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools as MyDevtools } from '@tanstack/solid-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return }]} /> } `), ) }) }) describe('namespace import pattern', () => { test('should handle namespace import', () => { const code = ` import * as DevtoolsModule from '@tanstack/react-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import * as DevtoolsModule from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return }]} /> } `), ) }) test('should handle namespace import without plugins prop', () => { const code = ` import * as TSD from '@tanstack/solid-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/solid-router-devtools', 'TanStack Router', { importName: 'SolidRouterDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import * as TSD from '@tanstack/solid-devtools' import { SolidRouterDevtoolsPanel } from "@tanstack/solid-router-devtools"; function App() { return }]} /> } `), ) }) }) describe('different plugin types', () => { test('should handle router devtools', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-router-devtools', 'TanStack Router', { importName: 'ReactRouterDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactRouterDevtoolsPanel } from "@tanstack/react-router-devtools"; function App() { return }]} /> } `), ) }) test('should handle form devtools', () => { const code = ` import { TanStackDevtools } from '@tanstack/solid-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-form-devtools', 'TanStack Form', { importName: 'ReactFormDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/solid-devtools' import { ReactFormDevtoolsPanel } from "@tanstack/react-form-devtools"; function App() { return }]} /> } `), ) }) test('should handle query devtools', () => { const code = ` import { TanStackDevtools } from '@tanstack/vue-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/vue-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return }]} /> } `), ) }) }) describe('edge cases', () => { test('should not transform files without TanStackDevtools component', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return
Hello World
} ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(false) }) test('should handle TanStackDevtools with children', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return (
Custom content
) } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return ( }]}>
Custom content
) } `), ) }) test('should handle multiple TanStackDevtools in same file', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return ( <> ) } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return ( <> }]} /> }]} /> ) } `), ) }) test('should handle TanStackDevtools deeply nested', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return (
) } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return (
) } `), ) }) test('should preserve existing code formatting and structure', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' import { useState } from 'react' function App() { const [count, setCount] = useState(0) return (
) } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { useState } from 'react' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { const [count, setCount] = useState(0) return (
}]} />
) } `), ) }) test('should handle TypeScript code', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' import type { FC } from 'react' const App: FC = () => { return } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import type { FC } from 'react' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; const App: FC = () => { return }]} /> } `), ) }) test('should handle plugins array with trailing comma', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return }, ]} /> } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from "@tanstack/react-query-devtools"; function App() { return }, { name: "TanStack Query", render: } ]} /> } `), ) }) test('should not transform if devtools import not found', () => { const code = ` import { SomeOtherComponent } from 'some-package' function App() { return } ` const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: 'ReactQueryDevtoolsPanel', type: 'jsx', }, ) expect(result.transformed).toBe(false) }) }) describe('function-based plugins', () => { test('should add function plugin to empty plugins array', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-form-devtools', 'react-form', { importName: 'FormDevtoolsPlugin', type: 'function', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { FormDevtoolsPlugin } from "@tanstack/react-form-devtools"; function App() { return } `), ) }) test('should add function plugin alongside JSX plugins', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools' function App() { return } ]} /> } ` const result = testTransform( code, '@tanstack/react-form-devtools', 'react-form', { importName: 'FormDevtoolsPlugin', type: 'function', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { ReactQueryDevtoolsPanel } from '@tanstack/react-query-devtools' import { FormDevtoolsPlugin } from "@tanstack/react-form-devtools"; function App() { return }, FormDevtoolsPlugin() ]} /> } `), ) }) test('should create plugins prop with function plugin when it does not exist', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-form-devtools', 'react-form', { importName: 'FormDevtoolsPlugin', type: 'function', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { FormDevtoolsPlugin } from "@tanstack/react-form-devtools"; function App() { return } `), ) }) test('should not add function plugin if it already exists', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' import { FormDevtoolsPlugin } from '@tanstack/react-form-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-form-devtools', 'react-form', { importName: 'FormDevtoolsPlugin', type: 'function', }, ) expect(result.transformed).toBe(false) }) test('should handle function plugin with renamed devtools import', () => { const code = ` import { TanStackDevtools as DevtoolsPanel } from '@tanstack/react-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-form-devtools', 'react-form', { importName: 'FormDevtoolsPlugin', type: 'function', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools as DevtoolsPanel } from '@tanstack/react-devtools' import { FormDevtoolsPlugin } from "@tanstack/react-form-devtools"; function App() { return } `), ) }) test('should handle function plugin with namespace import', () => { const code = ` import * as DevtoolsModule from '@tanstack/solid-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/solid-form-devtools', 'solid-form', { importName: 'FormDevtoolsPlugin', type: 'function', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import * as DevtoolsModule from '@tanstack/solid-devtools' import { FormDevtoolsPlugin } from "@tanstack/solid-form-devtools"; function App() { return } `), ) }) test('should add multiple function plugins correctly', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' import { FormDevtoolsPlugin } from '@tanstack/react-form-devtools' function App() { return } ` const result = testTransform( code, '@tanstack/react-router-devtools', 'react-router', { importName: 'RouterDevtoolsPlugin', type: 'function', }, ) expect(result.transformed).toBe(true) expect(removeEmptySpace(result.code)).toBe( removeEmptySpace(` import { TanStackDevtools } from '@tanstack/react-devtools' import { FormDevtoolsPlugin } from '@tanstack/react-form-devtools' import { RouterDevtoolsPlugin } from "@tanstack/react-router-devtools"; function App() { return } `), ) }) test('should not transform when pluginImport is not provided', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return } ` // No pluginImport provided - should return false const result = testTransform( code, '@tanstack/react-query-devtools', 'TanStack Query', { importName: '', type: 'jsx', }, ) expect(result.transformed).toBe(false) }) }) describe('safe code generation', () => { test('escapes quotes/backslashes in displayName when emitting JSX object', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' function App() { return } ` const result = testTransform( code, '@example/plugin', 'has "quotes" and \\backslash', { importName: 'ExamplePlugin', type: 'jsx' }, ) expect(result.transformed).toBe(true) // displayName must be serialized via JSON.stringify so embedded quotes // and backslashes don't break the generated source. expect(result.code).toContain( 'name: "has \\"quotes\\" and \\\\backslash"', ) // and the package name in the appended import is also a JSON literal expect(result.code).toContain( 'import { ExamplePlugin } from "@example/plugin"', ) }) test('does not re-add an existing import when the plugin component is already imported', () => { const code = ` import { TanStackDevtools } from '@tanstack/react-devtools' import { ExamplePlugin } from '@example/plugin' function App() { return } ` const result = testTransform(code, '@example/plugin', 'Example', { importName: 'ExamplePlugin', type: 'jsx', }) expect(result.transformed).toBe(true) // Exactly one import for ExamplePlugin from @example/plugin should remain. const importMatches = result.code.match( /import\s*\{\s*ExamplePlugin\s*\}\s*from\s*['"]@example\/plugin['"]/g, ) expect(importMatches).not.toBeNull() expect(importMatches!.length).toBe(1) }) }) })