npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@putout/test

v15.8.2

Published

Test runner for 🐊Putout plugins

Readme

@putout/test NPM version

Test runner for 🐊Putout. Basically it is supercharged tape with additional asseritions:

Install

npm i @putout/test -D

Autofix

Set environment variable UPDATE=1 to update transform and format fixtures (both source and fixed).

☝️ Remember that -fix.js fixtures will be removed when used in noReport, noTransform and noTransformWithOptions

UPDATE=1 tape test/*.js

When you need to override extension use UPDATE_EXTENSION, for example UPDATE_EXTENSION=abc will create files with extension 'abc'.

Plugins API

🐊Putout can be used in all IDE's supported byESLint as eslint-plugin-putout. When async rules will be supported we can switch to ESM.

To write test for your plugins you need initialize test using createTest:

import {createTest} from '@putout/test';
import * as rmVars from '@putout/plugin-remove-unused-variables';

const test = createTest(import.meta.url, {
    'remove-unused-variables': rmVars,
});

You can also pass all 🐊Putout options:

const test = createTest(import.meta.url, {
    plugins: [
        ['remove-unused-variables', rmVars],
    ],
});

Or use another linter with the same signature as 🐊Putout using lint field:

const test = createTest(import.meta.url, {
    lint: putout,
    plugins: [
        ['remove-unused-variables', rmVars],
    ],
});

You can also pass extensions to read from fixture directory:

const test = createTest(import.meta.url, {
    extension: 'wast',
    extensionFix: 'wast',
    lint: putout,
    plugins: [
        ['remove-unused-variables', rmVars],
    ],
});

And apply new operators:

const test = createTest(import.meta.url, {
    extension: 'wast',
    lint: putout,
    plugins: [
        ['remove-unused-variables', rmVars],
    ],
}, {
    render: (operator) => (name) => operator.transform,
});

report(filename, message: string | string[], plugins?: PutoutPlugin[])

Check error message (or messages) of a plugin:

test('remove useless variables: dot', (t) => {
    t.report('dot', 'Dot files should be added to .gitignore');
    t.end();
});

When you want to check that report called exact count of times pass an array of messages:

test('remove useless variables: dot', (t) => {
    t.report('dot', [
        'Dot files should be added to .gitignore',
    ]);
    t.end();
});

reportCode(input: string, message: string | string[], plugins?: PutoutPlugins)

Check error message of a plugin from input code:

test('remove debugger: report', (t) => {
    t.reportCode('debugger', 'Unexpected "debugger" statement');
    t.end();
});

transform(filename [, output, plugins])

Check transform of filename.js -> filename-fix.js in test/fixtures directory:

test('remove useless variables: array-from', (t) => {
    t.transform('array-from', {
        'remove-useless-array-from': removeUselessArrayFrom,
    });
    t.end();
});

☝️When input and output the same test fails. Use noTransform() for such cases.

transformCode(input, output)

Check transform of input -> output code:

test('remove-console: property identifier: code', (t) => {
    t.transformCode('console.log()', '');
    t.end();
});

reportWithOptions(filename, message: string | string[], options)

Check report of filename.js with options:

test('putout: test: reportWithOptions', (t) => {
    const cache = new Map();
    cache.set('x', 'y');
    
    t.reportWithOptions('remove-import', 'avoid imports', {
        cache,
    });
    t.end();
});

noReportWithOptions(filename, options)

Check no report of filename.js with options:

test('putout: test: no report with options: remove-import', (t) => {
    const cache = new Map();
    
    t.noReportWithOptions('remove-import', {
        cache,
    });
    t.end();
});

transformWithOptions(filename, options)

Check transform of filename.js with options:

test('putout: plugin: declare-undefined-variables: transform with options: parse', (t) => {
    t.transformWithOptions('parse', {
        dismiss: [
            'assign',
            'stringify',
        ],
    });
    t.end();
});

noTransformWithOptions(filename, options)

When file should not be transformed:

test('test: declared', (t) => {
    t.noTransform('declared');
    t.end();
});

noTransformWithOptions(filename, options)

Check transform of filename.js with options:

test('putout: plugin: declare-undefined-variables: transform: assign: dismiss', (t) => {
    t.noTransformWithOptions('assign', {
        dismiss: [
            'assign',
            'stringify',
        ],
    });
    t.end();
});

noReport(filename [, plugins])

Check error message of a plugin not produces

test('plugin-putout: check-replace-code: no report: typescript', (t) => {
    t.noReport('typescript');
    t.end();
});

with plugins:

test('plugin-putout: check-replace-code: no report: hello', (t) => {
    t.noReport('typescript', [
        ['plugin', plugin],
    ]);
    t.end();
});

noReportCode(filename)

Check error message of a plugin not produces

test('plugin-putout: check-replace-code: no report: import', (t) => {
    t.noReportCode(`import a from 'a'`);
    t.end();
});

noReportAfterTransform(filename)

Check error message of a plugin not produced

test('test: no report after transform: file', (t) => {
    t.noReportAfterTransform('file');
    t.end();
});

noReportAfterTransformWithOptions(filename)

Check error message of a plugin not produced with provided options:

test('test: no report', (t) => {
    t.noReportAfterTransformWithOptions('file');
    t.end();
});

noTransform(filename)

Check transform of filename.js produce nothing

test('plugin-apply-numeric-separators: no transform: hex', (t) => {
    t.noTransform('hex');
    t.end();
});

progress(filename, expected)

Check progress of filename.js;

test('remove useless variables: for-of', async ({progress}) => {
    await progress('progress', {
        i: 0,
        n: 2,
        rule: 'read-all-files',
    });
});

progressWithOptions(filename, options, expected)

Check progress of filename.js;

test('remove useless variables: for-of', async ({progressWithOptions}) => {
    const options = {
        from: '/home/coderaiser',
        to: '/',
    };
    
    await progressWithOptions('progress', options, {
        i: 0,
        n: 2,
        rule: 'read-all-files',
    });
});

Formatters API

First you need to create test with:

import {createTest} from '@putout/test';
import * as rmVars from '@putout/plugin-remove-unused-variables';

const test = createTest(import.meta.url, {
    'remove-unused-variables': rmVars,
});

format(formatter, filename)

Check file name formatting (pass process.env.UPDATE=1 to save fixture):

test('formatter: codeframe', async ({format}) => {
    await format(codeframe);
});

noFormat

Check that there is no formatting for for such file:

test('formatter: codeframe: no', async ({noFormat}) => {
    await noFormat(codeframe, 'no');
});

formatMany(formatter, [filename1, filename2])

Check file name formatting (pass process.env.UPDATE=1 to save fixture):

test('formatter: dump: many', async ({formatMany}) => {
    await formatMany(dump, ['var', 'var']);
});

Usage Example

Here is example of tests for remove-console:

const {createTest} = require('@putout/test');
const removeConsole = require('@putout/plugin-remove-console');

const test = createTest(__dirname, {
    'remove-console': removeConsole,
});

test('remove-console: report: property-identifier', (t) => {
    t.report('property-identifier', 'Unexpected "console" call');
    t.end();
});

test('remove-console: property-identifier', (t) => {
    t.transform('property-identifier');
    t.end();
});

// when code should not be transformed
test('test: declared', (t) => {
    t.noTransformCode('alert()');
    t.end();
});

ESLint API

First you need to create test with:

import {createTest} from '@putout/test/eslint';

const test = createTest(import.meta.url, config);

process(filename [, config])

Works in similar to transform way:

  • ✅ reads operator-linebreak.js;
  • ✅ transforms it;
  • ✅ check that transformed is equal to operator-linebreak-fix.js;

Example:

test('test: eslint: transform', async ({process}) => {
    await process('operator-linebreak');
});

test('test: eslint: transform', async ({process}) => {
    await process('operator-linebreak', {
        rules: {
            'putout/putout': {
                rules: {
                    'convert-esm-to-commonjs': 'on',
                },
            },
        },
    });
});

☝️When input and output the same test fails. Use noProcess() for such cases.

noProcess(filename [, overrides])

Check that filename would not be processed.

Example:

test('test: eslint: noProcess', async ({noProcess}) => {
    await noProcess('operator-linebreak-fix');
});

test('test: eslint: noProcess', async ({noProcess}) => {
    await noProcess('operator-linebreak-fix', {
        rules: {
            'putout/putout': ['error', {}],
        },
    });
});

comparePlaces(filename, places[, overrides])

test('eslint-config: operator-line-break', async ({comparePlaces}) => {
    await comparePlaces('operator-linebreak', [{
        message: 'There should be no line break before or after \'=\'.',
        position: {
            column: 1,
            line: 2,
        },
        rule: 'operator-linebreak (eslint)',
    }]);
});

with overrides:

test('eslint-config: operator-line-break', async ({comparePlaces}) => {
    const overrides = {
        extends: ['plugin:putout/safe'],
    };
    
    await comparePlaces('operator-linebreak', [{
        message: 'There should be no line break before or after \'=\'.',
        position: {
            column: 1,
            line: 2,
        },
        rule: 'operator-linebreak (eslint)',
    }], overrides);
});

transform(name)

test('test: eslint: transform: remove-debugger', (t) => {
    t.transform('remove-debugger');
    t.end();
});

report(filename, message | []messages)

test('test: eslint: report: remove-debugger', (t) => {
    t.report('remove-debugger', `Avoid 'debugger' statement`);
    t.end();
});

Processors API

With processors api you can test processors in a simplest possible way.

First things first, init test with:

import {createTest} from '@putout/test/processor';

const test = createTest(import.meta.url, {
    extension: 'json',
    processors: ['json'],
    plugins: ['eslint'],
});

process(filename [, plugins, ])

Example:

test('putout: processor: json', async ({process}) => {
    await process('eslintrc');
});

test('putout: processor: json', async ({process}) => {
    await process('package', ['package-json']);
});

noProcess(filename [, plugins, processors])

Check that filename would not be processed.

Example:

test('putout: process: json: no process', async ({noProcess}) => {
    await noProcess('eslintrc', [], ['json']);
});

comparePlaces(filename, places)

test('putout: processor: css: places', async ({comparePlaces}) => {
    await comparePlaces('style', [{
        message: 'Expected indentation of 4 spaces (indentation)',
        position: {
            column: 1,
            line: 2,
        },
        rule: 'indentation (stylelint)',
    }]);
});

Filesystem API

import {test} from '@putout/test/filesystem';

equalFilesystem(ast, expected)

test('putout: operator: filesystem: findFile: exclude', (t) => {
    const ast = parseFilesystem([
        '/',
        '/hello/',
        '/hello/world.txt',
        '/hello/hello.txt',
    ]);
    
    findFile(ast, '*.txt', ['hello.txt']).map(removeFile);
    
    const expected = {
        type: 'directory',
        filename: '/',
        files: [{
            type: 'directory',
            filename: '/hello',
            files: [{
                type: 'file',
                filename: '/hello/hello.txt',
            }],
        }],
    };
    
    t.equalFilesystems(ast, expected);
    t.end();
});

parseFilesystem(array)

test('putout: operator: filesystem: writeFileContent: emoji', (t) => {
    const ast = parseFilesystem([
        '/hello/world/',
        '/hello/world/README.md',
    ]);
    
    const [filePath] = findFile(ast, 'README.md');
    
    writeFileContent(filePath, 'hello 🐊');
    const content = readFileContent(filePath);
    
    t.equal(content, 'hello 🐊');
    t.end();
});

License

MIT