import * as utils from './ast-utils'; import * as ts from 'typescript'; import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing'; import { applyChangesToString, Tree } from '@nrwl/devkit'; describe('findDefaultExport', () => { it('should find exported variable', () => { const sourceCode = ` const main = () => {}; export default main; `; const source = ts.createSourceFile( 'test.ts', sourceCode, ts.ScriptTarget.Latest, true ); const result = utils.findDefaultExport(source) as any; expect(result).toBeDefined(); expect(result.name.text).toEqual('main'); }); it('should find exported function', () => { const sourceCode = ` function main() {} export default main; `; const source = ts.createSourceFile( 'test.ts', sourceCode, ts.ScriptTarget.Latest, true ); const result = utils.findDefaultExport(source) as any; expect(result).toBeDefined(); expect(result.name.text).toEqual('main'); }); it('should find default export function', () => { const sourceCode = ` export default function main() {}; `; const source = ts.createSourceFile( 'test.ts', sourceCode, ts.ScriptTarget.Latest, true ); const result = utils.findDefaultExport(source) as any; expect(result).toBeDefined(); expect(result.name.text).toEqual('main'); }); it('should find default class export', () => { const sourceCode = ` export default class Main {}; `; const source = ts.createSourceFile( 'test.ts', sourceCode, ts.ScriptTarget.Latest, true ); const result = utils.findDefaultExport(source) as any; expect(result).toBeDefined(); expect(result.name.text).toEqual('Main'); }); it('should find exported class', () => { const sourceCode = ` class Main {}; export default Main; `; const source = ts.createSourceFile( 'test.ts', sourceCode, ts.ScriptTarget.Latest, true ); const result = utils.findDefaultExport(source) as any; expect(result).toBeDefined(); expect(result.name.text).toEqual('Main'); }); }); describe('addRoute', () => { let tree: Tree; let context: any; beforeEach(() => { context = { warn: jest.fn(), }; tree = createTreeWithEmptyV1Workspace(); }); it('should add links and routes if they are not present', async () => { const sourceCode = ` import React from 'react'; const App = () => ( <>

Hello

); export default App; `; tree.write('/app.tsx', sourceCode); const source = ts.createSourceFile( '/app.tsx', sourceCode, ts.ScriptTarget.Latest, true ); const result = applyChangesToString( sourceCode, utils.addInitialRoutes('/app.tsx', source) ); expect(result).toMatch(/role="navigation"/); expect(result).toMatch(/ { const sourceCode = ` import React from 'react'; import { Home } from '@example/home'; const App = () => ( <>

Hello

  • Home

Hello World!

}/> ); export default App; `; tree.write('/app.tsx', sourceCode); const source = ts.createSourceFile( '/app.tsx', sourceCode, ts.ScriptTarget.Latest, true ); const result = applyChangesToString( sourceCode, utils.addRoute('/app.tsx', source, { routePath: '/about', componentName: 'About', moduleName: '@example/about', }) ); expect(result).toMatch(/
  • }/); }); }); describe('addBrowserRouter', () => { let tree: Tree; let context: any; beforeEach(() => { context = { warn: jest.fn(), }; tree = createTreeWithEmptyV1Workspace(); }); it('should wrap around App component', () => { const sourceCode = ` import React from 'react'; import ReactDOM from 'react-dom'; import { App } from '@example/my-app'; ReactDOM.render(, document.getElementById('root')); `; tree.write('/main.tsx', sourceCode); const source = ts.createSourceFile( '/main.tsx', sourceCode, ts.ScriptTarget.Latest, true ); const result = applyChangesToString( sourceCode, utils.addBrowserRouter('/main.tsx', source) ); expect(result).toContain(''); }); }); describe('findMainRenderStatement', () => { let tree: Tree; let context: any; beforeEach(() => { context = { warn: jest.fn(), }; tree = createTreeWithEmptyV1Workspace(); }); it('should return ReactDOM.render(...)', () => { const sourceCode = ` import React from 'react'; import ReactDOM from 'react-dom'; ReactDOM.render(
    , document.getElementById('root')); `; tree.write('/main.tsx', sourceCode); const source = ts.createSourceFile( '/main.tsx', sourceCode, ts.ScriptTarget.Latest, true ); const node = utils.findMainRenderStatement(source); expect(node).toBeTruthy(); }); it('should return root.render(...)', () => { const sourceCode = ` import React from 'react'; import ReactDOM from 'react-dom/client'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render(
    ); `; tree.write('/main.tsx', sourceCode); const source = ts.createSourceFile( '/main.tsx', sourceCode, ts.ScriptTarget.Latest, true ); const node = utils.findMainRenderStatement(source); expect(node).toBeTruthy(); }); it('should return render(...)', () => { const sourceCode = ` import React from 'react'; import { render } from 'react-dom'; render(
    , document.getElementById('root')); `; tree.write('/main.tsx', sourceCode); const source = ts.createSourceFile( '/main.tsx', sourceCode, ts.ScriptTarget.Latest, true ); const node = utils.findMainRenderStatement(source); expect(node).toBeTruthy(); }); it('should return null when not found', () => { const sourceCode = ``; tree.write('/main.tsx', sourceCode); const source = ts.createSourceFile( '/main.tsx', sourceCode, ts.ScriptTarget.Latest, true ); const node = utils.findMainRenderStatement(source); expect(node).toBe(null); }); }); describe('addReduxStoreToMain', () => { let tree: Tree; let context: any; beforeEach(() => { context = { warn: jest.fn(), }; tree = createTreeWithEmptyV1Workspace(); }); it('should wrap around App component', () => { const sourceCode = ` import React from 'react'; import ReactDOM from 'react-dom'; import { App } from '@example/my-app'; ReactDOM.render(, document.getElementById('root')); `; tree.write('/main.tsx', sourceCode); const source = ts.createSourceFile( '/main.tsx', sourceCode, ts.ScriptTarget.Latest, true ); const result = applyChangesToString( sourceCode, utils.addReduxStoreToMain('/main.tsx', source) ); expect(result).toContain('@reduxjs/toolkit'); expect(result).toContain('const store = configureStore'); expect(result).toContain(''); }); }); describe('updateReduxStore', () => { let tree: Tree; let context: any; beforeEach(() => { context = { warn: jest.fn(), }; tree = createTreeWithEmptyV1Workspace(); }); it('should update configureStore call', () => { const sourceCode = ` import { configureStore } from '@reduxjs/toolkit'; const store = configureStore({ reducer: {} }); `; tree.write('/main.tsx', sourceCode); const source = ts.createSourceFile( '/main.tsx', sourceCode, ts.ScriptTarget.Latest, true ); const result = applyChangesToString( sourceCode, utils.updateReduxStore('/main.tsx', source, { keyName: 'SLICE_KEY', reducerName: 'sliceReducer', modulePath: '@test/slice', }) ); expect(result).toContain( "import { SLICE_KEY, sliceReducer } from '@test/slice'" ); expect(result).toContain('[SLICE_KEY]: sliceReducer'); }); it('should update combineReducer call', () => { const sourceCode = ` import { createStore, combineReducer } from 'redux'; const store = createStore(combineReducer({})); `; tree.write('/main.tsx', sourceCode); const source = ts.createSourceFile( '/main.tsx', sourceCode, ts.ScriptTarget.Latest, true ); const result = applyChangesToString( sourceCode, utils.updateReduxStore('/main.tsx', source, { keyName: 'SLICE_KEY', reducerName: 'sliceReducer', modulePath: '@test/slice', }) ); expect(result).toContain( "import { SLICE_KEY, sliceReducer } from '@test/slice'" ); expect(result).toContain('[SLICE_KEY]: sliceReducer'); }); }); describe('findExportDeclarationsForJsx', () => { const testConfigs = [ { testName: 'exporting a function', src: `export function Test(props: TestProps) { return (

    Welcome to test component, {props.name}

    );`, expectedName: 'Test', }, { testName: 'exporting an arrow function', src: ` export const Test = (props: TestProps) => { return (

    Welcome to test component, {props.name}

    ); }; `, expectedName: 'Test', }, { testName: 'defining an arrow function that directly returns JSX', src: ` export const Test = (props: TestProps) =>

    Welcome to test component, {props.name}

    ; `, expectedName: 'Test', }, { testName: 'exporting a react class component', src: ` export class Test extends React.Component { render() { return

    Welcome to test component, {this.props.name}

    ; } } `, expectedName: 'Test', }, { testName: 'exporting a react functional component', src: ` export const Test: React.FC = (props) => { return (

    Welcome to test component, {props.name}

    ); }; `, expectedName: 'Test', }, { testName: 'using a JSX self closing element', src: ` export function Test(props: TestProps) { return ; }; `, expectedName: 'Test', }, ]; it.each(testConfigs)( 'should find the component when: $testName', ({ src, expectedName }) => { const source = ts.createSourceFile( 'some-component.tsx', src, ts.ScriptTarget.Latest, true ); const result: Array< ts.VariableDeclaration | ts.FunctionDeclaration | ts.ClassDeclaration > = utils.findExportDeclarationsForJsx(source) as any; expect(result).toBeDefined(); expect(result[0]?.name.getText()).toEqual(expectedName); } ); }); describe('getComponentNode', () => { const testConfigs = [ { testName: 'exporting a function', src: `export default function Test(props: TestProps) { return (

    Welcome to test component, {props.name}

    );`, expectedName: 'Test', }, { testName: 'defining a function and then default exporting it', src: ` function Test(props: TestProps) { return (

    Welcome to test component, {props.name}

    ); }; export default Test; `, expectedName: 'Test', }, { testName: 'defining an arrow function and then exporting it', src: ` const Test = (props: TestProps) => { return (

    Welcome to test component, {props.name}

    ); }; export default Test; `, expectedName: 'Test', }, { testName: 'defining an arrow function that directly returns JSX', src: ` const Test = (props: TestProps) =>

    Welcome to test component, {props.name}

    ; export default Test `, expectedName: 'Test', }, { testName: 'exporting a react class component', src: ` export default class Test extends React.Component { render() { return

    Welcome to test component, {this.props.name}

    ; } } `, expectedName: 'Test', }, { testName: 'defining a react class component & then default exporting it', src: ` export default class Test extends React.Component { render() { return

    Welcome to test component, {this.props.name}

    ; } } `, expectedName: 'Test', }, { testName: 'using a JSX self closing element', src: ` function Test(props: TestProps) { return ; }; export default Test; `, expectedName: 'Test', }, ]; it.each(testConfigs)( 'should find the component when: $testName', ({ src, expectedName }) => { const source = ts.createSourceFile( 'some-component.tsx', src, ts.ScriptTarget.Latest, true ); const result = utils.getComponentNode(source) as any; expect(result).toBeDefined(); expect((result as any).name.text).toEqual(expectedName); } ); it('should return null if there is no component', () => { const source = ts.createSourceFile( 'some-component.tsx', `console.log('hi there');`, ts.ScriptTarget.Latest, true ); const result = utils.getComponentNode(source) as any; expect(result).toBeNull(); }); });