chore: relax transpilation criteria to allow ems imports of .vue files (#15592)

This commit is contained in:
Pavel Feldman 2022-07-13 15:11:38 -08:00 committed by GitHub
parent 44e214a972
commit d02914fa3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 129 additions and 28 deletions

View File

@ -16,7 +16,7 @@
import fs from 'fs';
import url from 'url';
import { transformHook, resolveHook } from './transform';
import { transformHook, resolveHook, belongsToNodeModules } from './transform';
async function resolve(specifier: string, context: { parentURL: string }, defaultResolve: any) {
if (context.parentURL && context.parentURL.startsWith('file://')) {
@ -29,13 +29,27 @@ async function resolve(specifier: string, context: { parentURL: string }, defaul
}
async function load(moduleUrl: string, context: any, defaultLoad: any) {
if (moduleUrl.startsWith('file://') && (moduleUrl.endsWith('.ts') || moduleUrl.endsWith('.tsx'))) {
const filename = url.fileURLToPath(moduleUrl);
const code = fs.readFileSync(filename, 'utf-8');
const source = transformHook(code, filename, moduleUrl);
return { format: 'module', source };
}
return defaultLoad(moduleUrl, context, defaultLoad);
// Bail out for wasm, json, etc.
// non-js files have context.format === undefined
if (context.format !== 'commonjs' && context.format !== 'module' && context.format !== undefined)
return defaultLoad(moduleUrl, context, defaultLoad);
// Bail for built-in modules.
if (!moduleUrl.startsWith('file://'))
return defaultLoad(moduleUrl, context, defaultLoad);
if (!moduleUrl.startsWith('file://'))
return defaultLoad(moduleUrl, context, defaultLoad);
const filename = url.fileURLToPath(moduleUrl);
// Bail for node_modules.
if (belongsToNodeModules(filename))
return defaultLoad(moduleUrl, context, defaultLoad);
const code = fs.readFileSync(filename, 'utf-8');
const source = transformHook(code, filename, moduleUrl);
// Output format is always the same as input format, if it was unknown, we always report modules.
return { format: context.format || 'module', source };
}
module.exports = { resolve, load };

View File

@ -135,7 +135,7 @@ export function resolveHook(filename: string, specifier: string): string | undef
if (value.includes('*'))
candidate = candidate.replace('*', matchedPartOfSpecifier);
candidate = path.resolve(tsconfig.absoluteBaseUrl, candidate.replace(/\//g, path.sep));
for (const ext of ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx']) {
for (const ext of ['', '.js', '.ts', '.mjs', '.cjs', '.jsx', '.tsx', '.cjs', '.mts', '.cts']) {
if (fs.existsSync(candidate + ext)) {
if (keyPrefix.length > longestPrefixLength) {
longestPrefixLength = keyPrefix.length;
@ -162,15 +162,11 @@ export function transformHook(code: string, filename: string, moduleUrl?: string
// If we are not TypeScript and there is no applicable preprocessor - bail out.
const isModule = !!moduleUrl;
const isTypeScript = filename.endsWith('.ts') || filename.endsWith('.tsx');
const isJSX = filename.endsWith('.jsx');
const hasPreprocessor =
process.env.PW_TEST_SOURCE_TRANSFORM &&
process.env.PW_TEST_SOURCE_TRANSFORM_SCOPE &&
process.env.PW_TEST_SOURCE_TRANSFORM_SCOPE.split(pathSeparator).some(f => filename.startsWith(f));
if (!isTypeScript && !isJSX && !hasPreprocessor)
return code;
const cachePath = calculateCachePath(code, filename, isModule);
const codePath = cachePath + '.js';
const sourceMapPath = cachePath + '.map';
@ -212,11 +208,11 @@ export function installTransform(): () => void {
}
(Module as any)._resolveFilename = resolveFilename;
const exts = ['.ts', '.tsx', '.jsx'];
// When script preprocessor is engaged, we transpile JS as well.
if (scriptPreprocessor)
exts.push('.js', '.mjs');
const revertPirates = pirates.addHook((code: string, filename: string) => transformHook(code, filename), { exts });
const revertPirates = pirates.addHook((code: string, filename: string) => {
if (belongsToNodeModules(filename))
return code;
return transformHook(code, filename);
}, { exts: ['.ts', '.tsx', '.js', '.jsx', '.mjs'] });
return () => {
reverted = true;
@ -248,3 +244,13 @@ export function wrapFunctionWithLocation<A extends any[], R>(func: (location: Lo
return func(location, ...args);
};
}
export function belongsToNodeModules(file: string) {
if (file.includes(`${path.sep}node_modules${path.sep}`))
return true;
if (file.includes(`${path.sep}playwright${path.sep}packages${path.sep}playwright`))
return true;
if (file.includes(`${path.sep}playwright${path.sep}tests${path.sep}config${path.sep}coverage.js`))
return true;
return false;
}

View File

@ -0,0 +1,81 @@
import { test, expect } from '@playwright/experimental-ct-vue'
import has from 'has'
import Button from './components/Button.vue'
import DefaultSlot from './components/DefaultSlot.vue'
import NamedSlots from './components/NamedSlots.vue'
import Component from './components/Component.vue'
test.use({ viewport: { width: 500, height: 500 } })
test('props should work', async ({ mount }) => {
const component = await mount(Button, {
props: {
title: 'Submit'
}
})
await expect(component).toContainText('Submit')
})
test('event should work', async ({ mount }) => {
const messages = []
const component = await mount(Button, {
props: {
title: 'Submit'
},
on: {
submit: data => messages.push(data)
}
})
await component.click()
expect(messages).toEqual(['hello'])
})
test('default slot should work', async ({ mount }) => {
const component = await mount(DefaultSlot, {
slots: {
default: 'Main Content'
}
})
await expect(component).toContainText('Main Content')
})
test('multiple slots should work', async ({ mount }) => {
const component = await mount(DefaultSlot, {
slots: {
default: ['one', 'two']
}
})
await expect(component).toContainText('one')
await expect(component).toContainText('two')
})
test('named slots should work', async ({ mount }) => {
const component = await mount(NamedSlots, {
slots: {
header: 'Header',
main: 'Main Content',
footer: 'Footer'
}
})
await expect(component).toContainText('Header')
await expect(component).toContainText('Main Content')
await expect(component).toContainText('Footer')
})
test('optionless should work', async ({ mount }) => {
const component = await mount(Component)
await expect(component).toContainText('test')
})
test('should run hooks', async ({ page, mount }) => {
const messages = []
page.on('console', m => messages.push(m.text()))
await mount(Button, {
props: {
title: 'Submit'
},
hooksConfig: { route: 'A' }
})
expect(messages).toEqual(['Before mount: {\"route\":\"A\"}, app: true', 'After mount el: HTMLButtonElement'])
})

View File

@ -16,7 +16,7 @@
import { playwrightTest as it, expect } from '../config/browserTest';
import type { AttributeSelector } from '../../packages/playwright-core/src/server/isomorphic/selectorParser';
import { parseAttributeSelector } from '../../packages/playwright-core/src/server/isomorphic/selectorParser';
import { parseAttributeSelector } from '../../packages/playwright-core/lib/server/isomorphic/selectorParser';
const parse = (selector: string) => parseAttributeSelector(selector, false);
const serialize = (parsed: AttributeSelector) => {

View File

@ -28,7 +28,8 @@ test('should return the location of a syntax error', async ({ runInlineTest }) =
expect(result.exitCode).toBe(1);
expect(result.passed).toBe(0);
expect(result.failed).toBe(0);
expect(result.output).toContain('error.spec.js:6');
expect(result.output).toContain('error.spec.js');
expect(result.output).toContain('(6:18)');
});
test('should print an improper error', async ({ runInlineTest }) => {
@ -163,19 +164,17 @@ test('should load an mjs file', async ({ runInlineTest }) => {
expect(exitCode).toBe(0);
});
test('should throw a nice error if a js file uses import', async ({ runInlineTest }) => {
const { exitCode, output } = await runInlineTest({
test('should allow using import', async ({ runInlineTest }) => {
const { exitCode } = await runInlineTest({
'a.spec.js': `
import fs from 'fs';
const { test } = folio;
const { test } = pwt;
test('succeeds', () => {
expect(1 + 1).toBe(2);
});
`
});
expect(exitCode).toBe(1);
expect(output).toContain('a.spec.js');
expect(output).toContain('Cannot use import statement outside a module');
expect(exitCode).toBe(0);
});
test('should load esm when package.json has type module', async ({ runInlineTest }) => {

View File

@ -261,8 +261,9 @@ test('should print errors with inconsistent message/stack', async ({ runInlineTe
});
expect(result.exitCode).toBe(1);
expect(result.failed).toBe(1);
expect(result.output).toContain('hi!Error: Hello');
expect(result.output).toContain('at myTest');
const output = stripAnsi(result.output);
expect(output).toContain('hi!Error: Hello');
expect(output).toContain('function myTest');
});
test('should print "no tests found" error', async ({ runInlineTest }) => {