453 lines
12 KiB
JavaScript
453 lines
12 KiB
JavaScript
/* eslint no-console:0 */
|
|
import path from 'path'
|
|
import cosmiconfigMock from 'cosmiconfig'
|
|
import cpy from 'cpy'
|
|
import babel from '@babel/core'
|
|
import pluginTester from 'babel-plugin-tester'
|
|
import plugin from '../'
|
|
|
|
const projectRoot = path.join(__dirname, '../../')
|
|
|
|
jest.mock('cosmiconfig', () => {
|
|
const mockSearchSync = jest.fn()
|
|
Object.assign(mockSearchSync, {
|
|
mockReset() {
|
|
return mockSearchSync.mockImplementation(
|
|
(filename, configuredCosmiconfig) =>
|
|
configuredCosmiconfig.searchSync(filename),
|
|
)
|
|
},
|
|
})
|
|
|
|
mockSearchSync.mockReset()
|
|
|
|
const _cosmiconfigMock = (...args) => ({
|
|
searchSync(filename) {
|
|
return mockSearchSync(
|
|
filename,
|
|
require.requireActual('cosmiconfig')(...args),
|
|
)
|
|
},
|
|
})
|
|
|
|
return Object.assign(_cosmiconfigMock, {mockSearchSync})
|
|
})
|
|
|
|
beforeAll(() => {
|
|
// copy our mock modules to the node_modules directory
|
|
// so we can test how things work when importing a macro
|
|
// from the node_modules directory.
|
|
return cpy(['**/*.js'], path.join('..', '..', 'node_modules'), {
|
|
parents: true,
|
|
cwd: path.join(projectRoot, 'other', 'mock-modules'),
|
|
})
|
|
})
|
|
|
|
afterEach(() => {
|
|
// eslint-disable-next-line
|
|
require('babel-plugin-macros-test-fake/macro').innerFn.mockClear()
|
|
cosmiconfigMock.mockSearchSync.mockReset()
|
|
})
|
|
|
|
expect.addSnapshotSerializer({
|
|
print(val) {
|
|
return (
|
|
val
|
|
.split(projectRoot)
|
|
.join('<PROJECT_ROOT>/')
|
|
.replace(/\\/g, '/')
|
|
// Remove the path of file which thrown an error
|
|
.replace(/Error:[^:]*:/, 'Error:')
|
|
)
|
|
},
|
|
test(val) {
|
|
return typeof val === 'string'
|
|
},
|
|
})
|
|
|
|
pluginTester({
|
|
plugin,
|
|
snapshot: true,
|
|
babelOptions: {
|
|
filename: __filename,
|
|
parserOpts: {
|
|
plugins: ['jsx'],
|
|
},
|
|
generatorOpts: {quotes: 'double'},
|
|
},
|
|
tests: [
|
|
{
|
|
title: 'does nothing to code that does not import macro',
|
|
snapshot: false,
|
|
code: `
|
|
import foo from "./some-file-without-macro";
|
|
|
|
const bar = require("./some-other-file-without-macro");
|
|
`,
|
|
},
|
|
{
|
|
title: 'does nothing but remove macros if it is unused',
|
|
snapshot: true,
|
|
code: `
|
|
import foo from "./fixtures/eval.macro";
|
|
|
|
const bar = 42;
|
|
`,
|
|
},
|
|
{
|
|
title: 'raises an error if macro does not exist',
|
|
error: true,
|
|
code: `
|
|
import foo from './some-macros-that-doesnt-even-need-to-exist.macro'
|
|
export default 'something else'
|
|
`,
|
|
},
|
|
{
|
|
title: 'works with import',
|
|
code: `
|
|
import myEval from './fixtures/eval.macro'
|
|
const x = myEval\`34 + 45\`
|
|
`,
|
|
},
|
|
{
|
|
title: 'works with require',
|
|
code: `
|
|
const evaler = require('./fixtures/eval.macro')
|
|
const x = evaler\`34 + 45\`
|
|
`,
|
|
},
|
|
{
|
|
title: 'works with require destructuring',
|
|
code: `
|
|
const {css, styled} = require('./fixtures/emotion.macro')
|
|
const red = css\`
|
|
background-color: red;
|
|
\`
|
|
|
|
const Div = styled.div\`
|
|
composes: \${red}
|
|
color: blue;
|
|
\`
|
|
`,
|
|
},
|
|
{
|
|
title: 'works with require destructuring and aliasing',
|
|
code: `
|
|
const {css: CSS, styled: STYLED} = require('./fixtures/emotion.macro')
|
|
const red = CSS\`
|
|
background-color: red;
|
|
\`
|
|
|
|
const Div = STYLED.div\`
|
|
composes: \${red}
|
|
color: blue;
|
|
\`
|
|
`,
|
|
},
|
|
{
|
|
title: 'works with function calls',
|
|
code: `
|
|
import myEval from './fixtures/eval.macro'
|
|
const x = myEval('34 + 45')
|
|
`,
|
|
},
|
|
{
|
|
title: 'Works as a JSXElement',
|
|
code: `
|
|
import MyEval from './fixtures/eval.macro'
|
|
const x = <MyEval>34 + 45</MyEval>
|
|
`,
|
|
},
|
|
{
|
|
title: 'Supports named imports',
|
|
code: `
|
|
import {css as CSS, styled as STYLED} from './fixtures/emotion.macro'
|
|
const red = CSS\`
|
|
background-color: red;
|
|
\`
|
|
|
|
const Div = STYLED.div\`
|
|
composes: \${red}
|
|
color: blue;
|
|
\`
|
|
`,
|
|
},
|
|
{
|
|
title: 'supports compiled macros (`__esModule` + `export default`)',
|
|
code: `
|
|
import {css, styled} from './fixtures/emotion-esm.macro'
|
|
const red = css\`
|
|
background-color: red;
|
|
\`
|
|
|
|
const Div = styled.div\`
|
|
composes: \${red}
|
|
color: blue;
|
|
\`
|
|
`,
|
|
},
|
|
{
|
|
title: 'supports macros from node_modules',
|
|
code: `
|
|
import fakeMacro from 'babel-plugin-macros-test-fake/macro'
|
|
fakeMacro('hi')
|
|
`,
|
|
teardown() {
|
|
try {
|
|
// kinda abusing the babel-plugin-tester API here
|
|
// to make an extra assertion
|
|
// eslint-disable-next-line
|
|
const fakeMacro = require('babel-plugin-macros-test-fake/macro')
|
|
expect(fakeMacro.innerFn).toHaveBeenCalledTimes(1)
|
|
expect(fakeMacro.innerFn).toHaveBeenCalledWith({
|
|
references: expect.any(Object),
|
|
source: expect.stringContaining(
|
|
'babel-plugin-macros-test-fake/macro',
|
|
),
|
|
state: expect.any(Object),
|
|
babel: expect.any(Object),
|
|
isBabelMacrosCall: true,
|
|
})
|
|
expect(fakeMacro.innerFn.mock.calls[0].babel).toBe(babel)
|
|
} catch (e) {
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
},
|
|
},
|
|
{
|
|
title: 'optionally keep imports (variable assignment)',
|
|
code: `
|
|
const macro = require('./fixtures/keep-imports.macro')
|
|
const red = macro('noop');
|
|
`,
|
|
},
|
|
{
|
|
title: 'optionally keep imports (import declaration)',
|
|
code: `
|
|
import macro from './fixtures/keep-imports.macro'
|
|
const red = macro('noop');
|
|
`,
|
|
},
|
|
{
|
|
title:
|
|
'optionally keep imports in combination with babel-preset-env (#80)',
|
|
code: `
|
|
import macro from './fixtures/keep-imports.macro'
|
|
const red = macro('noop')
|
|
`,
|
|
babelOptions: {
|
|
plugins: [
|
|
require.resolve('babel-plugin-transform-es2015-modules-commonjs'),
|
|
],
|
|
},
|
|
},
|
|
{
|
|
title: 'throws an error if the macro is not properly wrapped',
|
|
error: true,
|
|
code: `
|
|
import unwrapped from './fixtures/non-wrapped.macro'
|
|
unwrapped('hey')
|
|
`,
|
|
},
|
|
{
|
|
title: 'forwards MacroErrors thrown by the macro',
|
|
error: true,
|
|
code: `
|
|
import errorThrower from './fixtures/macro-error-thrower.macro'
|
|
errorThrower('hey')
|
|
`,
|
|
},
|
|
{
|
|
title: 'prepends the relative path for errors thrown by the macro',
|
|
error: true,
|
|
code: `
|
|
import errorThrower from './fixtures/error-thrower.macro'
|
|
errorThrower('hey')
|
|
`,
|
|
},
|
|
{
|
|
title: 'appends the npm URL for errors thrown by node modules',
|
|
error: true,
|
|
code: `
|
|
import errorThrower from 'babel-plugin-macros-test-error-thrower.macro'
|
|
errorThrower('hi')
|
|
`,
|
|
},
|
|
{
|
|
title:
|
|
'appends the npm URL for errors thrown by node modules with a slash',
|
|
error: true,
|
|
code: `
|
|
import errorThrower from 'babel-plugin-macros-test-error-thrower/macro'
|
|
errorThrower('hi')
|
|
`,
|
|
},
|
|
{
|
|
title: 'macros can set their configName and get their config',
|
|
fixture: path.join(__dirname, 'fixtures/config/code.js'),
|
|
teardown() {
|
|
try {
|
|
const babelMacrosConfig = require('./fixtures/config/babel-plugin-macros.config')
|
|
const configurableMacro = require('./fixtures/config/configurable.macro')
|
|
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
|
|
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual(
|
|
babelMacrosConfig[configurableMacro.configName],
|
|
)
|
|
|
|
configurableMacro.realMacro.mockClear()
|
|
} catch (e) {
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
},
|
|
},
|
|
{
|
|
title:
|
|
'when there is an error reading the config, a helpful message is logged',
|
|
error: true,
|
|
fixture: path.join(__dirname, 'fixtures/config/code.js'),
|
|
setup() {
|
|
cosmiconfigMock.mockSearchSync.mockImplementationOnce(() => {
|
|
throw new Error('this is a cosmiconfig error')
|
|
})
|
|
const originalError = console.error
|
|
console.error = jest.fn()
|
|
return function teardown() {
|
|
try {
|
|
expect(console.error).toHaveBeenCalledTimes(1)
|
|
expect(console.error.mock.calls[0]).toMatchSnapshot()
|
|
console.error = originalError
|
|
} catch (e) {
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
title: 'when there is no config to load, then no config is passed',
|
|
fixture: path.join(__dirname, 'fixtures/config/code.js'),
|
|
setup() {
|
|
cosmiconfigMock.mockSearchSync.mockImplementationOnce(() => null)
|
|
return function teardown() {
|
|
try {
|
|
const configurableMacro = require('./fixtures/config/configurable.macro')
|
|
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
|
|
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual(
|
|
{},
|
|
)
|
|
configurableMacro.realMacro.mockClear()
|
|
} catch (e) {
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
}
|
|
},
|
|
},
|
|
{
|
|
title: 'when configuration is specified in plugin options',
|
|
pluginOptions: {
|
|
configurableMacro: {
|
|
someConfig: false,
|
|
somePluginConfig: true,
|
|
},
|
|
},
|
|
fixture: path.join(__dirname, 'fixtures/config/code.js'),
|
|
teardown() {
|
|
try {
|
|
const configurableMacro = require('./fixtures/config/configurable.macro')
|
|
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
|
|
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual({
|
|
fileConfig: true,
|
|
someConfig: true,
|
|
somePluginConfig: true,
|
|
})
|
|
configurableMacro.realMacro.mockClear()
|
|
} catch (e) {
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
},
|
|
},
|
|
{
|
|
title: 'when configuration is specified in plugin options',
|
|
pluginOptions: {
|
|
configurableMacro: {
|
|
someConfig: false,
|
|
somePluginConfig: true,
|
|
},
|
|
},
|
|
fixture: path.join(__dirname, 'fixtures/config/cjs-code.js'),
|
|
teardown() {
|
|
try {
|
|
const configurableMacro = require('./fixtures/config/configurable.macro')
|
|
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
|
|
expect(configurableMacro.realMacro.mock.calls[0][0].config).toEqual({
|
|
fileConfig: true,
|
|
someConfig: true,
|
|
somePluginConfig: true,
|
|
})
|
|
configurableMacro.realMacro.mockClear()
|
|
} catch (e) {
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
},
|
|
},
|
|
{
|
|
title: 'when configuration is specified incorrectly in plugin options',
|
|
fixture: path.join(__dirname, 'fixtures/config/code.js'),
|
|
pluginOptions: {
|
|
configurableMacro: 2,
|
|
},
|
|
teardown() {
|
|
try {
|
|
const configurableMacro = require('./fixtures/config/configurable.macro')
|
|
expect(configurableMacro.realMacro).toHaveBeenCalledTimes(1)
|
|
expect(configurableMacro.realMacro).not.toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
config: expect.any,
|
|
}),
|
|
)
|
|
configurableMacro.realMacro.mockClear()
|
|
} catch (e) {
|
|
console.error(e)
|
|
throw e
|
|
}
|
|
},
|
|
},
|
|
{
|
|
title:
|
|
'when plugin options configuration cannot be merged with file configuration',
|
|
error: true,
|
|
fixture: path.join(__dirname, 'fixtures/primitive-config/code.js'),
|
|
pluginOptions: {
|
|
configurableMacro: {},
|
|
},
|
|
},
|
|
{
|
|
title:
|
|
'when a plugin that replaces paths is used, macros still work properly',
|
|
fixture: path.join(
|
|
__dirname,
|
|
'fixtures/path-replace-issue/variable-assignment.js',
|
|
),
|
|
babelOptions: {
|
|
babelrc: true,
|
|
},
|
|
},
|
|
{
|
|
title: 'Macros are applied in the order respecting plugins order',
|
|
code: `
|
|
import Wrap from "./fixtures/jsx-id-prefix.macro";
|
|
|
|
const bar = Wrap(<div id="d1"><p id="p1"></p></div>);
|
|
`,
|
|
babelOptions: {
|
|
presets: [{plugins: [require('./fixtures/jsx-id-prefix.plugin')]}],
|
|
},
|
|
},
|
|
],
|
|
})
|