Skip to content

Commit

Permalink
fix(typescript): allow directory imports (#23254)
Browse files Browse the repository at this point in the history
This updates previous work in #22887 to align more fully with
`--moduleResolution=bundler`, allowing index files to be imported with
the /index extension

---------

Signed-off-by: Kristo Jorgenson <kristojorg@users.noreply.github.com>
Co-authored-by: Dmitry Gozman <dgozman@gmail.com>
  • Loading branch information
kristojorg and dgozman committed Jun 5, 2023
1 parent 24ac252 commit d5d155d
Show file tree
Hide file tree
Showing 3 changed files with 268 additions and 2 deletions.
18 changes: 16 additions & 2 deletions packages/playwright-test/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -321,16 +321,30 @@ const kExtLookups = new Map([
['', ['.js', '.ts', '.jsx', '.tsx', '.cjs', '.mjs', '.cts', '.mts']],
]);
export function resolveImportSpecifierExtension(resolved: string): string | undefined {
if (fs.existsSync(resolved))
if (fileExists(resolved))
return resolved;

for (const [ext, others] of kExtLookups) {
if (!resolved.endsWith(ext))
continue;
for (const other of others) {
const modified = resolved.substring(0, resolved.length - ext.length) + other;
if (fs.existsSync(modified))
if (fileExists(modified))
return modified;
}
break; // Do not try '' when a more specific extesion like '.jsx' matched.
}
// try directory imports last
if (dirExists(resolved)) {
const dirImport = path.join(resolved, 'index');
return resolveImportSpecifierExtension(dirImport);
}
}

function fileExists(resolved: string) {
return fs.statSync(resolved, { throwIfNoEntry: false })?.isFile();
}

function dirExists(resolved: string) {
return fs.statSync(resolved, { throwIfNoEntry: false })?.isDirectory();
}
132 changes: 132 additions & 0 deletions tests/playwright-test/esm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,138 @@ test('should filter by line', async ({ runInlineTest }) => {
expect(result.output).toMatch(/x\.spec\.ts.*two/);
});

test('should resolve directory import to index.js file in ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.ts': `export default { projects: [{name: 'foo'}] };`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils/index.js': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve directory import to index.ts file in ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.ts': `export default { projects: [{name: 'foo'}] };`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils/index.ts': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve directory import to index.tsx file in ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.ts': `export default { projects: [{name: 'foo'}] };`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils/index.tsx': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve directory import to index.mjs file in ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.ts': `export default { projects: [{name: 'foo'}] };`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils/index.mjs': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve directory import to index.jsx file in ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.ts': `export default { projects: [{name: 'foo'}] };`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils/index.jsx': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve file import before directory import in ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
'playwright.config.ts': `export default { projects: [{name: 'foo'}] };`,
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils.js': `
export function gimmeAOne() {
return 1;
}
`,
'playwright-utils/index.js': `
export function gimmeAOne() {
// intentionally return the wrong thing because this file shouldn't be resolved.
return 2;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve .js import to .ts file in ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'package.json': `{ "type": "module" }`,
Expand Down
120 changes: 120 additions & 0 deletions tests/playwright-test/loader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,126 @@ test('should remove type imports from ts', async ({ runInlineTest }) => {
expect(result.exitCode).toBe(0);
});

test('should resolve directory import to index.js file in non-ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils/index.js': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve directory import to index.ts file in non-ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils/index.ts': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve directory import to index.tsx file in non-ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils/index.tsx': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve directory import to index.mjs file in non-ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils/index.mjs': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve directory import to index.jsx file in non-ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils/index.jsx': `
export function gimmeAOne() {
return 1;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve file import before directory import in non-ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
import { test, expect } from '@playwright/test';
import { gimmeAOne } from './playwright-utils';
test('pass', ({}) => {
expect(gimmeAOne()).toBe(1);
});
`,
'playwright-utils.jsx': `
export function gimmeAOne() {
return 1;
}
`,
'playwright-utils/index.jsx': `
export function gimmeAOne() {
// intentionally return the wrong thing because this file shouldn't be resolved.
return 2;
}
`,
});
expect(result.passed).toBe(1);
expect(result.exitCode).toBe(0);
});

test('should resolve .js import to .ts file in non-ESM mode', async ({ runInlineTest }) => {
const result = await runInlineTest({
'a.test.ts': `
Expand Down

0 comments on commit d5d155d

Please sign in to comment.