fix(tsx): resolve .js imports to .tsx as well (#20092)

Fixes: https://github.com/microsoft/playwright/issues/20039
This commit is contained in:
Pavel Feldman 2023-01-13 10:49:10 -08:00 committed by GitHub
parent 3a1eb2abda
commit 736cf5c585
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 124 additions and 6 deletions

View File

@ -169,11 +169,14 @@ export function resolveHook(filename: string, specifier: string): string | undef
export function js2ts(resolved: string): string | undefined {
const match = resolved.match(/(.*)(\.js|\.jsx|\.mjs)$/);
if (match) {
const tsResolved = match[1] + match[2].replace('j', 't');
if (!fs.existsSync(resolved) && fs.existsSync(tsResolved))
return tsResolved;
}
if (!match || fs.existsSync(resolved))
return;
const tsResolved = match[1] + match[2].replace('js', 'ts');
if (fs.existsSync(tsResolved))
return tsResolved;
const tsxResolved = match[1] + match[2].replace('js', 'tsx');
if (fs.existsSync(tsxResolved))
return tsxResolved;
}
export function transformHook(code: string, filename: string, moduleUrl?: string): string {

View File

@ -17,6 +17,7 @@
import path from 'path';
import type { T, BabelAPI } from './babelBundle';
import { types, declare, traverse } from './babelBundle';
import { js2ts } from './transform';
const t: typeof T = types;
const fullNames = new Map<string, string | undefined>();
@ -173,7 +174,11 @@ export type ComponentInfo = {
export function componentInfo(specifier: T.ImportSpecifier | T.ImportDefaultSpecifier, importSource: string, filename: string): ComponentInfo {
const isModuleOrAlias = !importSource.startsWith('.');
const importPath = isModuleOrAlias ? importSource : require.resolve(path.resolve(path.dirname(filename), importSource));
const unresolvedImportPath = path.resolve(path.dirname(filename), importSource);
// Support following notations for Button.tsx:
// - import { Button } from './Button.js' - via js2ts, it handles tsx too
// - import { Button } from './Button' - via require.resolve
const importPath = isModuleOrAlias ? importSource : js2ts(unresolvedImportPath) || require.resolve(unresolvedImportPath);
const prefix = importPath.replace(/[^\w_\d]/g, '_');
const pathInfo = { importPath, isModuleOrAlias };

View File

@ -209,3 +209,72 @@ test('should filter by line', async ({ runInlineTest, nodeVersion }) => {
expect(result.failed).toBe(1);
expect(result.output).toMatch(/x\.spec\.ts.*two/);
});
test('should resolve .js import to .ts file in ESM mode', async ({ runInlineTest, nodeVersion }) => {
test.skip(nodeVersion.major < 16);
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.ts': `export default { projects: [{name: 'foo'}] };`,
'a.test.ts': `
const { test } = pwt;
import { gimmeAOne } from './playwright-utils.js';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils.ts': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});
test('should resolve .js import to .tsx file in ESM mode', async ({ runInlineTest, nodeVersion }) => {
test.skip(nodeVersion.major < 16);
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.ts': `export default { projects: [{name: 'foo'}] };`,
'a.test.ts': `
const { test } = pwt;
import { gimmeAOne } from './playwright-utils.js';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils.tsx': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});
test('should resolve .js import to .tsx file in ESM mode for components', async ({ runInlineTest, nodeVersion }) => {
test.skip(nodeVersion.major < 16);
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.ts': `export default { projects: [{name: 'foo'}] };`,
'playwright/index.html': `<script type="module" src="./index.ts"></script>`,
'playwright/index.ts': ``,
'src/button.tsx': `
export const Button = () => <button>Button</button>;
`,
'src/test.spec.tsx': `
//@no-header
import { test, expect } from '@playwright/experimental-ct-react';
import { Button } from './button.js';
test('pass', async ({ mount }) => {
await mount(<Button></Button>);
});
`,
}, { workers: 1 });
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

View File

@ -542,6 +542,47 @@ test('should resolve .js import to .ts file in non-ESM mode', async ({ runInline
expect(result.exitCode).toBe(0);
});
test('should resolve .js import to .tsx file in non-ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
const { test } = pwt;
import { gimmeAOne } from './playwright-utils.js';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils.tsx': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});
test('should resolve .js import to .tsx file in non-ESM mode for components', async ({ runInlineTest }) => {
const result = await runInlineTest({
'playwright/index.html': `<script type="module" src="./index.ts"></script>`,
'playwright/index.ts': ``,
'src/button.tsx': `
export const Button = () => <button>Button</button>;
`,
'src/test.spec.tsx': `
//@no-header
import { test, expect } from '@playwright/experimental-ct-react';
import { Button } from './button.js';
test('pass', async ({ mount }) => {
await mount(<Button></Button>);
});
`,
}, { workers: 1 });
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});
test('should import export assignment from ts', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `