Skip to content

Commit

Permalink
Add test.only helper to test a single case (#2236)
Browse files Browse the repository at this point in the history
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
  • Loading branch information
fisker and sindresorhus authored Dec 20, 2023
1 parent 9d7048c commit c91c6ad
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 20 deletions.
1 change: 1 addition & 0 deletions scripts/internal-rules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const RULES_DIRECTORIES = [
const rules = [
{id: 'fix-snapshot-test', directories: TEST_DIRECTORIES},
{id: 'prefer-negative-boolean-attribute', directories: RULES_DIRECTORIES},
{id: 'no-test-only', directories: TEST_DIRECTORIES},
];

const isFileInsideDirectory = (filename, directory) => filename.startsWith(directory + path.sep);
Expand Down
74 changes: 74 additions & 0 deletions scripts/internal-rules/no-test-only.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';
const path = require('node:path');

const messageId = path.basename(__filename, '.js');

module.exports = {
create(context) {
if (path.basename(context.physicalFilename) === 'snapshot-rule-tester.mjs') {
return {};
}

return {
MemberExpression(node) {
if (
!(
!node.computed
&& !node.optional
&& node.object.type === 'Identifier'
&& node.object.name === 'test'
&& node.property.type === 'Identifier'
&& node.property.name === 'only'
)
) {
return;
}

const isTaggedTemplateExpression = node.parent.type === 'TaggedTemplateExpression' && node.parent.tag === node;
const isCallee = !isTaggedTemplateExpression
&& node.parent.type === 'CallExpression'
&& node.parent.callee === node
&& !node.parent.optional
&& node.parent.arguments.length === 1;

const problem = {node, messageId};

if (isTaggedTemplateExpression) {
problem.fix = fixer => fixer.remove(node);
}

if (isCallee) {
problem.fix = function * (fixer) {
const {sourceCode} = context;
const openingParenToken = sourceCode.getTokenAfter(node);
const closingParenToken = sourceCode.getLastToken(node.parent);
if (openingParenToken.value !== '(' || closingParenToken.value !== ')') {
return;
}

yield fixer.remove(node);
yield fixer.remove(openingParenToken);
yield fixer.remove(closingParenToken);

// Trailing comma
const tokenBefore = sourceCode.getTokenBefore(closingParenToken);

if (tokenBefore.value !== ',') {
return;
}

yield fixer.remove(tokenBefore);
};
}

context.report(problem);
},
};
},
meta: {
fixable: 'code',
messages: {
[messageId]: '`test.only` should only be used for debugging purposes. Please remove it before committing.',
},
},
};
10 changes: 5 additions & 5 deletions test/utils/snapshot-rule-tester.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function normalizeTests(tests) {

const additionalProperties = getAdditionalProperties(
testCase,
['code', 'options', 'filename', 'parserOptions', 'parser', 'globals'],
['code', 'options', 'filename', 'parserOptions', 'parser', 'globals', 'only'],
);

if (additionalProperties.length > 0) {
Expand Down Expand Up @@ -154,11 +154,11 @@ class SnapshotRuleTester {
const {valid, invalid} = normalizeTests(tests);

for (const [index, testCase] of valid.entries()) {
const {code, filename} = testCase;
const {code, filename, only} = testCase;
const verifyConfig = getVerifyConfig(ruleId, config, testCase);
defineParser(linter, verifyConfig.parser);

test(
(only ? test.only : test)(
`valid(${index + 1}): ${code}`,
t => {
const messages = verify(linter, code, verifyConfig, {filename});
Expand All @@ -168,12 +168,12 @@ class SnapshotRuleTester {
}

for (const [index, testCase] of invalid.entries()) {
const {code, options, filename} = testCase;
const {code, options, filename, only} = testCase;
const verifyConfig = getVerifyConfig(ruleId, config, testCase);
defineParser(linter, verifyConfig.parser);
const runVerify = code => verify(linter, code, verifyConfig, {filename});

test(
(only ? test.only : test)(
`invalid(${index + 1}): ${code}`,
t => {
const messages = runVerify(code);
Expand Down
55 changes: 40 additions & 15 deletions test/utils/test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import SnapshotRuleTester from './snapshot-rule-tester.mjs';
import defaultOptions from './default-options.mjs';
import parsers from './parsers.mjs';

function normalizeTests(tests) {
return tests.map(test => typeof test === 'string' ? {code: test} : test);
function normalizeTestCase(testCase) {
return typeof testCase === 'string' ? {code: testCase} : {...testCase};
}

function normalizeInvalidTest(test, rule) {
Expand All @@ -34,22 +34,45 @@ function normalizeInvalidTest(test, rule) {
}

function normalizeParser(options) {
const {
let {
parser,
parserOptions,
} = options;

if (parser) {
if (parser.name) {
options.parser = parser.name;
if (parser.mergeParserOptions) {
parserOptions = parser.mergeParserOptions(parserOptions);
}

if (parser.mergeParserOptions) {
options.parserOptions = parser.mergeParserOptions(parserOptions);
if (parser.name) {
parser = parser.name;
}
}

return options;
return {...options, parser, parserOptions};
}

// https://github.com/tc39/proposal-array-is-template-object
const isTemplateObject = value => Array.isArray(value?.raw);
// https://github.com/tc39/proposal-string-cooked
const cooked = (raw, ...substitutions) => String.raw({raw}, ...substitutions);

function only(...arguments_) {
/*
```js
only`code`;
```
*/
if (isTemplateObject(arguments_[0])) {
return {code: cooked(...arguments_), only: true};
}

/*
```js
only('code');
only({code: 'code'});
*/
return {...normalizeTestCase(arguments_[0]), only: true};
}

class Tester {
Expand Down Expand Up @@ -98,8 +121,8 @@ class Tester {
} = tests;

testerOptions = normalizeParser(testerOptions);
valid = normalizeTests(valid).map(test => normalizeParser(test));
invalid = normalizeTests(invalid).map(test => normalizeParser(test));
valid = valid.map(testCase => normalizeParser(normalizeTestCase(testCase)));
invalid = invalid.map(testCase => normalizeParser(normalizeTestCase(testCase)));

const tester = new SnapshotRuleTester(test, {
...testerOptions,
Expand All @@ -126,6 +149,7 @@ function getTester(importMeta) {
const tester = new Tester(ruleId);
const runTest = Tester.prototype.runTest.bind(tester);
runTest.snapshot = Tester.prototype.snapshot.bind(tester);
runTest.only = only;

for (const [parserName, parserSettings] of Object.entries(parsers)) {
Reflect.defineProperty(runTest, parserName, {
Expand All @@ -152,10 +176,11 @@ function getTester(importMeta) {
};
}

const addComment = (test, comment) => {
const {code, output} = test;
const addComment = (testCase, comment) => {
testCase = normalizeTestCase(testCase);
const {code, output} = testCase;
const fixedTest = {
...test,
...testCase,
code: `${code}\n/* ${comment} */`,
};
if (Object.prototype.hasOwnProperty.call(fixedTest, 'output') && typeof output === 'string') {
Expand All @@ -169,8 +194,8 @@ const avoidTestTitleConflict = (tests, comment) => {
const {valid, invalid} = tests;
return {
...tests,
valid: normalizeTests(valid).map(test => addComment(test, comment)),
invalid: normalizeTests(invalid).map(test => addComment(test, comment)),
valid: valid.map(testCase => addComment(testCase, comment)),
invalid: invalid.map(testCase => addComment(testCase, comment)),
};
};

Expand Down

0 comments on commit c91c6ad

Please sign in to comment.