Skip to content

Commit 8810410

Browse files
authored
chore: improve snapshots in rule tester (#966)
1 parent 9c575c6 commit 8810410

File tree

69 files changed

+5077
-33645
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+5077
-33645
lines changed

jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,5 @@ module.exports = {
1010
'@eslint/eslintrc/universal': '@eslint/eslintrc/dist/eslintrc-universal.cjs',
1111
},
1212
snapshotSerializers: ['jest-snapshot-serializer-raw/always'],
13+
snapshotResolver: './snapshot-resolver.js',
1314
};

packages/plugin/src/testkit.ts

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
/* eslint-env jest */
12
import { readFileSync } from 'fs';
23
import { resolve } from 'path';
3-
import { RuleTester, AST, Linter } from 'eslint';
4+
import { RuleTester, AST, Linter, Rule } from 'eslint';
45
import { ASTKindToNode } from 'graphql';
56
import { codeFrameColumns } from '@babel/code-frame';
67
import { GraphQLESTreeNode } from './estree-parser';
@@ -35,6 +36,12 @@ function printCode(code: string): string {
3536
);
3637
}
3738

39+
// A simple version of `SourceCodeFixer.applyFixes`
40+
// https://github.com/eslint/eslint/issues/14936#issuecomment-906746754
41+
function applyFix(code: string, fix: Rule.Fix): string {
42+
return [code.slice(0, fix.range[0]), fix.text, code.slice(fix.range[1])].join('');
43+
}
44+
3845
export class GraphQLRuleTester extends RuleTester {
3946
config: {
4047
parser: string;
@@ -97,37 +104,73 @@ export class GraphQLRuleTester extends RuleTester {
97104
const hasOnlyTest = tests.invalid.some(t => t.only);
98105

99106
for (const testCase of tests.invalid) {
100-
const { only, code, filename } = testCase;
107+
const { only, filename, options } = testCase;
101108
if (hasOnlyTest && !only) {
102109
continue;
103110
}
104111

112+
const code = removeTrailingBlankLines(testCase.code);
105113
const verifyConfig = getVerifyConfig(name, this.config, testCase);
106114
defineParser(linter, verifyConfig.parser);
107115

108116
const messages = linter.verify(code, verifyConfig, { filename });
109117
const messageForSnapshot: string[] = [];
118+
const hasMultipleMessages = messages.length > 1;
119+
if (hasMultipleMessages) {
120+
messageForSnapshot.push('Code', indentCode(printCode(code)));
121+
}
122+
if (options) {
123+
const opts = JSON.stringify(options, null, 2).slice(1, -1);
124+
messageForSnapshot.push('⚙️ Options', indentCode(removeTrailingBlankLines(opts), 2));
125+
}
126+
110127
for (const [index, message] of messages.entries()) {
111128
if (message.fatal) {
112129
throw new Error(message.message);
113130
}
114131

115-
messageForSnapshot.push(`❌ Error ${index + 1}/${messages.length}`, visualizeEslintMessage(code, message));
132+
const codeWithMessage = visualizeEslintMessage(code, message, hasMultipleMessages ? 1 : undefined);
133+
messageForSnapshot.push(printWithIndex('❌ Error', index, messages.length), indentCode(codeWithMessage));
134+
135+
const { suggestions } = message;
116136

137+
// Don't print suggestions in snapshots for too big codes
138+
if (suggestions && (code.match(/\n/g) || '').length < 1000) {
139+
for (const [i, suggestion] of message.suggestions.entries()) {
140+
const output = applyFix(code, suggestion.fix);
141+
const title = printWithIndex('💡 Suggestion', i, suggestions.length, suggestion.desc);
142+
messageForSnapshot.push(title, indentCode(printCode(output), 2));
143+
}
144+
}
117145
}
146+
118147
if (rule.meta.fixable) {
119148
const { fixed, output } = linter.verifyAndFix(code, verifyConfig, { filename });
120149
if (fixed) {
121-
messageForSnapshot.push('🔧 Autofix output', indentCode(printCode(output), 2));
150+
messageForSnapshot.push('🔧 Autofix output', indentCode(codeFrameColumns(output, {} as any)));
122151
}
123152
}
124153
expect(messageForSnapshot.join('\n\n')).toMatchSnapshot();
125154
}
126155
}
127156
}
128157

158+
function removeTrailingBlankLines(text: string): string {
159+
return text.replace(/^\s*\n/gm, '').trimEnd();
160+
}
161+
162+
function printWithIndex(title: string, index: number, total: number, description?: string): string {
163+
if (total > 1) {
164+
title += ` ${index + 1}/${total}`;
165+
}
166+
if (description) {
167+
title += `: ${description}`;
168+
}
169+
return title;
170+
}
171+
129172
function getVerifyConfig(ruleId: string, testerConfig, testCase) {
130-
const { options, parserOptions, parser = testerConfig.parser } = testCase;
173+
const { parser = testerConfig.parser, parserOptions, options } = testCase;
131174

132175
return {
133176
...testerConfig,
@@ -137,7 +180,7 @@ function getVerifyConfig(ruleId: string, testerConfig, testCase) {
137180
...parserOptions,
138181
},
139182
rules: {
140-
[ruleId]: ['error', ...(Array.isArray(options) ? options : [])],
183+
[ruleId]: Array.isArray(options) ? ['error', ...options] : 'error',
141184
},
142185
};
143186
}
@@ -159,7 +202,11 @@ function defineParser(linter: Linter, parser: string): void {
159202
}
160203
}
161204

162-
function visualizeEslintMessage(text: string, result: Linter.LintMessage): string {
205+
function visualizeEslintMessage(
206+
text: string,
207+
result: Linter.LintMessage,
208+
linesOffset = Number.POSITIVE_INFINITY
209+
): string {
163210
const { line, column, endLine, endColumn, message } = result;
164211
const location: Partial<AST.SourceLocation> = {
165212
start: {
@@ -176,8 +223,8 @@ function visualizeEslintMessage(text: string, result: Linter.LintMessage): strin
176223
}
177224

178225
return codeFrameColumns(text, location as AST.SourceLocation, {
179-
linesAbove: Number.POSITIVE_INFINITY,
180-
linesBelow: Number.POSITIVE_INFINITY,
226+
linesAbove: linesOffset,
227+
linesBelow: linesOffset,
181228
message,
182229
});
183230
}

0 commit comments

Comments
 (0)