Security News
Research
Supply Chain Attack on Rspack npm Packages Injects Cryptojacking Malware
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
babel-plugin-tester
Advanced tools
Utilities for testing babel plugins
You're writing a babel plugin and want to write tests for it.
This is a fairly simple abstraction to help you write tests for your babel
plugin. It works with jest
(my personal favorite) and most of it should also
work with mocha
and jasmine
.
This module is distributed via npm which is bundled with node and
should be installed as one of your project's devDependencies
:
npm install --save-dev babel-plugin-tester
import pluginTester from 'babel-plugin-tester'
// or
const pluginTester = require('babel-plugin-tester').default
import yourPlugin from '../your-plugin'
pluginTester({
plugin: yourPlugin,
tests: {
/* your test objects */
},
})
Your babel plugin. For example:
pluginTester({
plugin: identifierReversePlugin,
tests: {
/* your test objects */
},
})
// normally you would import this from your plugin module
function identifierReversePlugin() {
return {
name: 'identifier reverse',
visitor: {
Identifier(idPath) {
idPath.node.name = idPath.node.name.split('').reverse().join('')
},
},
}
}
This is used for the describe
title as well as the test titles.
This can be used to pass options into your plugin at transform time. This option can be overwritten using the test object.
To use babel.config.js instead of .babelrc, set babelOptions to the config object:
pluginTester({
plugin: yourPlugin,
...
babelOptions: require('./babel.config.js'),
...
tests: {
/* your test objects */
},
});
This can be used to specify a title for the describe block (rather than using
the pluginName
).
Relative paths from the other options will be relative to this. Normally you'll
provide this as filename: __filename
. The only options
property affected by
this value is fixtures
. Test Object properties affected by this value are:
fixture
and outputFixture
. If those properties are not absolute paths, then
they will be path.join
ed with path.dirname
of the filename
.
This is used to control which line endings the output from babel should have
Options | Description |
---|---|
lf | Unix - default |
crlf | Windows |
auto | Use the system default |
preserve | Use the line ending from the input |
This is a path to a directory with this format:
__fixtures__
├── first-test # test title will be: "first test"
│ ├── code.js # required
│ └── output.js # required
└── second-test
├── .babelrc # optional
├── options.json # optional
├── code.js
└── output.js
With this you could make your test config like so:
pluginTester({
plugin,
fixtures: path.join(__dirname, '__fixtures__'),
})
And it would run two tests. One for each directory in __fixtures__
, with
plugin options set to the content of options.json
Options are inherited, placing a options.json
file in __fixtures__
would add
those options to all fixtures.
You provide test objects as the tests
option to babel-plugin-tester
. You can
either provide the tests
as an object of test objects or an array of test
objects.
If you provide the tests as an object, the key will be used as the title of the test.
If you provide an array, the title will be derived from it's index and a
specified title
property or the pluginName
.
Read more about test objects below.
Use this to provide your own implementation of babel. This is particularly useful if you want to use a different version of babel than what's included in this package.
Use this to provide your own fixture output file extension. This is particularly useful if you are testing typescript input. If ommited fixture input file extension will be used.
The rest of the options you provide will be [lodash.merge
][lodash.merge]d with
each test object. Read more about those next!
A minimal test object can be:
string
representing codeobject
with a code
propertyHere are the available properties if you provide an object:
The code that you want to run through your babel plugin. This must be provided
unless you provide a fixture
instead. If there's no output
or
outputFixture
and snapshot
is not true
, then the assertion is that this
code is unchanged by the plugin.
If provided, this will be used instead of the pluginName
. If you're using the
object API, then the key
of this object will be the title (see example below).
If this is provided, the result of the plugin will be compared with this output for the assertion. It will have any indentation stripped and will be trimmed as a convenience for template literals.
If you'd rather put your code
in a separate file, you can specify a filename
here. If it's an absolute path, that's the file that will be loaded, otherwise,
this will be path.join
ed with the filename
path.
If you'd rather put your output
in a separate file, you can specify this
instead (works the same as fixture
).
To run only this test. Useful while developing to help focus on a single test. Can be used on multiple tests.
To skip running this test. Useful for when you're working on a feature that is not yet supported.
If you'd prefer to take a snapshot of your output rather than compare it to
something you hard-code, then specify snapshot: true
. This will take a
snapshot with both the source code and the output, making the snapshot easier to
understand.
If a particular test case should be throwing an error, you can that using one of the following:
{
// ...
error: true,
error: 'should have this exact message',
error: /should pass this regex/,
error: SyntaxError, // should be instance of this constructor
error: err => {
if (err instanceof SyntaxError && /message/.test(err.message)) {
return true; // test will fail if function doesn't return `true`
}
},
}
If you need something set up before a particular test is run, you can do this
with setup
. This function will be run before the test runs. It can return a
function which will be treated as a teardown
function. It can also return a
promise. If that promise resolves to a function, that will be treated as a
teardown
function.
If you set up some state, it's quite possible you want to tear it down. You can
either define this as its own property, or you can return it from the setup
function. This can likewise return a promise if it's asynchronous.
This defaults to a function which formats your code output with prettier. If you have prettier configured, then it will use your configuration. If you don't then it will be default configuration.
If you'd like to specify your own, then feel free to do so. Here's the API:
function customFormatter(code, {filename}) {
// format the code
return formattedCode
}
Learn more about the built-in formatter below.
The use case for this originally was for testing codemods and formatting their
result with prettier-eslint
.
import pluginTester from 'babel-plugin-tester'
import identifierReversePlugin from '../identifier-reverse-plugin'
// NOTE: you can use beforeAll, afterAll, beforeEach, and afterEach
// right here if you need
pluginTester({
// required
plugin: identifierReversePlugin,
// unnecessary if it's returned with the plugin
pluginName: 'identifier reverse',
// defaults to the plugin name
title: 'describe block title',
// used to test specific plugin options
pluginOptions: {
optionA: true,
},
// only necessary if you use fixture or outputFixture in your tests
filename: __filename,
// these will be `lodash.mergeWith`d with the test objects
// below are the defaults:
babelOptions: {
parserOpts: {},
generatorOpts: {},
babelrc: false,
configFile: false,
},
// use jest snapshots (only works with jest)
snapshot: false,
// defaults to a function that formats with prettier
formatResult: customFormatFunction,
// tests as objects
tests: {
// the key is the title
// the value is the code that is unchanged (because `snapshot: false`)
// test title will be: `1. does not change code with no identifiers`
'does not change code with no identifiers': '"hello";',
// test title will be: `2. changes this code`
'changes this code': {
// input to the plugin
code: 'var hello = "hi";',
// expected output
output: 'var olleh = "hi";',
},
},
// tests as an array
tests: [
// should be unchanged by the plugin (because `snapshot: false`)
// test title will be: `1. identifier reverse`
'"hello";',
{
// test title will be: `2. identifier reverse`
code: 'var hello = "hi";',
output: 'var olleh = "hi";',
},
{
// test title will be: `3. unchanged code`
title: 'unchanged code',
// because this is an absolute path, the `fixtures` above will not be
// used to resolve this path.
fixture: path.join(__dirname, 'some-path', 'unchanged.js'),
// no output, outputFixture, or snapshot, so the assertion will be that
// the plugin does not change this code.
},
{
// because these are not absolute paths, they will be joined with the
// `fixtures` path provided above
fixture: '__fixtures__/changed.js',
// because outputFixture is provided, the assertion will be that the
// plugin will change the contents of `changed.js` to the contents of
// `changed-output.js`
outputFixture: '__fixtures__/changed-output.js',
},
{
// as a convenience, this will have the indentation striped and it will
// be trimmed.
code: `
function sayHi(person) {
return 'Hello ' + person + '!'
}
`,
// this will take a jest snapshot. The snapshot will have both the
// source code and the transformed version to make the snapshot file
// easier to understand.
snapshot: true,
},
{
code: 'var hello = "hi";',
output: 'var olleh = "hi";',
// this can be used to overwrite the setting set above
pluginOptions: {
optionA: false,
},
},
{
title: 'unchanged code',
setup() {
// runs before this test
return function teardown() {
// runs after this tests
}
// can also return a promise
},
teardown() {
// runs after this test
// can return a promise
},
},
],
})
import pluginTester from 'babel-plugin-tester'
import identifierReversePlugin from '../identifier-reverse-plugin'
pluginTester({
plugin: identifierReversePlugin,
snapshot: true,
tests: [
{code: '"hello";', snapshot: false},
{
code: 'var hello = "hi";',
output: 'var olleh = "hi";',
},
`
function sayHi(person) {
return 'Hello ' + person + '!'
}
console.log(sayHi('Jenny'))
`,
],
})
If you're using jest and snapshots, then the snapshot output could have a bunch
of bothersome \"
to escape quotes because when Jest serializes a string, it
will wrap everything in double quotes. This isn't a huge deal, but it makes the
snapshots harder to read. So we automatically add a snapshot serializer for you
to remove those.
If you don't like that, then do this:
- import pluginTester from 'babel-plugin-tester'
+ import pluginTester from 'babel-plugin-tester/pure'
By default, a formatter is included which formats your results with
prettier
. It will look for a prettier configuration
relative to the file that's being tested or the current working directory. If it
can't find one, then it uses the default configuration for prettier.
This makes your snapshots easier to read. But if you'd like to not have that,
then you can either import the pure
file (as shown above) or you can override
the formatResult
option:
pluginTester({
// ... other options
formatResult: r => r,
// ... more options
})
I've been thinking about this for a while. The API was inspired by:
Looking to contribute? Look for the [Good First Issue][good-first-issue] label.
Please file an issue for bugs, missing documentation, or unexpected behavior.
[See Bugs][bugs]
Please file an issue to suggest new features. Vote on feature requests by adding a 👍. This helps maintainers prioritize what to work on.
[See Feature Requests][requests]
Thanks goes to these people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
MIT
FAQs
Utilities for testing babel plugins
The npm package babel-plugin-tester receives a total of 55,538 weekly downloads. As such, babel-plugin-tester popularity was classified as popular.
We found that babel-plugin-tester demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 2 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
A supply chain attack on Rspack's npm packages injected cryptomining malware, potentially impacting thousands of developers.
Research
Security News
Socket researchers discovered a malware campaign on npm delivering the Skuld infostealer via typosquatted packages, exposing sensitive data.
Security News
Sonar’s acquisition of Tidelift highlights a growing industry shift toward sustainable open source funding, addressing maintainer burnout and critical software dependencies.