pve-eslint/eslint/tests/lib/rule-tester/flat-rule-tester.js
Dominik Csapak f2a92ac62f import 8.41.0 source
Signed-off-by: Dominik Csapak <d.csapak@proxmox.com>
2023-05-24 18:51:55 +02:00

2628 lines
100 KiB
JavaScript

/**
* @fileoverview Tests for ESLint Tester
* @author Nicholas C. Zakas
*/
"use strict";
//------------------------------------------------------------------------------
// Requirements
//------------------------------------------------------------------------------
const sinon = require("sinon"),
EventEmitter = require("events"),
FlatRuleTester = require("../../../lib/rule-tester/flat-rule-tester"),
assert = require("chai").assert,
nodeAssert = require("assert");
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => {
try {
nodeAssert.strictEqual(1, 2);
} catch (err) {
return err.operator;
}
throw new Error("unexpected successful assertion");
})();
/**
* A helper function to verify Node.js core error messages.
* @param {string} actual The actual input
* @param {string} expected The expected input
* @returns {Function} Error callback to verify that the message is correct
* for the actual and expected input.
*/
function assertErrorMatches(actual, expected) {
const err = new nodeAssert.AssertionError({
actual,
expected,
operator: NODE_ASSERT_STRICT_EQUAL_OPERATOR
});
return err.message;
}
/**
* Do nothing.
* @returns {void}
*/
function noop() {
// do nothing.
}
//------------------------------------------------------------------------------
// Rewire Things
//------------------------------------------------------------------------------
/*
* So here's the situation. Because RuleTester uses it() and describe() from
* Mocha, any failures would show up in the output of this test file. That means
* when we tested that a failure is thrown, that would also count as a failure
* in the testing for RuleTester. In order to remove those results from the
* results of this file, we need to overwrite it() and describe() just in
* RuleTester to do nothing but run code. Effectively, it() and describe()
* just become regular functions inside of index.js, not at all related to Mocha.
* That allows the results of this file to be untainted and therefore accurate.
*
* To assert that the right arguments are passed to RuleTester.describe/it, an
* event emitter is used which emits the arguments.
*/
const ruleTesterTestEmitter = new EventEmitter();
//------------------------------------------------------------------------------
// Tests
//------------------------------------------------------------------------------
describe("FlatRuleTester", () => {
let ruleTester;
// Stub `describe()` and `it()` while this test suite.
before(() => {
FlatRuleTester.describe = function(text, method) {
ruleTesterTestEmitter.emit("describe", text, method);
return method.call(this);
};
FlatRuleTester.it = function(text, method) {
ruleTesterTestEmitter.emit("it", text, method);
return method.call(this);
};
});
after(() => {
FlatRuleTester.describe = null;
FlatRuleTester.it = null;
});
beforeEach(() => {
ruleTester = new FlatRuleTester();
});
describe("Default Config", () => {
afterEach(() => {
FlatRuleTester.resetDefaultConfig();
});
it("should correctly set the globals configuration", () => {
const config = { languageOptions: { globals: { test: true } } };
FlatRuleTester.setDefaultConfig(config);
assert(
FlatRuleTester.getDefaultConfig().languageOptions.globals.test,
"The default config object is incorrect"
);
});
it("should correctly reset the global configuration", () => {
const config = { languageOptions: { globals: { test: true } } };
FlatRuleTester.setDefaultConfig(config);
FlatRuleTester.resetDefaultConfig();
assert.deepStrictEqual(
FlatRuleTester.getDefaultConfig(),
{ rules: {} },
"The default configuration has not reset correctly"
);
});
it("should enforce the global configuration to be an object", () => {
/**
* Set the default config for the rules tester
* @param {Object} config configuration object
* @returns {Function} Function to be executed
* @private
*/
function setConfig(config) {
return function() {
FlatRuleTester.setDefaultConfig(config);
};
}
const errorMessage = "FlatRuleTester.setDefaultConfig: config must be an object";
assert.throw(setConfig(), errorMessage);
assert.throw(setConfig(1), errorMessage);
assert.throw(setConfig(3.14), errorMessage);
assert.throw(setConfig("foo"), errorMessage);
assert.throw(setConfig(null), errorMessage);
assert.throw(setConfig(true), errorMessage);
});
it("should pass-through the globals config to the tester then to the to rule", () => {
const config = { languageOptions: { sourceType: "script", globals: { test: true } } };
FlatRuleTester.setDefaultConfig(config);
ruleTester = new FlatRuleTester();
ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
valid: [
"var test = 'foo'",
"var test2 = test"
],
invalid: [{ code: "bar", errors: 1, languageOptions: { globals: { foo: true } } }]
});
});
it("should throw an error if node.start is accessed with parser in default config", () => {
const enhancedParser = require("../../fixtures/parsers/enhanced-parser");
FlatRuleTester.setDefaultConfig({
languageOptions: {
parser: enhancedParser
}
});
ruleTester = new FlatRuleTester();
/*
* Note: More robust test for start/end found later in file.
* This one is just for checking the default config has a
* parser that is wrapped.
*/
const usesStartEndRule = {
create() {
return {
CallExpression(node) {
noop(node.arguments[1].start);
}
};
}
};
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: ["foo(a, b)"],
invalid: []
});
}, "Use node.range[0] instead of node.start");
});
});
describe("only", () => {
describe("`itOnly` accessor", () => {
describe("when `itOnly` is set", () => {
before(() => {
FlatRuleTester.itOnly = sinon.spy();
});
after(() => {
FlatRuleTester.itOnly = void 0;
});
beforeEach(() => {
FlatRuleTester.itOnly.resetHistory();
ruleTester = new FlatRuleTester();
});
it("is called by exclusive tests", () => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [{
code: "const notVar = 42;",
only: true
}],
invalid: []
});
sinon.assert.calledWith(FlatRuleTester.itOnly, "const notVar = 42;");
});
});
describe("when `it` is set and has an `only()` method", () => {
before(() => {
FlatRuleTester.it.only = () => {};
sinon.spy(FlatRuleTester.it, "only");
});
after(() => {
FlatRuleTester.it.only = void 0;
});
beforeEach(() => {
FlatRuleTester.it.only.resetHistory();
ruleTester = new FlatRuleTester();
});
it("is called by tests with `only` set", () => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [{
code: "const notVar = 42;",
only: true
}],
invalid: []
});
sinon.assert.calledWith(FlatRuleTester.it.only, "const notVar = 42;");
});
});
describe("when global `it` is a function that has an `only()` method", () => {
let originalGlobalItOnly;
before(() => {
/*
* We run tests with `--forbid-only`, so we have to override
* `it.only` to prevent the real one from being called.
*/
originalGlobalItOnly = it.only;
it.only = () => {};
sinon.spy(it, "only");
});
after(() => {
it.only = originalGlobalItOnly;
});
beforeEach(() => {
it.only.resetHistory();
ruleTester = new FlatRuleTester();
});
it("is called by tests with `only` set", () => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [{
code: "const notVar = 42;",
only: true
}],
invalid: []
});
sinon.assert.calledWith(it.only, "const notVar = 42;");
});
});
describe("when `describe` and `it` are overridden without `itOnly`", () => {
let originalGlobalItOnly;
before(() => {
/*
* These tests override `describe` and `it` already, so we
* don't need to override them here. We do, however, need to
* remove `only` from the global `it` to prevent it from
* being used instead.
*/
originalGlobalItOnly = it.only;
it.only = void 0;
});
after(() => {
it.only = originalGlobalItOnly;
});
beforeEach(() => {
ruleTester = new FlatRuleTester();
});
it("throws an error recommending overriding `itOnly`", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [{
code: "const notVar = 42;",
only: true
}],
invalid: []
});
}, "Set `RuleTester.itOnly` to use `only` with a custom test framework.");
});
});
describe("when global `it` is a function that does not have an `only()` method", () => {
let originalGlobalIt;
let originalRuleTesterDescribe;
let originalRuleTesterIt;
before(() => {
originalGlobalIt = global.it;
// eslint-disable-next-line no-global-assign -- Temporarily override Mocha global
it = () => {};
/*
* These tests override `describe` and `it`, so we need to
* un-override them here so they won't interfere.
*/
originalRuleTesterDescribe = FlatRuleTester.describe;
FlatRuleTester.describe = void 0;
originalRuleTesterIt = FlatRuleTester.it;
FlatRuleTester.it = void 0;
});
after(() => {
// eslint-disable-next-line no-global-assign -- Restore Mocha global
it = originalGlobalIt;
FlatRuleTester.describe = originalRuleTesterDescribe;
FlatRuleTester.it = originalRuleTesterIt;
});
beforeEach(() => {
ruleTester = new FlatRuleTester();
});
it("throws an error explaining that the current test framework does not support `only`", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [{
code: "const notVar = 42;",
only: true
}],
invalid: []
});
}, "The current test framework does not support exclusive tests with `only`.");
});
});
});
describe("test cases", () => {
const ruleName = "no-var";
const rule = require("../../fixtures/testers/rule-tester/no-var");
let originalRuleTesterIt;
let spyRuleTesterIt;
let originalRuleTesterItOnly;
let spyRuleTesterItOnly;
before(() => {
originalRuleTesterIt = FlatRuleTester.it;
spyRuleTesterIt = sinon.spy();
FlatRuleTester.it = spyRuleTesterIt;
originalRuleTesterItOnly = FlatRuleTester.itOnly;
spyRuleTesterItOnly = sinon.spy();
FlatRuleTester.itOnly = spyRuleTesterItOnly;
});
after(() => {
FlatRuleTester.it = originalRuleTesterIt;
FlatRuleTester.itOnly = originalRuleTesterItOnly;
});
beforeEach(() => {
spyRuleTesterIt.resetHistory();
spyRuleTesterItOnly.resetHistory();
ruleTester = new FlatRuleTester();
});
it("isn't called for normal tests", () => {
ruleTester.run(ruleName, rule, {
valid: ["const notVar = 42;"],
invalid: []
});
sinon.assert.calledWith(spyRuleTesterIt, "const notVar = 42;");
sinon.assert.notCalled(spyRuleTesterItOnly);
});
it("calls it or itOnly for every test case", () => {
/*
* `RuleTester` doesn't implement test case exclusivity itself.
* Setting `only: true` just causes `RuleTester` to call
* whatever `only()` function is provided by the test framework
* instead of the regular `it()` function.
*/
ruleTester.run(ruleName, rule, {
valid: [
"const valid = 42;",
{
code: "const onlyValid = 42;",
only: true
}
],
invalid: [
{
code: "var invalid = 42;",
errors: [/^Bad var/u]
},
{
code: "var onlyInvalid = 42;",
errors: [/^Bad var/u],
only: true
}
]
});
sinon.assert.calledWith(spyRuleTesterIt, "const valid = 42;");
sinon.assert.calledWith(spyRuleTesterItOnly, "const onlyValid = 42;");
sinon.assert.calledWith(spyRuleTesterIt, "var invalid = 42;");
sinon.assert.calledWith(spyRuleTesterItOnly, "var onlyInvalid = 42;");
});
});
describe("static helper wrapper", () => {
it("adds `only` to string test cases", () => {
const test = FlatRuleTester.only("const valid = 42;");
assert.deepStrictEqual(test, {
code: "const valid = 42;",
only: true
});
});
it("adds `only` to object test cases", () => {
const test = FlatRuleTester.only({ code: "const valid = 42;" });
assert.deepStrictEqual(test, {
code: "const valid = 42;",
only: true
});
});
});
});
it("should not throw an error when everything passes", () => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"Eval(foo)"
],
invalid: [
{ code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
]
});
});
it("should throw correct error when valid code is invalid and enables other core rule", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"/*eslint semi: 2*/ eval(foo);"
],
invalid: [
{ code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
]
});
}, /Should have no errors but had 1/u);
});
it("should throw an error when valid code is invalid", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"eval(foo)"
],
invalid: [
{ code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
]
});
}, /Should have no errors but had 1/u);
});
it("should throw an error when valid code is invalid", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
{ code: "eval(foo)" }
],
invalid: [
{ code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
]
});
}, /Should have no errors but had 1/u);
});
it("should throw an error if invalid code is valid", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"Eval(foo)"
],
invalid: [
{ code: "Eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
]
});
}, /Should have 1 error but had 0/u);
});
it("should throw an error when the error message is wrong", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
// Only the invalid test matters here
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", errors: [{ message: "Bad error message." }] }
]
});
}, assertErrorMatches("Bad var.", "Bad error message."));
});
it("should throw an error when the error message regex does not match", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: [
{ code: "var foo = bar;", errors: [{ message: /Bad error message/u }] }
]
});
}, /Expected 'Bad var.' to match \/Bad error message\//u);
});
it("should throw an error when the error is not a supported type", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
// Only the invalid test matters here
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", errors: [42] }
]
});
}, /Error should be a string, object, or RegExp/u);
});
it("should throw an error when any of the errors is not a supported type", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
// Only the invalid test matters here
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar; var baz = quux", errors: [{ type: "VariableDeclaration" }, null] }
]
});
}, /Error should be a string, object, or RegExp/u);
});
it("should throw an error when the error is a string and it does not match error message", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
// Only the invalid test matters here
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", errors: ["Bad error message."] }
]
});
}, assertErrorMatches("Bad var.", "Bad error message."));
});
it("should throw an error when the error is a string and it does not match error message", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
],
invalid: [
{ code: "var foo = bar;", errors: [/Bad error message/u] }
]
});
}, /Expected 'Bad var.' to match \/Bad error message\//u);
});
it("should not throw an error when the error is a string and it matches error message", () => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
// Only the invalid test matters here
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", output: " foo = bar;", errors: ["Bad var."] }
]
});
});
it("should not throw an error when the error is a regex and it matches error message", () => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: [
{ code: "var foo = bar;", output: " foo = bar;", errors: [/^Bad var/u] }
]
});
});
it("should throw an error when the error is an object with an unknown property name", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", errors: [{ Message: "Bad var." }] }
]
});
}, /Invalid error property name 'Message'/u);
});
it("should throw an error when any of the errors is an object with an unknown property name", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"bar = baz;"
],
invalid: [
{
code: "var foo = bar; var baz = quux",
errors: [
{ message: "Bad var.", type: "VariableDeclaration" },
{ message: "Bad var.", typo: "VariableDeclaration" }
]
}
]
});
}, /Invalid error property name 'typo'/u);
});
it("should not throw an error when the error is a regex in an object and it matches error message", () => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: [
{ code: "var foo = bar;", output: " foo = bar;", errors: [{ message: /^Bad var/u }] }
]
});
});
it("should throw an error when the expected output doesn't match", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration" }] }
]
});
}, /Output is incorrect/u);
});
it("should use strict equality to compare output", () => {
const replaceProgramWith5Rule = {
meta: {
fixable: "code"
},
create: context => ({
Program(node) {
context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
}
})
};
// Should not throw.
ruleTester.run("foo", replaceProgramWith5Rule, {
valid: [],
invalid: [
{ code: "var foo = bar;", output: "5", errors: 1 }
]
});
assert.throws(() => {
ruleTester.run("foo", replaceProgramWith5Rule, {
valid: [],
invalid: [
{ code: "var foo = bar;", output: 5, errors: 1 }
]
});
}, /Output is incorrect/u);
});
it("should throw an error when the expected output doesn't match and errors is just a number", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", output: "foo = bar", errors: 1 }
]
});
}, /Output is incorrect/u);
});
it("should not throw an error when the expected output is null and no errors produce output", () => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"bar = baz;"
],
invalid: [
{ code: "eval(x)", errors: 1, output: null },
{ code: "eval(x); eval(y);", errors: 2, output: null }
]
});
});
it("should throw an error when the expected output is null and problems produce output", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", output: null, errors: 1 }
]
});
}, /Expected no autofixes to be suggested/u);
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"bar = baz;"
],
invalid: [
{
code: "var foo = bar; var qux = boop;",
output: null,
errors: 2
}
]
});
}, /Expected no autofixes to be suggested/u);
});
it("should throw an error when the expected output is null and only some problems produce output", () => {
assert.throws(() => {
ruleTester.run("fixes-one-problem", require("../../fixtures/testers/rule-tester/fixes-one-problem"), {
valid: [],
invalid: [
{ code: "foo", output: null, errors: 2 }
]
});
}, /Expected no autofixes to be suggested/u);
});
it("should throw an error when the expected output isn't specified and problems produce output", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", errors: 1 }
]
});
}, "The rule fixed the code. Please add 'output' property.");
});
it("should throw an error if invalid code specifies wrong type", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"Eval(foo)"
],
invalid: [
{ code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression2" }] }
]
});
}, /Error type should be CallExpression2, found CallExpression/u);
});
it("should throw an error if invalid code specifies wrong line", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"Eval(foo)"
],
invalid: [
{ code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 5 }] }
]
});
}, /Error line should be 5/u);
});
it("should not skip line assertion if line is a falsy value", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"Eval(foo)"
],
invalid: [
{ code: "\neval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 0 }] }
]
});
}, /Error line should be 0/u);
});
it("should throw an error if invalid code specifies wrong column", () => {
const wrongColumn = 10,
expectedErrorMessage = "Error column should be 1";
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: ["Eval(foo)"],
invalid: [{
code: "eval(foo)",
errors: [{
message: "eval sucks.",
column: wrongColumn
}]
}]
});
}, expectedErrorMessage);
});
it("should throw error for empty error array", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: []
}]
});
}, /Invalid cases must have at least one error/u);
});
it("should throw error for errors : 0", () => {
assert.throws(() => {
ruleTester.run(
"suggestions-messageIds",
require("../../fixtures/testers/rule-tester/suggestions")
.withMessageIds,
{
valid: [],
invalid: [
{
code: "var foo;",
errors: 0
}
]
}
);
}, /Invalid cases must have 'error' value greater than 0/u);
});
it("should not skip column assertion if column is a falsy value", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: ["Eval(foo)"],
invalid: [{
code: "var foo; eval(foo)",
errors: [{ message: "eval sucks.", column: 0 }]
}]
});
}, /Error column should be 0/u);
});
it("should throw an error if invalid code specifies wrong endLine", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endLine: 10 }] }
]
});
}, "Error endLine should be 10");
});
it("should throw an error if invalid code specifies wrong endColumn", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"bar = baz;"
],
invalid: [
{ code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endColumn: 10 }] }
]
});
}, "Error endColumn should be 10");
});
it("should throw an error if invalid code has the wrong number of errors", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"Eval(foo)"
],
invalid: [
{
code: "eval(foo)",
errors: [
{ message: "eval sucks.", type: "CallExpression" },
{ message: "eval sucks.", type: "CallExpression" }
]
}
]
});
}, /Should have 2 errors but had 1/u);
});
it("should throw an error if invalid code does not have errors", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"Eval(foo)"
],
invalid: [
{ code: "eval(foo)" }
]
});
}, /Did not specify errors for an invalid test of no-eval/u);
});
it("should throw an error if invalid code has the wrong explicit number of errors", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"Eval(foo)"
],
invalid: [
{ code: "eval(foo)", errors: 2 }
]
});
}, /Should have 2 errors but had 1/u);
});
it("should throw an error if there's a parsing error in a valid test", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"1eval('foo')"
],
invalid: [
{ code: "eval('foo')", errors: [{}] }
]
});
}, /fatal parsing error/iu);
});
it("should throw an error if there's a parsing error in an invalid test", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"noeval('foo')"
],
invalid: [
{ code: "1eval('foo')", errors: [{}] }
]
});
}, /fatal parsing error/iu);
});
it("should throw an error if there's a parsing error in an invalid test and errors is just a number", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
"noeval('foo')"
],
invalid: [
{ code: "1eval('foo')", errors: 1 }
]
});
}, /fatal parsing error/iu);
});
// https://github.com/eslint/eslint/issues/4779
it("should throw an error if there's a parsing error and output doesn't match", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [],
invalid: [
{ code: "eval(`foo`", output: "eval(`foo`);", errors: [{}] }
]
});
}, /fatal parsing error/iu);
});
it("should not throw an error if invalid code has at least an expected empty error object", () => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: ["Eval(foo)"],
invalid: [{
code: "eval(foo)",
errors: [{}]
}]
});
});
it("should pass-through the globals config of valid tests to the to rule", () => {
ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
valid: [
{
code: "var test = 'foo'",
languageOptions: {
sourceType: "script"
}
},
{
code: "var test2 = 'bar'",
languageOptions: {
globals: { test: true }
}
}
],
invalid: [{ code: "bar", errors: 1 }]
});
});
it("should pass-through the globals config of invalid tests to the rule", () => {
ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
valid: [
{
code: "var test = 'foo'",
languageOptions: {
sourceType: "script"
}
}
],
invalid: [
{
code: "var test = 'foo'; var foo = 'bar'",
languageOptions: {
sourceType: "script"
},
errors: 1
},
{
code: "var test = 'foo'",
languageOptions: {
sourceType: "script",
globals: { foo: true }
},
errors: [{ message: "Global variable foo should not be used." }]
}
]
});
});
it("should pass-through the settings config to rules", () => {
ruleTester.run("no-test-settings", require("../../fixtures/testers/rule-tester/no-test-settings"), {
valid: [
{
code: "var test = 'bar'", settings: { test: 1 }
}
],
invalid: [
{
code: "var test = 'bar'", settings: { "no-test": 22 }, errors: 1
}
]
});
});
it("should pass-through the filename to the rule", () => {
(function() {
ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), {
valid: [
{
code: "var foo = 'bar'",
filename: "somefile.js"
}
],
invalid: [
{
code: "var foo = 'bar'",
errors: [
{ message: "Filename test was not defined." }
]
}
]
});
}());
});
it("should pass-through the options to the rule", () => {
ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
valid: [
{
code: "var foo = 'bar'",
options: [false]
}
],
invalid: [
{
code: "var foo = 'bar'",
options: [true],
errors: [{ message: "Invalid args" }]
}
]
});
});
it("should throw an error if the options are an object", () => {
assert.throws(() => {
ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
valid: [
{
code: "foo",
options: { ok: true }
}
],
invalid: []
});
}, /options must be an array/u);
});
it("should throw an error if the options are a number", () => {
assert.throws(() => {
ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
valid: [
{
code: "foo",
options: 0
}
],
invalid: []
});
}, /options must be an array/u);
});
describe("Parsers", () => {
it("should pass-through the parser to the rule", () => {
const spy = sinon.spy(ruleTester.linter, "verify");
const esprima = require("esprima");
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
{
code: "Eval(foo)"
}
],
invalid: [
{
code: "eval(foo)",
languageOptions: {
parser: esprima
},
errors: [{ line: 1 }]
}
]
});
const configs = spy.args[1][1];
const config = configs.getConfig("test.js");
assert.strictEqual(
config.languageOptions.parser[Symbol.for("eslint.RuleTester.parser")],
esprima
);
});
it("should pass-through services from parseForESLint to the rule", () => {
const enhancedParser = require("../../fixtures/parsers/enhanced-parser");
const disallowHiRule = {
create: context => ({
Literal(node) {
const disallowed = context.parserServices.test.getMessage(); // returns "Hi!"
if (node.value === disallowed) {
context.report({ node, message: `Don't use '${disallowed}'` });
}
}
})
};
ruleTester.run("no-hi", disallowHiRule, {
valid: [
{
code: "'Hello!'",
languageOptions: {
parser: enhancedParser
}
}
],
invalid: [
{
code: "'Hi!'",
languageOptions: {
parser: enhancedParser
},
errors: [{ message: "Don't use 'Hi!'" }]
}
]
});
});
it("should throw an error when the parser is not an object", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [],
invalid: [{
code: "var foo;",
languageOptions: {
parser: "esprima"
},
errors: 1
}]
});
}, /Parser must be an object with a parse\(\) or parseForESLint\(\) method/u);
});
});
it("should prevent invalid options schemas", () => {
assert.throws(() => {
ruleTester.run("no-invalid-schema", require("../../fixtures/testers/rule-tester/no-invalid-schema"), {
valid: [
"var answer = 6 * 7;",
{ code: "var answer = 6 * 7;", options: [] }
],
invalid: [
{ code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected nothing." }] }
]
});
}, "Schema for rule no-invalid-schema is invalid:,\titems: should be object\n\titems[0].enum: should NOT have fewer than 1 items\n\titems: should match some schema in anyOf");
});
it("should prevent schema violations in options", () => {
assert.throws(() => {
ruleTester.run("no-schema-violation", require("../../fixtures/testers/rule-tester/no-schema-violation"), {
valid: [
"var answer = 6 * 7;",
{ code: "var answer = 6 * 7;", options: ["foo"] }
],
invalid: [
{ code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected foo." }] }
]
});
}, /Value "bar" should be equal to one of the allowed values./u);
});
it("should disallow invalid defaults in rules", () => {
const ruleWithInvalidDefaults = {
meta: {
schema: [
{
oneOf: [
{ enum: ["foo"] },
{
type: "object",
properties: {
foo: {
enum: ["foo", "bar"],
default: "foo"
}
},
additionalProperties: false
}
]
}
]
},
create: () => ({})
};
assert.throws(() => {
ruleTester.run("invalid-defaults", ruleWithInvalidDefaults, {
valid: [
{
code: "foo",
options: [{}]
}
],
invalid: []
});
}, /Schema for rule invalid-defaults is invalid: default is ignored for: data1\.foo/u);
});
it("throw an error when an unknown config option is included", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
{ code: "Eval(foo)", foo: "bar" }
],
invalid: []
});
}, /ESLint configuration in rule-tester is invalid./u);
});
it("throw an error when env is included in config", () => {
assert.throws(() => {
ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [
{ code: "Eval(foo)", env: ["es6"] }
],
invalid: []
});
}, /Unexpected key "env" found./u);
});
it("should pass-through the tester config to the rule", () => {
ruleTester = new FlatRuleTester({
languageOptions: {
globals: { test: true }
}
});
ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
valid: [
"var test = 'foo'",
"var test2 = test"
],
invalid: [{ code: "bar", errors: 1, languageOptions: { globals: { foo: true } } }]
});
});
it("should throw an error if AST was modified", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), {
valid: [
"var foo = 0;"
],
invalid: []
});
}, "Rule should not modify AST.");
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), {
valid: [],
invalid: [
{ code: "var bar = 0;", errors: ["error"] }
]
});
}, "Rule should not modify AST.");
});
it("should throw an error node.start is accessed with custom parser", () => {
const enhancedParser = require("../../fixtures/parsers/enhanced-parser");
ruleTester = new FlatRuleTester({
languageOptions: {
parser: enhancedParser
}
});
/*
* Note: More robust test for start/end found later in file.
* This one is just for checking the custom config has a
* parser that is wrapped.
*/
const usesStartEndRule = {
create() {
return {
CallExpression(node) {
noop(node.arguments[1].start);
}
};
}
};
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: ["foo(a, b)"],
invalid: []
});
}, "Use node.range[0] instead of node.start");
});
it("should throw an error if AST was modified (at Program)", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), {
valid: [
"var foo = 0;"
],
invalid: []
});
}, "Rule should not modify AST.");
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), {
valid: [],
invalid: [
{ code: "var bar = 0;", errors: ["error"] }
]
});
}, "Rule should not modify AST.");
});
it("should throw an error if AST was modified (at Program:exit)", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
valid: [
"var foo = 0;"
],
invalid: []
});
}, "Rule should not modify AST.");
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
valid: [],
invalid: [
{ code: "var bar = 0;", errors: ["error"] }
]
});
}, "Rule should not modify AST.");
});
it("should throw an error if rule uses start and end properties on nodes, tokens or comments", () => {
const usesStartEndRule = {
create(context) {
const sourceCode = context.sourceCode;
return {
CallExpression(node) {
noop(node.arguments[1].start);
},
"BinaryExpression[operator='+']"(node) {
noop(node.end);
},
"UnaryExpression[operator='-']"(node) {
noop(sourceCode.getFirstToken(node).start);
},
ConditionalExpression(node) {
noop(sourceCode.getFirstToken(node).end);
},
BlockStatement(node) {
noop(sourceCode.getCommentsInside(node)[0].start);
},
ObjectExpression(node) {
noop(sourceCode.getCommentsInside(node)[0].end);
},
Decorator(node) {
noop(node.start);
}
};
}
};
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: ["foo(a, b)"],
invalid: []
});
}, "Use node.range[0] instead of node.start");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: [],
invalid: [{ code: "var a = b * (c + d) / e;", errors: 1 }]
});
}, "Use node.range[1] instead of node.end");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: [],
invalid: [{ code: "var a = -b * c;", errors: 1 }]
});
}, "Use token.range[0] instead of token.start");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: ["var a = b ? c : d;"],
invalid: []
});
}, "Use token.range[1] instead of token.end");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: ["function f() { /* comment */ }"],
invalid: []
});
}, "Use token.range[0] instead of token.start");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: [],
invalid: [{ code: "var x = //\n {\n //comment\n //\n}", errors: 1 }]
});
}, "Use token.range[1] instead of token.end");
const enhancedParser = require("../../fixtures/parsers/enhanced-parser");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: [{ code: "foo(a, b)", languageOptions: { parser: enhancedParser } }],
invalid: []
});
}, "Use node.range[0] instead of node.start");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: [],
invalid: [{ code: "var a = b * (c + d) / e;", languageOptions: { parser: enhancedParser }, errors: 1 }]
});
}, "Use node.range[1] instead of node.end");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: [],
invalid: [{ code: "var a = -b * c;", languageOptions: { parser: enhancedParser }, errors: 1 }]
});
}, "Use token.range[0] instead of token.start");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: [{ code: "var a = b ? c : d;", languageOptions: { parser: enhancedParser } }],
invalid: []
});
}, "Use token.range[1] instead of token.end");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: [{ code: "function f() { /* comment */ }", languageOptions: { parser: enhancedParser } }],
invalid: []
});
}, "Use token.range[0] instead of token.start");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: [],
invalid: [{ code: "var x = //\n {\n //comment\n //\n}", languageOptions: { parser: enhancedParser }, errors: 1 }]
});
}, "Use token.range[1] instead of token.end");
assert.throws(() => {
ruleTester.run("uses-start-end", usesStartEndRule, {
valid: [{ code: "@foo class A {}", languageOptions: { parser: require("../../fixtures/parsers/enhanced-parser2") } }],
invalid: []
});
}, "Use node.range[0] instead of node.start");
});
it("should throw an error if no test scenarios given", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"));
}, "Test Scenarios for rule foo : Could not find test scenario object");
});
it("should throw an error if no acceptable test scenario object is given", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), []);
}, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios");
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), "");
}, "Test Scenarios for rule foo : Could not find test scenario object");
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), 2);
}, "Test Scenarios for rule foo : Could not find test scenario object");
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {});
}, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios");
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
valid: []
});
}, "Test Scenarios for rule foo is invalid:\nCould not find any invalid test scenarios");
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
invalid: []
});
}, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios");
});
// Nominal message/messageId use cases
it("should assert match if message provided in both test and result.", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
valid: [],
invalid: [{ code: "foo", errors: [{ message: "something" }] }]
});
}, /Avoid using variables named/u);
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
valid: [],
invalid: [{ code: "foo", errors: [{ message: "Avoid using variables named 'foo'." }] }]
});
});
it("should assert match between messageId if provided in both test and result.", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "unused" }] }]
});
}, "messageId 'avoidFoo' does not match expected messageId 'unused'.");
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
});
});
it("should assert match between resulting message output if messageId and data provided in both test and result", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo", data: { name: "notFoo" } }] }]
});
}, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\"");
});
// messageId/message misconfiguration cases
it("should throw if user tests for both message and messageId", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
valid: [],
invalid: [{ code: "foo", errors: [{ message: "something", messageId: "avoidFoo" }] }]
});
}, "Error should not specify both 'message' and a 'messageId'.");
});
it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
});
}, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'");
});
it("should throw if user tests for messageId not listed in the rule's meta syntax.", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
valid: [],
invalid: [{ code: "foo", errors: [{ messageId: "useFoo" }] }]
});
}, /Invalid messageId 'useFoo'/u);
});
it("should throw if data provided without messageId.", () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
valid: [],
invalid: [{ code: "foo", errors: [{ data: "something" }] }]
});
}, "Error must specify 'messageId' if 'data' is used.");
});
// fixable rules with or without `meta` property
it("should not throw an error if a rule that has `meta.fixable` produces fixes", () => {
const replaceProgramWith5Rule = {
meta: {
fixable: "code"
},
create(context) {
return {
Program(node) {
context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
}
};
}
};
ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
valid: [],
invalid: [
{ code: "var foo = bar;", output: "5", errors: 1 }
]
});
});
it("should throw an error if a new-format rule that doesn't have `meta` produces fixes", () => {
const replaceProgramWith5Rule = {
create(context) {
return {
Program(node) {
context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
}
};
}
};
assert.throws(() => {
ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
valid: [],
invalid: [
{ code: "var foo = bar;", output: "5", errors: 1 }
]
});
}, /Fixable rules must set the `meta\.fixable` property/u);
});
it("should throw an error if a legacy-format rule produces fixes", () => {
/**
* Legacy-format rule (a function instead of an object with `create` method).
* @param {RuleContext} context The ESLint rule context object.
* @returns {Object} Listeners.
*/
function replaceProgramWith5Rule(context) {
return {
Program(node) {
context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
}
};
}
assert.throws(() => {
ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
valid: [],
invalid: [
{ code: "var foo = bar;", output: "5", errors: 1 }
]
});
}, /Fixable rules must set the `meta\.fixable` property/u);
});
describe("suggestions", () => {
it("should pass with valid suggestions (tested using desc)", () => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
valid: [
"var boo;"
],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
desc: "Rename identifier 'foo' to 'bar'",
output: "var bar;"
}]
}]
}]
});
});
it("should pass with suggestions on multiple lines", () => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
valid: [],
invalid: [
{
code: "function foo() {\n var foo = 1;\n}",
errors: [{
suggestions: [{
desc: "Rename identifier 'foo' to 'bar'",
output: "function bar() {\n var foo = 1;\n}"
}]
}, {
suggestions: [{
desc: "Rename identifier 'foo' to 'bar'",
output: "function foo() {\n var bar = 1;\n}"
}]
}]
}
]
});
});
it("should pass with valid suggestions (tested using messageIds)", () => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "renameFoo",
output: "var bar;"
}, {
messageId: "renameFoo",
output: "var baz;"
}]
}]
}]
});
});
it("should pass with valid suggestions (one tested using messageIds, the other using desc)", () => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "renameFoo",
output: "var bar;"
}, {
desc: "Rename identifier 'foo' to 'baz'",
output: "var baz;"
}]
}]
}]
});
});
it("should pass with valid suggestions (tested using both desc and messageIds for the same suggestion)", () => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
desc: "Rename identifier 'foo' to 'bar'",
messageId: "renameFoo",
output: "var bar;"
}, {
desc: "Rename identifier 'foo' to 'baz'",
messageId: "renameFoo",
output: "var baz;"
}]
}]
}]
});
});
it("should pass with valid suggestions (tested using only desc on a rule that utilizes meta.messages)", () => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
desc: "Rename identifier 'foo' to 'bar'",
output: "var bar;"
}, {
desc: "Rename identifier 'foo' to 'baz'",
output: "var baz;"
}]
}]
}]
});
});
it("should pass with valid suggestions (tested using messageIds and data)", () => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "renameFoo",
data: { newName: "bar" },
output: "var bar;"
}, {
messageId: "renameFoo",
data: { newName: "baz" },
output: "var baz;"
}]
}]
}]
});
});
it("should pass when tested using empty suggestion test objects if the array length is correct", () => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{}, {}]
}]
}]
});
});
it("should support explicitly expecting no suggestions", () => {
[void 0, null, false, []].forEach(suggestions => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/no-eval"), {
valid: [],
invalid: [{
code: "eval('var foo');",
errors: [{
suggestions
}]
}]
});
});
});
it("should fail when expecting no suggestions and there are suggestions", () => {
[void 0, null, false, []].forEach(suggestions => {
assert.throws(() => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions
}]
}]
});
}, "Error should have no suggestions on error with message: \"Avoid using identifiers named 'foo'.\"");
});
});
it("should fail when testing for suggestions that don't exist", () => {
assert.throws(() => {
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "this-does-not-exist"
}]
}]
}]
});
}, "Error should have an array of suggestions. Instead received \"undefined\" on error with message: \"Bad var.\"");
});
it("should fail when there are a different number of suggestions", () => {
assert.throws(() => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
desc: "Rename identifier 'foo' to 'bar'",
output: "var bar;"
}, {
desc: "Rename identifier 'foo' to 'baz'",
output: "var baz;"
}]
}]
}]
});
}, "Error should have 2 suggestions. Instead found 1 suggestions");
});
it("should throw if the suggestion description doesn't match", () => {
assert.throws(() => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
desc: "not right",
output: "var baz;"
}]
}]
}]
});
}, "Error Suggestion at index 0 : desc should be \"not right\" but got \"Rename identifier 'foo' to 'bar'\" instead.");
});
it("should throw if the suggestion description doesn't match (although messageIds match)", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
desc: "Rename identifier 'foo' to 'bar'",
messageId: "renameFoo",
output: "var bar;"
}, {
desc: "Rename id 'foo' to 'baz'",
messageId: "renameFoo",
output: "var baz;"
}]
}]
}]
});
}, "Error Suggestion at index 1 : desc should be \"Rename id 'foo' to 'baz'\" but got \"Rename identifier 'foo' to 'baz'\" instead.");
});
it("should throw if the suggestion messageId doesn't match", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "unused",
output: "var bar;"
}, {
messageId: "renameFoo",
output: "var baz;"
}]
}]
}]
});
}, "Error Suggestion at index 0 : messageId should be 'unused' but got 'renameFoo' instead.");
});
it("should throw if the suggestion messageId doesn't match (although descriptions match)", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
desc: "Rename identifier 'foo' to 'bar'",
messageId: "renameFoo",
output: "var bar;"
}, {
desc: "Rename identifier 'foo' to 'baz'",
messageId: "avoidFoo",
output: "var baz;"
}]
}]
}]
});
}, "Error Suggestion at index 1 : messageId should be 'avoidFoo' but got 'renameFoo' instead.");
});
it("should throw if test specifies messageId for a rule that doesn't have meta.messages", () => {
assert.throws(() => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "renameFoo",
output: "var bar;"
}]
}]
}]
});
}, "Error Suggestion at index 0 : Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.");
});
it("should throw if test specifies messageId that doesn't exist in the rule's meta.messages", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "renameFoo",
output: "var bar;"
}, {
messageId: "removeFoo",
output: "var baz;"
}]
}]
}]
});
}, "Error Suggestion at index 1 : Test has invalid messageId 'removeFoo', the rule under test allows only one of ['avoidFoo', 'unused', 'renameFoo'].");
});
it("should throw if hydrated desc doesn't match (wrong data value)", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "renameFoo",
data: { newName: "car" },
output: "var bar;"
}, {
messageId: "renameFoo",
data: { newName: "baz" },
output: "var baz;"
}]
}]
}]
});
}, "Error Suggestion at index 0 : Hydrated test desc \"Rename identifier 'foo' to 'car'\" does not match received desc \"Rename identifier 'foo' to 'bar'\".");
});
it("should throw if hydrated desc doesn't match (wrong data key)", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "renameFoo",
data: { newName: "bar" },
output: "var bar;"
}, {
messageId: "renameFoo",
data: { name: "baz" },
output: "var baz;"
}]
}]
}]
});
}, "Error Suggestion at index 1 : Hydrated test desc \"Rename identifier 'foo' to '{{ newName }}'\" does not match received desc \"Rename identifier 'foo' to 'baz'\".");
});
it("should throw if test specifies both desc and data", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
desc: "Rename identifier 'foo' to 'bar'",
messageId: "renameFoo",
data: { newName: "bar" },
output: "var bar;"
}, {
messageId: "renameFoo",
data: { newName: "baz" },
output: "var baz;"
}]
}]
}]
});
}, "Error Suggestion at index 0 : Test should not specify both 'desc' and 'data'.");
});
it("should throw if test uses data but doesn't specify messageId", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "renameFoo",
data: { newName: "bar" },
output: "var bar;"
}, {
data: { newName: "baz" },
output: "var baz;"
}]
}]
}]
});
}, "Error Suggestion at index 1 : Test must specify 'messageId' if 'data' is used.");
});
it("should throw if the resulting suggestion output doesn't match", () => {
assert.throws(() => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
desc: "Rename identifier 'foo' to 'bar'",
output: "var baz;"
}]
}]
}]
});
}, "Expected the applied suggestion fix to match the test suggestion output");
});
it("should fail when specified suggestion isn't an object", () => {
assert.throws(() => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [null]
}]
}]
});
}, "Test suggestion in 'suggestions' array must be an object.");
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [
{
messageId: "renameFoo",
output: "var bar;"
},
"Rename identifier 'foo' to 'baz'"
]
}]
}]
});
}, "Test suggestion in 'suggestions' array must be an object.");
});
it("should fail when the suggestion is an object with an unknown property name", () => {
assert.throws(() => {
ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
valid: [
"var boo;"
],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
message: "Rename identifier 'foo' to 'bar'"
}]
}]
}]
});
}, /Invalid suggestion property name 'message'/u);
});
it("should fail when any of the suggestions is an object with an unknown property name", () => {
assert.throws(() => {
ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
valid: [],
invalid: [{
code: "var foo;",
errors: [{
suggestions: [{
messageId: "renameFoo",
output: "var bar;"
}, {
messageId: "renameFoo",
outpt: "var baz;"
}]
}]
}]
});
}, /Invalid suggestion property name 'outpt'/u);
});
it("should throw an error if a rule that doesn't have `meta.hasSuggestions` enabled produces suggestions", () => {
assert.throws(() => {
ruleTester.run("suggestions-missing-hasSuggestions-property", require("../../fixtures/testers/rule-tester/suggestions").withoutHasSuggestionsProperty, {
valid: [],
invalid: [
{ code: "var foo = bar;", output: "5", errors: 1 }
]
});
}, "Rules with suggestions must set the `meta.hasSuggestions` property to `true`.");
});
});
/**
* Asserts that a particular value will be emitted from an EventEmitter.
* @param {EventEmitter} emitter The emitter that should emit a value
* @param {string} emitType The type of emission to listen for
* @param {any} expectedValue The value that should be emitted
* @returns {Promise<void>} A Promise that fulfills if the value is emitted, and rejects if something else is emitted.
* The Promise will be indefinitely pending if no value is emitted.
*/
function assertEmitted(emitter, emitType, expectedValue) {
return new Promise((resolve, reject) => {
emitter.once(emitType, emittedValue => {
if (emittedValue === expectedValue) {
resolve();
} else {
reject(new Error(`Expected ${expectedValue} to be emitted but ${emittedValue} was emitted instead.`));
}
});
});
}
describe("naming test cases", () => {
it("should use the first argument as the name of the test suite", () => {
const assertion = assertEmitted(ruleTesterTestEmitter, "describe", "this-is-a-rule-name");
ruleTester.run("this-is-a-rule-name", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: []
});
return assertion;
});
it("should use the test code as the name of the tests for valid code (string form)", () => {
const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);");
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"valid(code);"
],
invalid: []
});
return assertion;
});
it("should use the test code as the name of the tests for valid code (object form)", () => {
const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);");
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
{
code: "valid(code);"
}
],
invalid: []
});
return assertion;
});
it("should use the test code as the name of the tests for invalid code", () => {
const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);");
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: [
{
code: "var x = invalid(code);",
output: " x = invalid(code);",
errors: 1
}
]
});
return assertion;
});
// https://github.com/eslint/eslint/issues/8142
it("should use the empty string as the name of the test if the test case is an empty string", () => {
const assertion = assertEmitted(ruleTesterTestEmitter, "it", "");
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
{
code: ""
}
],
invalid: []
});
return assertion;
});
it('should use the "name" property if set to a non-empty string', () => {
const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test");
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: [
{
name: "my test",
code: "var x = invalid(code);",
output: " x = invalid(code);",
errors: 1
}
]
});
return assertion;
});
it('should use the "name" property if set to a non-empty string for valid cases too', () => {
const assertion = assertEmitted(ruleTesterTestEmitter, "it", "my test");
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
{
name: "my test",
code: "valid(code);"
}
],
invalid: []
});
return assertion;
});
it('should use the test code as the name if the "name" property is set to an empty string', () => {
const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);");
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: [
{
name: "",
code: "var x = invalid(code);",
output: " x = invalid(code);",
errors: 1
}
]
});
return assertion;
});
it('should throw if "name" property is not a string', () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [{ code: "foo", name: 123 }],
invalid: [{ code: "foo" }]
});
}, /Optional test case property 'name' must be a string/u);
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: ["foo"],
invalid: [{ code: "foo", name: 123 }]
});
}, /Optional test case property 'name' must be a string/u);
});
it('should throw if "code" property is not a string', () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [{ code: 123 }],
invalid: [{ code: "foo" }]
});
}, /Test case must specify a string value for 'code'/u);
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [123],
invalid: [{ code: "foo" }]
});
}, /Test case must specify a string value for 'code'/u);
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: ["foo"],
invalid: [{ code: 123 }]
});
}, /Test case must specify a string value for 'code'/u);
});
it('should throw if "code" property is missing', () => {
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [{ }],
invalid: [{ code: "foo" }]
});
}, /Test case must specify a string value for 'code'/u);
assert.throws(() => {
ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
valid: ["foo"],
invalid: [{ }]
});
}, /Test case must specify a string value for 'code'/u);
});
});
// https://github.com/eslint/eslint/issues/11615
it("should fail the case if autofix made a syntax error.", () => {
assert.throw(() => {
ruleTester.run(
"foo",
{
meta: {
fixable: "code"
},
create(context) {
return {
Identifier(node) {
context.report({
node,
message: "make a syntax error",
fix(fixer) {
return fixer.replaceText(node, "one two");
}
});
}
};
}
},
{
valid: ["one()"],
invalid: []
}
);
}, /A fatal parsing error occurred in autofix.\nError: .+\nAutofix output:\n.+/u);
});
describe("sanitize test cases", () => {
let originalRuleTesterIt;
let spyRuleTesterIt;
before(() => {
originalRuleTesterIt = FlatRuleTester.it;
spyRuleTesterIt = sinon.spy();
FlatRuleTester.it = spyRuleTesterIt;
});
after(() => {
FlatRuleTester.it = originalRuleTesterIt;
});
beforeEach(() => {
spyRuleTesterIt.resetHistory();
ruleTester = new FlatRuleTester();
});
it("should present newline when using back-tick as new line", () => {
const code = `
var foo = bar;`;
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: [
{
code,
errors: [/^Bad var/u]
}
]
});
sinon.assert.calledWith(spyRuleTesterIt, code);
});
it("should present \\u0000 as a string", () => {
const code = "\u0000";
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: [
{
code,
errors: [/^Bad var/u]
}
]
});
sinon.assert.calledWith(spyRuleTesterIt, "\\u0000");
});
it("should present the pipe character correctly", () => {
const code = "var foo = bar || baz;";
ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [],
invalid: [
{
code,
errors: [/^Bad var/u]
}
]
});
sinon.assert.calledWith(spyRuleTesterIt, code);
});
});
describe("SourceCode#getComments()", () => {
const useGetCommentsRule = {
create: context => ({
Program(node) {
const sourceCode = context.sourceCode;
sourceCode.getComments(node);
}
})
};
it("should throw if called from a valid test case", () => {
assert.throws(() => {
ruleTester.run("use-get-comments", useGetCommentsRule, {
valid: [""],
invalid: []
});
}, /`SourceCode#getComments\(\)` is deprecated/u);
});
it("should throw if called from an invalid test case", () => {
assert.throws(() => {
ruleTester.run("use-get-comments", useGetCommentsRule, {
valid: [],
invalid: [{
code: "",
errors: [{}]
}]
});
}, /`SourceCode#getComments\(\)` is deprecated/u);
});
});
describe("Subclassing", () => {
it("should allow subclasses to set the describe/it/itOnly statics and should correctly use those values", () => {
const assertionDescribe = assertEmitted(ruleTesterTestEmitter, "custom describe", "this-is-a-rule-name");
const assertionIt = assertEmitted(ruleTesterTestEmitter, "custom it", "valid(code);");
const assertionItOnly = assertEmitted(ruleTesterTestEmitter, "custom itOnly", "validOnly(code);");
/**
* Subclass for testing
*/
class RuleTesterSubclass extends FlatRuleTester { }
RuleTesterSubclass.describe = function(text, method) {
ruleTesterTestEmitter.emit("custom describe", text, method);
return method.call(this);
};
RuleTesterSubclass.it = function(text, method) {
ruleTesterTestEmitter.emit("custom it", text, method);
return method.call(this);
};
RuleTesterSubclass.itOnly = function(text, method) {
ruleTesterTestEmitter.emit("custom itOnly", text, method);
return method.call(this);
};
const ruleTesterSubclass = new RuleTesterSubclass();
ruleTesterSubclass.run("this-is-a-rule-name", require("../../fixtures/testers/rule-tester/no-var"), {
valid: [
"valid(code);",
{
code: "validOnly(code);",
only: true
}
],
invalid: []
});
return Promise.all([
assertionDescribe,
assertionIt,
assertionItOnly
]);
});
});
});