2 * @fileoverview Tests for ESLint Tester
3 * @author Nicholas C. Zakas
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
10 const sinon
= require("sinon"),
11 EventEmitter
= require("events"),
12 { RuleTester
} = require("../../../lib/rule-tester"),
13 assert
= require("chai").assert
,
14 nodeAssert
= require("assert"),
15 { noop
} = require("lodash");
17 const NODE_ASSERT_STRICT_EQUAL_OPERATOR
= (() => {
19 nodeAssert
.strictEqual(1, 2);
23 throw new Error("unexpected successful assertion");
26 //------------------------------------------------------------------------------
28 //------------------------------------------------------------------------------
31 * So here's the situation. Because RuleTester uses it() and describe() from
32 * Mocha, any failures would show up in the output of this test file. That means
33 * when we tested that a failure is thrown, that would also count as a failure
34 * in the testing for RuleTester. In order to remove those results from the
35 * results of this file, we need to overwrite it() and describe() just in
36 * RuleTester to do nothing but run code. Effectively, it() and describe()
37 * just become regular functions inside of index.js, not at all related to Mocha.
38 * That allows the results of this file to be untainted and therefore accurate.
40 * To assert that the right arguments are passed to RuleTester.describe/it, an
41 * event emitter is used which emits the arguments.
44 const ruleTesterTestEmitter
= new EventEmitter();
46 //------------------------------------------------------------------------------
48 //------------------------------------------------------------------------------
50 describe("RuleTester", () => {
52 // Stub `describe()` and `it()` while this test suite.
54 RuleTester
.describe = function(text
, method
) {
55 ruleTesterTestEmitter
.emit("describe", text
, method
);
56 return method
.call(this);
58 RuleTester
.it = function(text
, method
) {
59 ruleTesterTestEmitter
.emit("it", text
, method
);
60 return method
.call(this);
64 RuleTester
.describe
= null;
71 * A helper function to verify Node.js core error messages.
72 * @param {string} actual The actual input
73 * @param {string} expected The expected input
74 * @returns {Function} Error callback to verify that the message is correct
75 * for the actual and expected input.
77 function assertErrorMatches(actual
, expected
) {
78 const err
= new nodeAssert
.AssertionError({
81 operator
: NODE_ASSERT_STRICT_EQUAL_OPERATOR
88 RuleTester
.resetDefaultConfig();
89 ruleTester
= new RuleTester();
92 it("should not throw an error when everything passes", () => {
93 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
98 { code
: "eval(foo)", errors
: [{ message
: "eval sucks.", type
: "CallExpression" }] }
103 it("should throw an error when valid code is invalid", () => {
105 assert
.throws(() => {
106 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
111 { code
: "eval(foo)", errors
: [{ message
: "eval sucks.", type
: "CallExpression" }] }
114 }, /Should have no errors but had 1/u);
117 it("should throw an error when valid code is invalid", () => {
119 assert
.throws(() => {
120 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
122 { code
: "eval(foo)" }
125 { code
: "eval(foo)", errors
: [{ message
: "eval sucks.", type
: "CallExpression" }] }
128 }, /Should have no errors but had 1/u);
131 it("should throw an error if invalid code is valid", () => {
133 assert
.throws(() => {
134 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
139 { code
: "Eval(foo)", errors
: [{ message
: "eval sucks.", type
: "CallExpression" }] }
142 }, /Should have 1 error but had 0/u);
145 it("should throw an error when the error message is wrong", () => {
146 assert
.throws(() => {
147 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
149 // Only the invalid test matters here
154 { code
: "var foo = bar;", errors
: [{ message
: "Bad error message." }] }
157 }, assertErrorMatches("Bad var.", "Bad error message."));
160 it("should throw an error when the error message regex does not match", () => {
161 assert
.throws(() => {
162 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
165 { code
: "var foo = bar;", errors
: [{ message
: /Bad error message/u }] }
168 }, /Expected 'Bad var.' to match \/Bad error message\//u);
171 it("should throw an error when the error is not a supported type", () => {
172 assert
.throws(() => {
173 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
175 // Only the invalid test matters here
180 { code
: "var foo = bar;", errors
: [42] }
183 }, /Error should be a string, object, or RegExp/u);
186 it("should throw an error when any of the errors is not a supported type", () => {
187 assert
.throws(() => {
188 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
190 // Only the invalid test matters here
195 { code
: "var foo = bar; var baz = quux", errors
: [{ type
: "VariableDeclaration" }, null] }
198 }, /Error should be a string, object, or RegExp/u);
201 it("should throw an error when the error is a string and it does not match error message", () => {
202 assert
.throws(() => {
203 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
205 // Only the invalid test matters here
210 { code
: "var foo = bar;", errors
: ["Bad error message."] }
213 }, assertErrorMatches("Bad var.", "Bad error message."));
216 it("should throw an error when the error is a string and it does not match error message", () => {
217 assert
.throws(() => {
218 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
223 { code
: "var foo = bar;", errors
: [/Bad error message/u] }
226 }, /Expected 'Bad var.' to match \/Bad error message\//u);
229 it("should not throw an error when the error is a string and it matches error message", () => {
230 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
232 // Only the invalid test matters here
237 { code
: "var foo = bar;", output
: " foo = bar;", errors
: ["Bad var."] }
242 it("should not throw an error when the error is a regex and it matches error message", () => {
243 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
246 { code
: "var foo = bar;", output
: " foo = bar;", errors
: [/^Bad var/u] }
251 it("should throw an error when the error is an object with an unknown property name", () => {
252 assert
.throws(() => {
253 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
258 { code
: "var foo = bar;", errors
: [{ Message
: "Bad var." }] }
261 }, /Invalid error property name 'Message'/u);
264 it("should throw an error when any of the errors is an object with an unknown property name", () => {
265 assert
.throws(() => {
266 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
272 code
: "var foo = bar; var baz = quux",
274 { message
: "Bad var.", type
: "VariableDeclaration" },
275 { message
: "Bad var.", typo
: "VariableDeclaration" }
280 }, /Invalid error property name 'typo'/u);
283 it("should not throw an error when the error is a regex in an object and it matches error message", () => {
284 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
287 { code
: "var foo = bar;", output
: " foo = bar;", errors
: [{ message
: /^Bad var/u }] }
292 it("should throw an error when the expected output doesn't match", () => {
293 assert
.throws(() => {
294 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
299 { code
: "var foo = bar;", output
: "foo = bar", errors
: [{ message
: "Bad var.", type
: "VariableDeclaration" }] }
302 }, /Output is incorrect/u);
305 it("should use strict equality to compare output", () => {
306 const replaceProgramWith5Rule
= {
307 create
: context
=> ({
309 context
.report({ node
, message
: "bad", fix
: fixer
=> fixer
.replaceText(node
, "5") });
315 ruleTester
.run("foo", replaceProgramWith5Rule
, {
318 { code
: "var foo = bar;", output
: "5", errors
: 1 }
322 assert
.throws(() => {
323 ruleTester
.run("foo", replaceProgramWith5Rule
, {
326 { code
: "var foo = bar;", output
: 5, errors
: 1 }
329 }, /Output is incorrect/u);
332 it("should throw an error when the expected output doesn't match and errors is just a number", () => {
333 assert
.throws(() => {
334 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
339 { code
: "var foo = bar;", output
: "foo = bar", errors
: 1 }
342 }, /Output is incorrect/u);
345 it("should not throw an error when the expected output is null and no errors produce output", () => {
346 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
351 { code
: "eval(x)", errors
: 1, output
: null },
352 { code
: "eval(x); eval(y);", errors
: 2, output
: null }
357 it("should throw an error when the expected output is null and problems produce output", () => {
358 assert
.throws(() => {
359 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
364 { code
: "var foo = bar;", output
: null, errors
: 1 }
367 }, /Expected no autofixes to be suggested/u);
369 assert
.throws(() => {
370 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
376 code
: "var foo = bar; var qux = boop;",
382 }, /Expected no autofixes to be suggested/u);
385 it("should throw an error when the expected output is null and only some problems produce output", () => {
386 assert
.throws(() => {
387 ruleTester
.run("fixes-one-problem", require("../../fixtures/testers/rule-tester/fixes-one-problem"), {
390 { code
: "foo", output
: null, errors
: 2 }
393 }, /Expected no autofixes to be suggested/u);
396 it("should throw an error when the expected output isn't specified and problems produce output", () => {
397 assert
.throws(() => {
398 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
403 { code
: "var foo = bar;", errors
: 1 }
406 }, "The rule fixed the code. Please add 'output' property.");
409 it("should throw an error if invalid code specifies wrong type", () => {
410 assert
.throws(() => {
411 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
416 { code
: "eval(foo)", errors
: [{ message
: "eval sucks.", type
: "CallExpression2" }] }
419 }, /Error type should be CallExpression2, found CallExpression/u);
422 it("should throw an error if invalid code specifies wrong line", () => {
423 assert
.throws(() => {
424 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
429 { code
: "eval(foo)", errors
: [{ message
: "eval sucks.", type
: "CallExpression", line
: 5 }] }
432 }, /Error line should be 5/u);
435 it("should not skip line assertion if line is a falsy value", () => {
436 assert
.throws(() => {
437 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
442 { code
: "\neval(foo)", errors
: [{ message
: "eval sucks.", type
: "CallExpression", line
: 0 }] }
445 }, /Error line should be 0/u);
448 it("should throw an error if invalid code specifies wrong column", () => {
449 const wrongColumn
= 10,
450 expectedErrorMessage
= "Error column should be 1";
452 assert
.throws(() => {
453 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
454 valid
: ["Eval(foo)"],
458 message
: "eval sucks.",
463 }, expectedErrorMessage
);
466 it("should not skip column assertion if column is a falsy value", () => {
467 assert
.throws(() => {
468 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
469 valid
: ["Eval(foo)"],
471 code
: "var foo; eval(foo)",
472 errors
: [{ message
: "eval sucks.", column
: 0 }]
475 }, /Error column should be 0/u);
478 it("should throw an error if invalid code specifies wrong endLine", () => {
479 assert
.throws(() => {
480 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
485 { code
: "var foo = bar;", output
: "foo = bar", errors
: [{ message
: "Bad var.", type
: "VariableDeclaration", endLine
: 10 }] }
488 }, "Error endLine should be 10");
491 it("should throw an error if invalid code specifies wrong endColumn", () => {
492 assert
.throws(() => {
493 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
498 { code
: "var foo = bar;", output
: "foo = bar", errors
: [{ message
: "Bad var.", type
: "VariableDeclaration", endColumn
: 10 }] }
501 }, "Error endColumn should be 10");
504 it("should throw an error if invalid code has the wrong number of errors", () => {
505 assert
.throws(() => {
506 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
514 { message
: "eval sucks.", type
: "CallExpression" },
515 { message
: "eval sucks.", type
: "CallExpression" }
520 }, /Should have 2 errors but had 1/u);
523 it("should throw an error if invalid code does not have errors", () => {
524 assert
.throws(() => {
525 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
530 { code
: "eval(foo)" }
533 }, /Did not specify errors for an invalid test of no-eval/u);
536 it("should throw an error if invalid code has the wrong explicit number of errors", () => {
537 assert
.throws(() => {
538 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
543 { code
: "eval(foo)", errors
: 2 }
546 }, /Should have 2 errors but had 1/u);
549 it("should throw an error if there's a parsing error in a valid test", () => {
550 assert
.throws(() => {
551 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
556 { code
: "eval('foo')", errors
: [{}] }
559 }, /fatal parsing error/iu);
562 it("should throw an error if there's a parsing error in an invalid test", () => {
563 assert
.throws(() => {
564 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
569 { code
: "1eval('foo')", errors
: [{}] }
572 }, /fatal parsing error/iu);
575 it("should throw an error if there's a parsing error in an invalid test and errors is just a number", () => {
576 assert
.throws(() => {
577 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
582 { code
: "1eval('foo')", errors
: 1 }
585 }, /fatal parsing error/iu);
588 // https://github.com/eslint/eslint/issues/4779
589 it("should throw an error if there's a parsing error and output doesn't match", () => {
590 assert
.throws(() => {
591 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
594 { code
: "eval(`foo`)", output
: "eval(`foo`);", errors
: [{}] }
597 }, /fatal parsing error/iu);
600 it("should not throw an error if invalid code has at least an expected empty error object", () => {
601 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
602 valid
: ["Eval(foo)"],
610 it("should pass-through the globals config of valid tests to the to rule", () => {
611 ruleTester
.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
615 code
: "var test2 = 'bar'",
616 globals
: { test
: true }
619 invalid
: [{ code
: "bar", errors
: 1 }]
623 it("should pass-through the globals config of invalid tests to the to rule", () => {
624 ruleTester
.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
625 valid
: ["var test = 'foo'"],
628 code
: "var test = 'foo'; var foo = 'bar'",
632 code
: "var test = 'foo'",
633 globals
: { foo
: true },
634 errors
: [{ message
: "Global variable foo should not be used." }]
640 it("should pass-through the settings config to rules", () => {
641 ruleTester
.run("no-test-settings", require("../../fixtures/testers/rule-tester/no-test-settings"), {
644 code
: "var test = 'bar'", settings
: { test
: 1 }
649 code
: "var test = 'bar'", settings
: { "no-test": 22 }, errors
: 1
655 it("should pass-through the filename to the rule", () => {
657 ruleTester
.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), {
660 code
: "var foo = 'bar'",
661 filename
: "somefile.js"
666 code
: "var foo = 'bar'",
668 { message
: "Filename test was not defined." }
676 it("should pass-through the options to the rule", () => {
677 ruleTester
.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
680 code
: "var foo = 'bar'",
686 code
: "var foo = 'bar'",
688 errors
: [{ message
: "Invalid args" }]
694 it("should throw an error if the options are an object", () => {
695 assert
.throws(() => {
696 ruleTester
.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
700 options
: { ok
: true }
705 }, /options must be an array/u);
708 it("should throw an error if the options are a number", () => {
709 assert
.throws(() => {
710 ruleTester
.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
719 }, /options must be an array/u);
722 it("should pass-through the parser to the rule", () => {
723 const spy
= sinon
.spy(ruleTester
.linter
, "verify");
725 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
734 parser
: require
.resolve("esprima"),
739 assert
.strictEqual(spy
.args
[1][1].parser
, require
.resolve("esprima"));
742 it("should pass-through services from parseForESLint to the rule", () => {
743 const enhancedParserPath
= require
.resolve("../../fixtures/parsers/enhanced-parser");
744 const disallowHiRule
= {
745 create
: context
=> ({
747 const disallowed
= context
.parserServices
.test
.getMessage(); // returns "Hi!"
749 if (node
.value
=== disallowed
) {
750 context
.report({ node
, message
: `Don't use '${disallowed}'` });
756 ruleTester
.run("no-hi", disallowHiRule
, {
760 parser
: enhancedParserPath
766 parser
: enhancedParserPath
,
767 errors
: [{ message
: "Don't use 'Hi!'" }]
773 it("should prevent invalid options schemas", () => {
774 assert
.throws(() => {
775 ruleTester
.run("no-invalid-schema", require("../../fixtures/testers/rule-tester/no-invalid-schema"), {
777 "var answer = 6 * 7;",
778 { code
: "var answer = 6 * 7;", options
: [] }
781 { code
: "var answer = 6 * 7;", options
: ["bar"], errors
: [{ message
: "Expected nothing." }] }
784 }, "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");
788 it("should prevent schema violations in options", () => {
789 assert
.throws(() => {
790 ruleTester
.run("no-schema-violation", require("../../fixtures/testers/rule-tester/no-schema-violation"), {
792 "var answer = 6 * 7;",
793 { code
: "var answer = 6 * 7;", options
: ["foo"] }
796 { code
: "var answer = 6 * 7;", options
: ["bar"], errors
: [{ message
: "Expected foo." }] }
799 }, /Value "bar" should be equal to one of the allowed values./u);
803 it("should disallow invalid defaults in rules", () => {
804 const ruleWithInvalidDefaults
= {
814 enum: ["foo", "bar"],
818 additionalProperties
: false
827 assert
.throws(() => {
828 ruleTester
.run("invalid-defaults", ruleWithInvalidDefaults
, {
837 }, /Schema for rule invalid-defaults is invalid: default is ignored for: data1\.foo/u);
840 it("throw an error when an unknown config option is included", () => {
841 assert
.throws(() => {
842 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
844 { code
: "Eval(foo)", foo
: "bar" }
848 }, /ESLint configuration in rule-tester is invalid./u);
851 it("throw an error when an invalid config value is included", () => {
852 assert
.throws(() => {
853 ruleTester
.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
855 { code
: "Eval(foo)", env
: ["es6"] }
859 }, /Property "env" is the wrong type./u);
862 it("should pass-through the tester config to the rule", () => {
863 ruleTester
= new RuleTester({
864 globals
: { test
: true }
867 ruleTester
.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
872 invalid
: [{ code
: "bar", errors
: 1, globals
: { foo
: true } }]
876 it("should correctly set the globals configuration", () => {
877 const config
= { globals
: { test
: true } };
879 RuleTester
.setDefaultConfig(config
);
881 RuleTester
.getDefaultConfig().globals
.test
,
882 "The default config object is incorrect"
886 it("should correctly reset the global configuration", () => {
887 const config
= { globals
: { test
: true } };
889 RuleTester
.setDefaultConfig(config
);
890 RuleTester
.resetDefaultConfig();
891 assert
.deepStrictEqual(
892 RuleTester
.getDefaultConfig(),
894 "The default configuration has not reset correctly"
898 it("should enforce the global configuration to be an object", () => {
901 * Set the default config for the rules tester
902 * @param {Object} config configuration object
903 * @returns {Function} Function to be executed
906 function setConfig(config
) {
908 RuleTester
.setDefaultConfig(config
);
911 assert
.throw(setConfig());
912 assert
.throw(setConfig(1));
913 assert
.throw(setConfig(3.14));
914 assert
.throw(setConfig("foo"));
915 assert
.throw(setConfig(null));
916 assert
.throw(setConfig(true));
919 it("should pass-through the globals config to the tester then to the to rule", () => {
920 const config
= { globals
: { test
: true } };
922 RuleTester
.setDefaultConfig(config
);
923 ruleTester
= new RuleTester();
925 ruleTester
.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
930 invalid
: [{ code
: "bar", errors
: 1, globals
: { foo
: true } }]
934 it("should throw an error if AST was modified", () => {
935 assert
.throws(() => {
936 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), {
942 }, "Rule should not modify AST.");
943 assert
.throws(() => {
944 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), {
947 { code
: "var bar = 0;", errors
: ["error"] }
950 }, "Rule should not modify AST.");
953 it("should throw an error if AST was modified (at Program)", () => {
954 assert
.throws(() => {
955 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), {
961 }, "Rule should not modify AST.");
962 assert
.throws(() => {
963 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), {
966 { code
: "var bar = 0;", errors
: ["error"] }
969 }, "Rule should not modify AST.");
972 it("should throw an error if AST was modified (at Program:exit)", () => {
973 assert
.throws(() => {
974 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
980 }, "Rule should not modify AST.");
981 assert
.throws(() => {
982 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
985 { code
: "var bar = 0;", errors
: ["error"] }
988 }, "Rule should not modify AST.");
991 it("should throw an error if rule uses start and end properties on nodes, tokens or comments", () => {
992 const usesStartEndRule
= {
995 const sourceCode
= context
.getSourceCode();
998 CallExpression(node
) {
999 noop(node
.arguments
[1].start
);
1001 "BinaryExpression[operator='+']"(node
) {
1004 "UnaryExpression[operator='-']"(node
) {
1005 noop(sourceCode
.getFirstToken(node
).start
);
1007 ConditionalExpression(node
) {
1008 noop(sourceCode
.getFirstToken(node
).end
);
1010 BlockStatement(node
) {
1011 noop(sourceCode
.getCommentsInside(node
)[0].start
);
1013 ObjectExpression(node
) {
1014 noop(sourceCode
.getCommentsInside(node
)[0].end
);
1023 assert
.throws(() => {
1024 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1025 valid
: ["foo(a, b)"],
1028 }, "Use node.range[0] instead of node.start");
1029 assert
.throws(() => {
1030 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1032 invalid
: [{ code
: "var a = b * (c + d) / e;", errors
: 1 }]
1034 }, "Use node.range[1] instead of node.end");
1035 assert
.throws(() => {
1036 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1038 invalid
: [{ code
: "var a = -b * c;", errors
: 1 }]
1040 }, "Use token.range[0] instead of token.start");
1041 assert
.throws(() => {
1042 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1043 valid
: ["var a = b ? c : d;"],
1046 }, "Use token.range[1] instead of token.end");
1047 assert
.throws(() => {
1048 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1049 valid
: ["function f() { /* comment */ }"],
1052 }, "Use token.range[0] instead of token.start");
1053 assert
.throws(() => {
1054 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1056 invalid
: [{ code
: "var x = //\n {\n //comment\n //\n}", errors
: 1 }]
1058 }, "Use token.range[1] instead of token.end");
1060 const enhancedParserPath
= require
.resolve("../../fixtures/parsers/enhanced-parser");
1062 assert
.throws(() => {
1063 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1064 valid
: [{ code
: "foo(a, b)", parser
: enhancedParserPath
}],
1067 }, "Use node.range[0] instead of node.start");
1068 assert
.throws(() => {
1069 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1071 invalid
: [{ code
: "var a = b * (c + d) / e;", parser
: enhancedParserPath
, errors
: 1 }]
1073 }, "Use node.range[1] instead of node.end");
1074 assert
.throws(() => {
1075 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1077 invalid
: [{ code
: "var a = -b * c;", parser
: enhancedParserPath
, errors
: 1 }]
1079 }, "Use token.range[0] instead of token.start");
1080 assert
.throws(() => {
1081 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1082 valid
: [{ code
: "var a = b ? c : d;", parser
: enhancedParserPath
}],
1085 }, "Use token.range[1] instead of token.end");
1086 assert
.throws(() => {
1087 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1088 valid
: [{ code
: "function f() { /* comment */ }", parser
: enhancedParserPath
}],
1091 }, "Use token.range[0] instead of token.start");
1092 assert
.throws(() => {
1093 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1095 invalid
: [{ code
: "var x = //\n {\n //comment\n //\n}", parser
: enhancedParserPath
, errors
: 1 }]
1097 }, "Use token.range[1] instead of token.end");
1099 assert
.throws(() => {
1100 ruleTester
.run("uses-start-end", usesStartEndRule
, {
1101 valid
: [{ code
: "@foo class A {}", parser
: require
.resolve("../../fixtures/parsers/enhanced-parser2") }],
1104 }, "Use node.range[0] instead of node.start");
1107 it("should throw an error if no test scenarios given", () => {
1108 assert
.throws(() => {
1109 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"));
1110 }, "Test Scenarios for rule foo : Could not find test scenario object");
1113 it("should throw an error if no acceptable test scenario object is given", () => {
1114 assert
.throws(() => {
1115 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), []);
1116 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios");
1117 assert
.throws(() => {
1118 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), "");
1119 }, "Test Scenarios for rule foo : Could not find test scenario object");
1120 assert
.throws(() => {
1121 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), 2);
1122 }, "Test Scenarios for rule foo : Could not find test scenario object");
1123 assert
.throws(() => {
1124 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {});
1125 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios");
1126 assert
.throws(() => {
1127 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1130 }, "Test Scenarios for rule foo is invalid:\nCould not find any invalid test scenarios");
1131 assert
.throws(() => {
1132 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1135 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios");
1138 // Nominal message/messageId use cases
1139 it("should assert match if message provided in both test and result.", () => {
1140 assert
.throws(() => {
1141 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly
, {
1143 invalid
: [{ code
: "foo", errors
: [{ message
: "something" }] }]
1145 }, /Avoid using variables named/u);
1147 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly
, {
1149 invalid
: [{ code
: "foo", errors
: [{ message
: "Avoid using variables named 'foo'." }] }]
1153 it("should assert match between messageId if provided in both test and result.", () => {
1154 assert
.throws(() => {
1155 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData
, {
1157 invalid
: [{ code
: "foo", errors
: [{ messageId
: "unused" }] }]
1159 }, "messageId 'avoidFoo' does not match expected messageId 'unused'.");
1161 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData
, {
1163 invalid
: [{ code
: "foo", errors
: [{ messageId
: "avoidFoo" }] }]
1166 it("should assert match between resulting message output if messageId and data provided in both test and result", () => {
1167 assert
.throws(() => {
1168 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData
, {
1170 invalid
: [{ code
: "foo", errors
: [{ messageId
: "avoidFoo", data
: { name
: "notFoo" } }] }]
1172 }, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\"");
1175 // messageId/message misconfiguration cases
1176 it("should throw if user tests for both message and messageId", () => {
1177 assert
.throws(() => {
1178 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData
, {
1180 invalid
: [{ code
: "foo", errors
: [{ message
: "something", messageId
: "avoidFoo" }] }]
1182 }, "Error should not specify both 'message' and a 'messageId'.");
1184 it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => {
1185 assert
.throws(() => {
1186 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly
, {
1188 invalid
: [{ code
: "foo", errors
: [{ messageId
: "avoidFoo" }] }]
1190 }, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'");
1192 it("should throw if user tests for messageId not listed in the rule's meta syntax.", () => {
1193 assert
.throws(() => {
1194 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData
, {
1196 invalid
: [{ code
: "foo", errors
: [{ messageId
: "useFoo" }] }]
1198 }, /Invalid messageId 'useFoo'/u);
1200 it("should throw if data provided without messageId.", () => {
1201 assert
.throws(() => {
1202 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData
, {
1204 invalid
: [{ code
: "foo", errors
: [{ data
: "something" }] }]
1206 }, "Error must specify 'messageId' if 'data' is used.");
1209 describe("suggestions", () => {
1210 it("should pass with valid suggestions (tested using desc)", () => {
1211 ruleTester
.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic
, {
1219 desc
: "Rename identifier 'foo' to 'bar'",
1227 it("should pass with suggestions on multiple lines", () => {
1228 ruleTester
.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic
, {
1232 code
: "function foo() {\n var foo = 1;\n}",
1235 desc
: "Rename identifier 'foo' to 'bar'",
1236 output
: "function bar() {\n var foo = 1;\n}"
1240 desc
: "Rename identifier 'foo' to 'bar'",
1241 output
: "function foo() {\n var bar = 1;\n}"
1249 it("should pass with valid suggestions (tested using messageIds)", () => {
1250 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1256 messageId
: "renameFoo",
1259 messageId
: "renameFoo",
1267 it("should pass with valid suggestions (one tested using messageIds, the other using desc)", () => {
1268 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1274 messageId
: "renameFoo",
1277 desc
: "Rename identifier 'foo' to 'baz'",
1285 it("should pass with valid suggestions (tested using both desc and messageIds for the same suggestion)", () => {
1286 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1292 desc
: "Rename identifier 'foo' to 'bar'",
1293 messageId
: "renameFoo",
1296 desc
: "Rename identifier 'foo' to 'baz'",
1297 messageId
: "renameFoo",
1305 it("should pass with valid suggestions (tested using only desc on a rule that utilizes meta.messages)", () => {
1306 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1312 desc
: "Rename identifier 'foo' to 'bar'",
1315 desc
: "Rename identifier 'foo' to 'baz'",
1323 it("should pass with valid suggestions (tested using messageIds and data)", () => {
1324 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1330 messageId
: "renameFoo",
1331 data
: { newName
: "bar" },
1334 messageId
: "renameFoo",
1335 data
: { newName
: "baz" },
1344 it("should pass when tested using empty suggestion test objects if the array length is correct", () => {
1345 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1350 suggestions
: [{}, {}]
1356 it("should support explicitly expecting no suggestions", () => {
1357 [void 0, null, false, []].forEach(suggestions
=> {
1358 ruleTester
.run("suggestions-basic", require("../../fixtures/testers/rule-tester/no-eval"), {
1361 code
: "eval('var foo');",
1370 it("should fail when expecting no suggestions and there are suggestions", () => {
1371 [void 0, null, false, []].forEach(suggestions
=> {
1372 assert
.throws(() => {
1373 ruleTester
.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic
, {
1382 }, "Error should have no suggestions on error with message: \"Avoid using identifiers named 'foo'.\"");
1386 it("should fail when testing for suggestions that don't exist", () => {
1387 assert
.throws(() => {
1388 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
1394 messageId
: "this-does-not-exist"
1399 }, "Error should have an array of suggestions. Instead received \"undefined\" on error with message: \"Bad var.\"");
1402 it("should fail when there are a different number of suggestions", () => {
1403 assert
.throws(() => {
1404 ruleTester
.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic
, {
1410 desc
: "Rename identifier 'foo' to 'bar'",
1413 desc
: "Rename identifier 'foo' to 'baz'",
1419 }, "Error should have 2 suggestions. Instead found 1 suggestions");
1422 it("should throw if the suggestion description doesn't match", () => {
1423 assert
.throws(() => {
1424 ruleTester
.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic
, {
1436 }, "Error Suggestion at index 0 : desc should be \"not right\" but got \"Rename identifier 'foo' to 'bar'\" instead.");
1439 it("should throw if the suggestion description doesn't match (although messageIds match)", () => {
1440 assert
.throws(() => {
1441 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1447 desc
: "Rename identifier 'foo' to 'bar'",
1448 messageId
: "renameFoo",
1451 desc
: "Rename id 'foo' to 'baz'",
1452 messageId
: "renameFoo",
1458 }, "Error Suggestion at index 1 : desc should be \"Rename id 'foo' to 'baz'\" but got \"Rename identifier 'foo' to 'baz'\" instead.");
1461 it("should throw if the suggestion messageId doesn't match", () => {
1462 assert
.throws(() => {
1463 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1469 messageId
: "unused",
1472 messageId
: "renameFoo",
1478 }, "Error Suggestion at index 0 : messageId should be 'unused' but got 'renameFoo' instead.");
1481 it("should throw if the suggestion messageId doesn't match (although descriptions match)", () => {
1482 assert
.throws(() => {
1483 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1489 desc
: "Rename identifier 'foo' to 'bar'",
1490 messageId
: "renameFoo",
1493 desc
: "Rename identifier 'foo' to 'baz'",
1494 messageId
: "avoidFoo",
1500 }, "Error Suggestion at index 1 : messageId should be 'avoidFoo' but got 'renameFoo' instead.");
1503 it("should throw if test specifies messageId for a rule that doesn't have meta.messages", () => {
1504 assert
.throws(() => {
1505 ruleTester
.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic
, {
1511 messageId
: "renameFoo",
1517 }, "Error Suggestion at index 0 : Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.");
1520 it("should throw if test specifies messageId that doesn't exist in the rule's meta.messages", () => {
1521 assert
.throws(() => {
1522 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1528 messageId
: "renameFoo",
1531 messageId
: "removeFoo",
1537 }, "Error Suggestion at index 1 : Test has invalid messageId 'removeFoo', the rule under test allows only one of ['avoidFoo', 'unused', 'renameFoo'].");
1540 it("should throw if hydrated desc doesn't match (wrong data value)", () => {
1541 assert
.throws(() => {
1542 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1548 messageId
: "renameFoo",
1549 data
: { newName
: "car" },
1552 messageId
: "renameFoo",
1553 data
: { newName
: "baz" },
1559 }, "Error Suggestion at index 0 : Hydrated test desc \"Rename identifier 'foo' to 'car'\" does not match received desc \"Rename identifier 'foo' to 'bar'\".");
1562 it("should throw if hydrated desc doesn't match (wrong data key)", () => {
1563 assert
.throws(() => {
1564 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1570 messageId
: "renameFoo",
1571 data
: { newName
: "bar" },
1574 messageId
: "renameFoo",
1575 data
: { name
: "baz" },
1581 }, "Error Suggestion at index 1 : Hydrated test desc \"Rename identifier 'foo' to '{{ newName }}'\" does not match received desc \"Rename identifier 'foo' to 'baz'\".");
1584 it("should throw if test specifies both desc and data", () => {
1585 assert
.throws(() => {
1586 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1592 desc
: "Rename identifier 'foo' to 'bar'",
1593 messageId
: "renameFoo",
1594 data
: { newName
: "bar" },
1597 messageId
: "renameFoo",
1598 data
: { newName
: "baz" },
1604 }, "Error Suggestion at index 0 : Test should not specify both 'desc' and 'data'.");
1607 it("should throw if test uses data but doesn't specify messageId", () => {
1608 assert
.throws(() => {
1609 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1615 messageId
: "renameFoo",
1616 data
: { newName
: "bar" },
1619 data
: { newName
: "baz" },
1625 }, "Error Suggestion at index 1 : Test must specify 'messageId' if 'data' is used.");
1628 it("should throw if the resulting suggestion output doesn't match", () => {
1629 assert
.throws(() => {
1630 ruleTester
.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic
, {
1636 desc
: "Rename identifier 'foo' to 'bar'",
1642 }, "Expected the applied suggestion fix to match the test suggestion output");
1645 it("should fail when specified suggestion isn't an object", () => {
1646 assert
.throws(() => {
1647 ruleTester
.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic
, {
1656 }, "Test suggestion in 'suggestions' array must be an object.");
1658 assert
.throws(() => {
1659 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1666 messageId
: "renameFoo",
1669 "Rename identifier 'foo' to 'baz'"
1674 }, "Test suggestion in 'suggestions' array must be an object.");
1677 it("should fail when the suggestion is an object with an unknown property name", () => {
1678 assert
.throws(() => {
1679 ruleTester
.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic
, {
1687 message
: "Rename identifier 'foo' to 'bar'"
1692 }, /Invalid suggestion property name 'message'/u);
1695 it("should fail when any of the suggestions is an object with an unknown property name", () => {
1696 assert
.throws(() => {
1697 ruleTester
.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds
, {
1703 messageId
: "renameFoo",
1706 messageId
: "renameFoo",
1712 }, /Invalid suggestion property name 'outpt'/u);
1716 describe("naming test cases", () => {
1719 * Asserts that a particular value will be emitted from an EventEmitter.
1720 * @param {EventEmitter} emitter The emitter that should emit a value
1721 * @param {string} emitType The type of emission to listen for
1722 * @param {*} expectedValue The value that should be emitted
1723 * @returns {Promise} A Promise that fulfills if the value is emitted, and rejects if something else is emitted.
1724 * The Promise will be indefinitely pending if no value is emitted.
1726 function assertEmitted(emitter
, emitType
, expectedValue
) {
1727 return new Promise((resolve
, reject
) => {
1728 emitter
.once(emitType
, emittedValue
=> {
1729 if (emittedValue
=== expectedValue
) {
1732 reject(new Error(`Expected ${expectedValue} to be emitted but ${emittedValue} was emitted instead.`));
1738 it("should use the first argument as the name of the test suite", () => {
1739 const assertion
= assertEmitted(ruleTesterTestEmitter
, "describe", "this-is-a-rule-name");
1741 ruleTester
.run("this-is-a-rule-name", require("../../fixtures/testers/rule-tester/no-var"), {
1749 it("should use the test code as the name of the tests for valid code (string form)", () => {
1750 const assertion
= assertEmitted(ruleTesterTestEmitter
, "it", "valid(code);");
1752 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
1762 it("should use the test code as the name of the tests for valid code (object form)", () => {
1763 const assertion
= assertEmitted(ruleTesterTestEmitter
, "it", "valid(code);");
1765 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
1768 code
: "valid(code);"
1777 it("should use the test code as the name of the tests for invalid code", () => {
1778 const assertion
= assertEmitted(ruleTesterTestEmitter
, "it", "var x = invalid(code);");
1780 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
1784 code
: "var x = invalid(code);",
1785 output
: " x = invalid(code);",
1794 // https://github.com/eslint/eslint/issues/8142
1795 it("should use the empty string as the name of the test if the test case is an empty string", () => {
1796 const assertion
= assertEmitted(ruleTesterTestEmitter
, "it", "");
1798 ruleTester
.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
1811 // https://github.com/eslint/eslint/issues/11615
1812 it("should fail the case if autofix made a syntax error.", () => {
1813 assert
.throw(() => {
1820 message
: "make a syntax error",
1822 return fixer
.replaceText(node
, "one two");
1832 }, /A fatal parsing error occurred in autofix.\nError: .+\nAutofix output:\n.+/u);
1835 describe("sanitize test cases", () => {
1836 let originalRuleTesterIt
;
1837 let spyRuleTesterIt
;
1840 originalRuleTesterIt
= RuleTester
.it
;
1841 spyRuleTesterIt
= sinon
.spy();
1842 RuleTester
.it
= spyRuleTesterIt
;
1845 RuleTester
.it
= originalRuleTesterIt
;
1848 spyRuleTesterIt
.resetHistory();
1849 ruleTester
= new RuleTester();
1851 it("should present newline when using back-tick as new line", () => {
1855 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
1860 errors
: [/^Bad var/u]
1864 sinon
.assert
.calledWith(spyRuleTesterIt
, code
);
1866 it("should present \\u0000 as a string", () => {
1867 const code
= "\u0000";
1869 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
1874 errors
: [/^Bad var/u]
1878 sinon
.assert
.calledWith(spyRuleTesterIt
, "\\u0000");
1880 it("should present the pipe character correctly", () => {
1881 const code
= "var foo = bar || baz;";
1883 ruleTester
.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
1888 errors
: [/^Bad var/u]
1892 sinon
.assert
.calledWith(spyRuleTesterIt
, code
);