]> git.proxmox.com Git - pve-eslint.git/blame - eslint/tests/lib/rule-tester/rule-tester.js
import eslint 7.28.0
[pve-eslint.git] / eslint / tests / lib / rule-tester / rule-tester.js
CommitLineData
eb39fafa
DC
1/**
2 * @fileoverview Tests for ESLint Tester
3 * @author Nicholas C. Zakas
4 */
5"use strict";
6
7//------------------------------------------------------------------------------
8// Requirements
9//------------------------------------------------------------------------------
10const sinon = require("sinon"),
11 EventEmitter = require("events"),
12 { RuleTester } = require("../../../lib/rule-tester"),
13 assert = require("chai").assert,
5422a9cc 14 nodeAssert = require("assert");
eb39fafa
DC
15
16const NODE_ASSERT_STRICT_EQUAL_OPERATOR = (() => {
17 try {
18 nodeAssert.strictEqual(1, 2);
19 } catch (err) {
20 return err.operator;
21 }
22 throw new Error("unexpected successful assertion");
23})();
24
5422a9cc
TL
25/**
26 * Do nothing.
27 * @returns {void}
28 */
29function noop() {
30
31 // do nothing.
32}
33
eb39fafa
DC
34//------------------------------------------------------------------------------
35// Rewire Things
36//------------------------------------------------------------------------------
37
38/*
39 * So here's the situation. Because RuleTester uses it() and describe() from
40 * Mocha, any failures would show up in the output of this test file. That means
41 * when we tested that a failure is thrown, that would also count as a failure
42 * in the testing for RuleTester. In order to remove those results from the
43 * results of this file, we need to overwrite it() and describe() just in
44 * RuleTester to do nothing but run code. Effectively, it() and describe()
45 * just become regular functions inside of index.js, not at all related to Mocha.
46 * That allows the results of this file to be untainted and therefore accurate.
47 *
48 * To assert that the right arguments are passed to RuleTester.describe/it, an
49 * event emitter is used which emits the arguments.
50 */
51
52const ruleTesterTestEmitter = new EventEmitter();
53
54//------------------------------------------------------------------------------
55// Tests
56//------------------------------------------------------------------------------
57
58describe("RuleTester", () => {
59
60 // Stub `describe()` and `it()` while this test suite.
61 before(() => {
62 RuleTester.describe = function(text, method) {
63 ruleTesterTestEmitter.emit("describe", text, method);
64 return method.call(this);
65 };
66 RuleTester.it = function(text, method) {
67 ruleTesterTestEmitter.emit("it", text, method);
68 return method.call(this);
69 };
70 });
71 after(() => {
72 RuleTester.describe = null;
73 RuleTester.it = null;
74 });
75
76 let ruleTester;
77
78 /**
79 * A helper function to verify Node.js core error messages.
80 * @param {string} actual The actual input
81 * @param {string} expected The expected input
82 * @returns {Function} Error callback to verify that the message is correct
83 * for the actual and expected input.
84 */
85 function assertErrorMatches(actual, expected) {
86 const err = new nodeAssert.AssertionError({
87 actual,
88 expected,
89 operator: NODE_ASSERT_STRICT_EQUAL_OPERATOR
90 });
91
92 return err.message;
93 }
94
95 beforeEach(() => {
96 RuleTester.resetDefaultConfig();
97 ruleTester = new RuleTester();
98 });
99
100 it("should not throw an error when everything passes", () => {
101 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
102 valid: [
103 "Eval(foo)"
104 ],
105 invalid: [
106 { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
107 ]
108 });
109 });
110
111 it("should throw an error when valid code is invalid", () => {
112
113 assert.throws(() => {
114 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
115 valid: [
116 "eval(foo)"
117 ],
118 invalid: [
119 { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
120 ]
121 });
122 }, /Should have no errors but had 1/u);
123 });
124
125 it("should throw an error when valid code is invalid", () => {
126
127 assert.throws(() => {
128 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
129 valid: [
130 { code: "eval(foo)" }
131 ],
132 invalid: [
133 { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
134 ]
135 });
136 }, /Should have no errors but had 1/u);
137 });
138
139 it("should throw an error if invalid code is valid", () => {
140
141 assert.throws(() => {
142 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
143 valid: [
144 "Eval(foo)"
145 ],
146 invalid: [
147 { code: "Eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression" }] }
148 ]
149 });
150 }, /Should have 1 error but had 0/u);
151 });
152
153 it("should throw an error when the error message is wrong", () => {
154 assert.throws(() => {
155 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
156
157 // Only the invalid test matters here
158 valid: [
159 "bar = baz;"
160 ],
161 invalid: [
162 { code: "var foo = bar;", errors: [{ message: "Bad error message." }] }
163 ]
164 });
165 }, assertErrorMatches("Bad var.", "Bad error message."));
166 });
167
168 it("should throw an error when the error message regex does not match", () => {
169 assert.throws(() => {
170 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
171 valid: [],
172 invalid: [
173 { code: "var foo = bar;", errors: [{ message: /Bad error message/u }] }
174 ]
175 });
176 }, /Expected 'Bad var.' to match \/Bad error message\//u);
177 });
178
179 it("should throw an error when the error is not a supported type", () => {
180 assert.throws(() => {
181 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
182
183 // Only the invalid test matters here
184 valid: [
185 "bar = baz;"
186 ],
187 invalid: [
188 { code: "var foo = bar;", errors: [42] }
189 ]
190 });
191 }, /Error should be a string, object, or RegExp/u);
192 });
193
194 it("should throw an error when any of the errors is not a supported type", () => {
195 assert.throws(() => {
196 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
197
198 // Only the invalid test matters here
199 valid: [
200 "bar = baz;"
201 ],
202 invalid: [
203 { code: "var foo = bar; var baz = quux", errors: [{ type: "VariableDeclaration" }, null] }
204 ]
205 });
206 }, /Error should be a string, object, or RegExp/u);
207 });
208
209 it("should throw an error when the error is a string and it does not match error message", () => {
210 assert.throws(() => {
211 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
212
213 // Only the invalid test matters here
214 valid: [
215 "bar = baz;"
216 ],
217 invalid: [
218 { code: "var foo = bar;", errors: ["Bad error message."] }
219 ]
220 });
221 }, assertErrorMatches("Bad var.", "Bad error message."));
222 });
223
224 it("should throw an error when the error is a string and it does not match error message", () => {
225 assert.throws(() => {
226 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
227
228 valid: [
229 ],
230 invalid: [
231 { code: "var foo = bar;", errors: [/Bad error message/u] }
232 ]
233 });
234 }, /Expected 'Bad var.' to match \/Bad error message\//u);
235 });
236
237 it("should not throw an error when the error is a string and it matches error message", () => {
238 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
239
240 // Only the invalid test matters here
241 valid: [
242 "bar = baz;"
243 ],
244 invalid: [
245 { code: "var foo = bar;", output: " foo = bar;", errors: ["Bad var."] }
246 ]
247 });
248 });
249
250 it("should not throw an error when the error is a regex and it matches error message", () => {
251 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
252 valid: [],
253 invalid: [
254 { code: "var foo = bar;", output: " foo = bar;", errors: [/^Bad var/u] }
255 ]
256 });
257 });
258
259 it("should throw an error when the error is an object with an unknown property name", () => {
260 assert.throws(() => {
261 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
262 valid: [
263 "bar = baz;"
264 ],
265 invalid: [
266 { code: "var foo = bar;", errors: [{ Message: "Bad var." }] }
267 ]
268 });
269 }, /Invalid error property name 'Message'/u);
270 });
271
272 it("should throw an error when any of the errors is an object with an unknown property name", () => {
273 assert.throws(() => {
274 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
275 valid: [
276 "bar = baz;"
277 ],
278 invalid: [
279 {
280 code: "var foo = bar; var baz = quux",
281 errors: [
282 { message: "Bad var.", type: "VariableDeclaration" },
283 { message: "Bad var.", typo: "VariableDeclaration" }
284 ]
285 }
286 ]
287 });
288 }, /Invalid error property name 'typo'/u);
289 });
290
291 it("should not throw an error when the error is a regex in an object and it matches error message", () => {
292 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
293 valid: [],
294 invalid: [
295 { code: "var foo = bar;", output: " foo = bar;", errors: [{ message: /^Bad var/u }] }
296 ]
297 });
298 });
299
300 it("should throw an error when the expected output doesn't match", () => {
301 assert.throws(() => {
302 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
303 valid: [
304 "bar = baz;"
305 ],
306 invalid: [
307 { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration" }] }
308 ]
309 });
310 }, /Output is incorrect/u);
311 });
312
313 it("should use strict equality to compare output", () => {
314 const replaceProgramWith5Rule = {
6f036462
TL
315 meta: {
316 fixable: "code"
317 },
318
eb39fafa
DC
319 create: context => ({
320 Program(node) {
321 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
322 }
323 })
324 };
325
326 // Should not throw.
327 ruleTester.run("foo", replaceProgramWith5Rule, {
328 valid: [],
329 invalid: [
330 { code: "var foo = bar;", output: "5", errors: 1 }
331 ]
332 });
333
334 assert.throws(() => {
335 ruleTester.run("foo", replaceProgramWith5Rule, {
336 valid: [],
337 invalid: [
338 { code: "var foo = bar;", output: 5, errors: 1 }
339 ]
340 });
341 }, /Output is incorrect/u);
342 });
343
344 it("should throw an error when the expected output doesn't match and errors is just a number", () => {
345 assert.throws(() => {
346 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
347 valid: [
348 "bar = baz;"
349 ],
350 invalid: [
351 { code: "var foo = bar;", output: "foo = bar", errors: 1 }
352 ]
353 });
354 }, /Output is incorrect/u);
355 });
356
357 it("should not throw an error when the expected output is null and no errors produce output", () => {
358 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
359 valid: [
360 "bar = baz;"
361 ],
362 invalid: [
363 { code: "eval(x)", errors: 1, output: null },
364 { code: "eval(x); eval(y);", errors: 2, output: null }
365 ]
366 });
367 });
368
369 it("should throw an error when the expected output is null and problems produce output", () => {
370 assert.throws(() => {
371 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
372 valid: [
373 "bar = baz;"
374 ],
375 invalid: [
376 { code: "var foo = bar;", output: null, errors: 1 }
377 ]
378 });
379 }, /Expected no autofixes to be suggested/u);
380
381 assert.throws(() => {
382 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
383 valid: [
384 "bar = baz;"
385 ],
386 invalid: [
387 {
388 code: "var foo = bar; var qux = boop;",
389 output: null,
390 errors: 2
391 }
392 ]
393 });
394 }, /Expected no autofixes to be suggested/u);
395 });
396
397 it("should throw an error when the expected output is null and only some problems produce output", () => {
398 assert.throws(() => {
399 ruleTester.run("fixes-one-problem", require("../../fixtures/testers/rule-tester/fixes-one-problem"), {
400 valid: [],
401 invalid: [
402 { code: "foo", output: null, errors: 2 }
403 ]
404 });
405 }, /Expected no autofixes to be suggested/u);
406 });
407
408 it("should throw an error when the expected output isn't specified and problems produce output", () => {
409 assert.throws(() => {
410 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
411 valid: [
412 "bar = baz;"
413 ],
414 invalid: [
415 { code: "var foo = bar;", errors: 1 }
416 ]
417 });
418 }, "The rule fixed the code. Please add 'output' property.");
419 });
420
421 it("should throw an error if invalid code specifies wrong type", () => {
422 assert.throws(() => {
423 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
424 valid: [
425 "Eval(foo)"
426 ],
427 invalid: [
428 { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression2" }] }
429 ]
430 });
431 }, /Error type should be CallExpression2, found CallExpression/u);
432 });
433
434 it("should throw an error if invalid code specifies wrong line", () => {
435 assert.throws(() => {
436 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
437 valid: [
438 "Eval(foo)"
439 ],
440 invalid: [
441 { code: "eval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 5 }] }
442 ]
443 });
444 }, /Error line should be 5/u);
445 });
446
447 it("should not skip line assertion if line is a falsy value", () => {
448 assert.throws(() => {
449 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
450 valid: [
451 "Eval(foo)"
452 ],
453 invalid: [
454 { code: "\neval(foo)", errors: [{ message: "eval sucks.", type: "CallExpression", line: 0 }] }
455 ]
456 });
457 }, /Error line should be 0/u);
458 });
459
460 it("should throw an error if invalid code specifies wrong column", () => {
461 const wrongColumn = 10,
462 expectedErrorMessage = "Error column should be 1";
463
464 assert.throws(() => {
465 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
466 valid: ["Eval(foo)"],
467 invalid: [{
468 code: "eval(foo)",
469 errors: [{
470 message: "eval sucks.",
471 column: wrongColumn
472 }]
473 }]
474 });
475 }, expectedErrorMessage);
476 });
477
6f036462
TL
478 it("should throw error for empty error array", () => {
479 assert.throws(() => {
480 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
481 valid: [],
482 invalid: [{
483 code: "var foo;",
484 errors: []
485 }]
486 });
487 }, /Invalid cases must have at least one error/u);
488 });
489
490 it("should throw error for errors : 0", () => {
491 assert.throws(() => {
492 ruleTester.run(
493 "suggestions-messageIds",
494 require("../../fixtures/testers/rule-tester/suggestions")
495 .withMessageIds,
496 {
497 valid: [],
498 invalid: [
499 {
500 code: "var foo;",
501 errors: 0
502 }
503 ]
504 }
505 );
506 }, /Invalid cases must have 'error' value greater than 0/u);
507 });
508
eb39fafa
DC
509 it("should not skip column assertion if column is a falsy value", () => {
510 assert.throws(() => {
511 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
512 valid: ["Eval(foo)"],
513 invalid: [{
514 code: "var foo; eval(foo)",
515 errors: [{ message: "eval sucks.", column: 0 }]
516 }]
517 });
518 }, /Error column should be 0/u);
519 });
520
521 it("should throw an error if invalid code specifies wrong endLine", () => {
522 assert.throws(() => {
523 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
524 valid: [
525 "bar = baz;"
526 ],
527 invalid: [
528 { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endLine: 10 }] }
529 ]
530 });
531 }, "Error endLine should be 10");
532 });
533
534 it("should throw an error if invalid code specifies wrong endColumn", () => {
535 assert.throws(() => {
536 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
537 valid: [
538 "bar = baz;"
539 ],
540 invalid: [
541 { code: "var foo = bar;", output: "foo = bar", errors: [{ message: "Bad var.", type: "VariableDeclaration", endColumn: 10 }] }
542 ]
543 });
544 }, "Error endColumn should be 10");
545 });
546
547 it("should throw an error if invalid code has the wrong number of errors", () => {
548 assert.throws(() => {
549 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
550 valid: [
551 "Eval(foo)"
552 ],
553 invalid: [
554 {
555 code: "eval(foo)",
556 errors: [
557 { message: "eval sucks.", type: "CallExpression" },
558 { message: "eval sucks.", type: "CallExpression" }
559 ]
560 }
561 ]
562 });
563 }, /Should have 2 errors but had 1/u);
564 });
565
566 it("should throw an error if invalid code does not have errors", () => {
567 assert.throws(() => {
568 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
569 valid: [
570 "Eval(foo)"
571 ],
572 invalid: [
573 { code: "eval(foo)" }
574 ]
575 });
576 }, /Did not specify errors for an invalid test of no-eval/u);
577 });
578
579 it("should throw an error if invalid code has the wrong explicit number of errors", () => {
580 assert.throws(() => {
581 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
582 valid: [
583 "Eval(foo)"
584 ],
585 invalid: [
586 { code: "eval(foo)", errors: 2 }
587 ]
588 });
589 }, /Should have 2 errors but had 1/u);
590 });
591
592 it("should throw an error if there's a parsing error in a valid test", () => {
593 assert.throws(() => {
594 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
595 valid: [
596 "1eval('foo')"
597 ],
598 invalid: [
599 { code: "eval('foo')", errors: [{}] }
600 ]
601 });
602 }, /fatal parsing error/iu);
603 });
604
605 it("should throw an error if there's a parsing error in an invalid test", () => {
606 assert.throws(() => {
607 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
608 valid: [
609 "noeval('foo')"
610 ],
611 invalid: [
612 { code: "1eval('foo')", errors: [{}] }
613 ]
614 });
615 }, /fatal parsing error/iu);
616 });
617
618 it("should throw an error if there's a parsing error in an invalid test and errors is just a number", () => {
619 assert.throws(() => {
620 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
621 valid: [
622 "noeval('foo')"
623 ],
624 invalid: [
625 { code: "1eval('foo')", errors: 1 }
626 ]
627 });
628 }, /fatal parsing error/iu);
629 });
630
631 // https://github.com/eslint/eslint/issues/4779
632 it("should throw an error if there's a parsing error and output doesn't match", () => {
633 assert.throws(() => {
634 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
635 valid: [],
636 invalid: [
637 { code: "eval(`foo`)", output: "eval(`foo`);", errors: [{}] }
638 ]
639 });
640 }, /fatal parsing error/iu);
641 });
642
643 it("should not throw an error if invalid code has at least an expected empty error object", () => {
644 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
645 valid: ["Eval(foo)"],
646 invalid: [{
647 code: "eval(foo)",
648 errors: [{}]
649 }]
650 });
651 });
652
653 it("should pass-through the globals config of valid tests to the to rule", () => {
654 ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
655 valid: [
656 "var test = 'foo'",
657 {
658 code: "var test2 = 'bar'",
659 globals: { test: true }
660 }
661 ],
662 invalid: [{ code: "bar", errors: 1 }]
663 });
664 });
665
666 it("should pass-through the globals config of invalid tests to the to rule", () => {
667 ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
668 valid: ["var test = 'foo'"],
669 invalid: [
670 {
671 code: "var test = 'foo'; var foo = 'bar'",
672 errors: 1
673 },
674 {
675 code: "var test = 'foo'",
676 globals: { foo: true },
677 errors: [{ message: "Global variable foo should not be used." }]
678 }
679 ]
680 });
681 });
682
683 it("should pass-through the settings config to rules", () => {
684 ruleTester.run("no-test-settings", require("../../fixtures/testers/rule-tester/no-test-settings"), {
685 valid: [
686 {
687 code: "var test = 'bar'", settings: { test: 1 }
688 }
689 ],
690 invalid: [
691 {
692 code: "var test = 'bar'", settings: { "no-test": 22 }, errors: 1
693 }
694 ]
695 });
696 });
697
698 it("should pass-through the filename to the rule", () => {
699 (function() {
700 ruleTester.run("", require("../../fixtures/testers/rule-tester/no-test-filename"), {
701 valid: [
702 {
703 code: "var foo = 'bar'",
704 filename: "somefile.js"
705 }
706 ],
707 invalid: [
708 {
709 code: "var foo = 'bar'",
710 errors: [
711 { message: "Filename test was not defined." }
712 ]
713 }
714 ]
715 });
716 }());
717 });
718
719 it("should pass-through the options to the rule", () => {
720 ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
721 valid: [
722 {
723 code: "var foo = 'bar'",
724 options: [false]
725 }
726 ],
727 invalid: [
728 {
729 code: "var foo = 'bar'",
730 options: [true],
731 errors: [{ message: "Invalid args" }]
732 }
733 ]
734 });
735 });
736
737 it("should throw an error if the options are an object", () => {
738 assert.throws(() => {
739 ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
740 valid: [
741 {
742 code: "foo",
743 options: { ok: true }
744 }
745 ],
746 invalid: []
747 });
748 }, /options must be an array/u);
749 });
750
751 it("should throw an error if the options are a number", () => {
752 assert.throws(() => {
753 ruleTester.run("no-invalid-args", require("../../fixtures/testers/rule-tester/no-invalid-args"), {
754 valid: [
755 {
756 code: "foo",
757 options: 0
758 }
759 ],
760 invalid: []
761 });
762 }, /options must be an array/u);
763 });
764
765 it("should pass-through the parser to the rule", () => {
766 const spy = sinon.spy(ruleTester.linter, "verify");
767
768 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
769 valid: [
770 {
771 code: "Eval(foo)"
772 }
773 ],
774 invalid: [
775 {
776 code: "eval(foo)",
777 parser: require.resolve("esprima"),
6f036462 778 errors: [{ line: 1 }]
eb39fafa
DC
779 }
780 ]
781 });
782 assert.strictEqual(spy.args[1][1].parser, require.resolve("esprima"));
783 });
784
785 it("should pass-through services from parseForESLint to the rule", () => {
786 const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser");
787 const disallowHiRule = {
788 create: context => ({
789 Literal(node) {
790 const disallowed = context.parserServices.test.getMessage(); // returns "Hi!"
791
792 if (node.value === disallowed) {
793 context.report({ node, message: `Don't use '${disallowed}'` });
794 }
795 }
796 })
797 };
798
799 ruleTester.run("no-hi", disallowHiRule, {
800 valid: [
801 {
802 code: "'Hello!'",
803 parser: enhancedParserPath
804 }
805 ],
806 invalid: [
807 {
808 code: "'Hi!'",
809 parser: enhancedParserPath,
810 errors: [{ message: "Don't use 'Hi!'" }]
811 }
812 ]
813 });
814 });
815
816 it("should prevent invalid options schemas", () => {
817 assert.throws(() => {
818 ruleTester.run("no-invalid-schema", require("../../fixtures/testers/rule-tester/no-invalid-schema"), {
819 valid: [
820 "var answer = 6 * 7;",
821 { code: "var answer = 6 * 7;", options: [] }
822 ],
823 invalid: [
824 { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected nothing." }] }
825 ]
826 });
827 }, "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");
828
829 });
830
831 it("should prevent schema violations in options", () => {
832 assert.throws(() => {
833 ruleTester.run("no-schema-violation", require("../../fixtures/testers/rule-tester/no-schema-violation"), {
834 valid: [
835 "var answer = 6 * 7;",
836 { code: "var answer = 6 * 7;", options: ["foo"] }
837 ],
838 invalid: [
839 { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected foo." }] }
840 ]
841 });
842 }, /Value "bar" should be equal to one of the allowed values./u);
843
844 });
845
846 it("should disallow invalid defaults in rules", () => {
847 const ruleWithInvalidDefaults = {
848 meta: {
849 schema: [
850 {
851 oneOf: [
852 { enum: ["foo"] },
853 {
854 type: "object",
855 properties: {
856 foo: {
857 enum: ["foo", "bar"],
858 default: "foo"
859 }
860 },
861 additionalProperties: false
862 }
863 ]
864 }
865 ]
866 },
867 create: () => ({})
868 };
869
870 assert.throws(() => {
871 ruleTester.run("invalid-defaults", ruleWithInvalidDefaults, {
872 valid: [
873 {
874 code: "foo",
875 options: [{}]
876 }
877 ],
878 invalid: []
879 });
880 }, /Schema for rule invalid-defaults is invalid: default is ignored for: data1\.foo/u);
881 });
882
883 it("throw an error when an unknown config option is included", () => {
884 assert.throws(() => {
885 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
886 valid: [
887 { code: "Eval(foo)", foo: "bar" }
888 ],
889 invalid: []
890 });
891 }, /ESLint configuration in rule-tester is invalid./u);
892 });
893
894 it("throw an error when an invalid config value is included", () => {
895 assert.throws(() => {
896 ruleTester.run("no-eval", require("../../fixtures/testers/rule-tester/no-eval"), {
897 valid: [
898 { code: "Eval(foo)", env: ["es6"] }
899 ],
900 invalid: []
901 });
902 }, /Property "env" is the wrong type./u);
903 });
904
905 it("should pass-through the tester config to the rule", () => {
906 ruleTester = new RuleTester({
907 globals: { test: true }
908 });
909
910 ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
911 valid: [
912 "var test = 'foo'",
913 "var test2 = test"
914 ],
915 invalid: [{ code: "bar", errors: 1, globals: { foo: true } }]
916 });
917 });
918
919 it("should correctly set the globals configuration", () => {
920 const config = { globals: { test: true } };
921
922 RuleTester.setDefaultConfig(config);
923 assert(
924 RuleTester.getDefaultConfig().globals.test,
925 "The default config object is incorrect"
926 );
927 });
928
929 it("should correctly reset the global configuration", () => {
930 const config = { globals: { test: true } };
931
932 RuleTester.setDefaultConfig(config);
933 RuleTester.resetDefaultConfig();
934 assert.deepStrictEqual(
935 RuleTester.getDefaultConfig(),
936 { rules: {} },
937 "The default configuration has not reset correctly"
938 );
939 });
940
941 it("should enforce the global configuration to be an object", () => {
942
943 /**
944 * Set the default config for the rules tester
945 * @param {Object} config configuration object
946 * @returns {Function} Function to be executed
947 * @private
948 */
949 function setConfig(config) {
950 return function() {
951 RuleTester.setDefaultConfig(config);
952 };
953 }
954 assert.throw(setConfig());
955 assert.throw(setConfig(1));
956 assert.throw(setConfig(3.14));
957 assert.throw(setConfig("foo"));
958 assert.throw(setConfig(null));
959 assert.throw(setConfig(true));
960 });
961
962 it("should pass-through the globals config to the tester then to the to rule", () => {
963 const config = { globals: { test: true } };
964
965 RuleTester.setDefaultConfig(config);
966 ruleTester = new RuleTester();
967
968 ruleTester.run("no-test-global", require("../../fixtures/testers/rule-tester/no-test-global"), {
969 valid: [
970 "var test = 'foo'",
971 "var test2 = test"
972 ],
973 invalid: [{ code: "bar", errors: 1, globals: { foo: true } }]
974 });
975 });
976
977 it("should throw an error if AST was modified", () => {
978 assert.throws(() => {
979 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), {
980 valid: [
981 "var foo = 0;"
982 ],
983 invalid: []
984 });
985 }, "Rule should not modify AST.");
986 assert.throws(() => {
987 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast"), {
988 valid: [],
989 invalid: [
990 { code: "var bar = 0;", errors: ["error"] }
991 ]
992 });
993 }, "Rule should not modify AST.");
994 });
995
996 it("should throw an error if AST was modified (at Program)", () => {
997 assert.throws(() => {
998 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), {
999 valid: [
1000 "var foo = 0;"
1001 ],
1002 invalid: []
1003 });
1004 }, "Rule should not modify AST.");
1005 assert.throws(() => {
1006 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-first"), {
1007 valid: [],
1008 invalid: [
1009 { code: "var bar = 0;", errors: ["error"] }
1010 ]
1011 });
1012 }, "Rule should not modify AST.");
1013 });
1014
1015 it("should throw an error if AST was modified (at Program:exit)", () => {
1016 assert.throws(() => {
1017 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1018 valid: [
1019 "var foo = 0;"
1020 ],
1021 invalid: []
1022 });
1023 }, "Rule should not modify AST.");
1024 assert.throws(() => {
1025 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1026 valid: [],
1027 invalid: [
1028 { code: "var bar = 0;", errors: ["error"] }
1029 ]
1030 });
1031 }, "Rule should not modify AST.");
1032 });
1033
1034 it("should throw an error if rule uses start and end properties on nodes, tokens or comments", () => {
1035 const usesStartEndRule = {
1036 create(context) {
1037
1038 const sourceCode = context.getSourceCode();
1039
1040 return {
1041 CallExpression(node) {
1042 noop(node.arguments[1].start);
1043 },
1044 "BinaryExpression[operator='+']"(node) {
1045 noop(node.end);
1046 },
1047 "UnaryExpression[operator='-']"(node) {
1048 noop(sourceCode.getFirstToken(node).start);
1049 },
1050 ConditionalExpression(node) {
1051 noop(sourceCode.getFirstToken(node).end);
1052 },
1053 BlockStatement(node) {
1054 noop(sourceCode.getCommentsInside(node)[0].start);
1055 },
1056 ObjectExpression(node) {
1057 noop(sourceCode.getCommentsInside(node)[0].end);
1058 },
1059 Decorator(node) {
1060 noop(node.start);
1061 }
1062 };
1063 }
1064 };
1065
1066 assert.throws(() => {
1067 ruleTester.run("uses-start-end", usesStartEndRule, {
1068 valid: ["foo(a, b)"],
1069 invalid: []
1070 });
1071 }, "Use node.range[0] instead of node.start");
1072 assert.throws(() => {
1073 ruleTester.run("uses-start-end", usesStartEndRule, {
1074 valid: [],
1075 invalid: [{ code: "var a = b * (c + d) / e;", errors: 1 }]
1076 });
1077 }, "Use node.range[1] instead of node.end");
1078 assert.throws(() => {
1079 ruleTester.run("uses-start-end", usesStartEndRule, {
1080 valid: [],
1081 invalid: [{ code: "var a = -b * c;", errors: 1 }]
1082 });
1083 }, "Use token.range[0] instead of token.start");
1084 assert.throws(() => {
1085 ruleTester.run("uses-start-end", usesStartEndRule, {
1086 valid: ["var a = b ? c : d;"],
1087 invalid: []
1088 });
1089 }, "Use token.range[1] instead of token.end");
1090 assert.throws(() => {
1091 ruleTester.run("uses-start-end", usesStartEndRule, {
1092 valid: ["function f() { /* comment */ }"],
1093 invalid: []
1094 });
1095 }, "Use token.range[0] instead of token.start");
1096 assert.throws(() => {
1097 ruleTester.run("uses-start-end", usesStartEndRule, {
1098 valid: [],
1099 invalid: [{ code: "var x = //\n {\n //comment\n //\n}", errors: 1 }]
1100 });
1101 }, "Use token.range[1] instead of token.end");
1102
1103 const enhancedParserPath = require.resolve("../../fixtures/parsers/enhanced-parser");
1104
1105 assert.throws(() => {
1106 ruleTester.run("uses-start-end", usesStartEndRule, {
1107 valid: [{ code: "foo(a, b)", parser: enhancedParserPath }],
1108 invalid: []
1109 });
1110 }, "Use node.range[0] instead of node.start");
1111 assert.throws(() => {
1112 ruleTester.run("uses-start-end", usesStartEndRule, {
1113 valid: [],
1114 invalid: [{ code: "var a = b * (c + d) / e;", parser: enhancedParserPath, errors: 1 }]
1115 });
1116 }, "Use node.range[1] instead of node.end");
1117 assert.throws(() => {
1118 ruleTester.run("uses-start-end", usesStartEndRule, {
1119 valid: [],
1120 invalid: [{ code: "var a = -b * c;", parser: enhancedParserPath, errors: 1 }]
1121 });
1122 }, "Use token.range[0] instead of token.start");
1123 assert.throws(() => {
1124 ruleTester.run("uses-start-end", usesStartEndRule, {
1125 valid: [{ code: "var a = b ? c : d;", parser: enhancedParserPath }],
1126 invalid: []
1127 });
1128 }, "Use token.range[1] instead of token.end");
1129 assert.throws(() => {
1130 ruleTester.run("uses-start-end", usesStartEndRule, {
1131 valid: [{ code: "function f() { /* comment */ }", parser: enhancedParserPath }],
1132 invalid: []
1133 });
1134 }, "Use token.range[0] instead of token.start");
1135 assert.throws(() => {
1136 ruleTester.run("uses-start-end", usesStartEndRule, {
1137 valid: [],
1138 invalid: [{ code: "var x = //\n {\n //comment\n //\n}", parser: enhancedParserPath, errors: 1 }]
1139 });
1140 }, "Use token.range[1] instead of token.end");
1141
1142 assert.throws(() => {
1143 ruleTester.run("uses-start-end", usesStartEndRule, {
1144 valid: [{ code: "@foo class A {}", parser: require.resolve("../../fixtures/parsers/enhanced-parser2") }],
1145 invalid: []
1146 });
1147 }, "Use node.range[0] instead of node.start");
1148 });
1149
1150 it("should throw an error if no test scenarios given", () => {
1151 assert.throws(() => {
1152 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"));
1153 }, "Test Scenarios for rule foo : Could not find test scenario object");
1154 });
1155
1156 it("should throw an error if no acceptable test scenario object is given", () => {
1157 assert.throws(() => {
1158 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), []);
1159 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios");
1160 assert.throws(() => {
1161 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), "");
1162 }, "Test Scenarios for rule foo : Could not find test scenario object");
1163 assert.throws(() => {
1164 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), 2);
1165 }, "Test Scenarios for rule foo : Could not find test scenario object");
1166 assert.throws(() => {
1167 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {});
1168 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios\nCould not find any invalid test scenarios");
1169 assert.throws(() => {
1170 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1171 valid: []
1172 });
1173 }, "Test Scenarios for rule foo is invalid:\nCould not find any invalid test scenarios");
1174 assert.throws(() => {
1175 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/modify-ast-at-last"), {
1176 invalid: []
1177 });
1178 }, "Test Scenarios for rule foo is invalid:\nCould not find any valid test scenarios");
1179 });
1180
1181 // Nominal message/messageId use cases
1182 it("should assert match if message provided in both test and result.", () => {
1183 assert.throws(() => {
1184 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
1185 valid: [],
1186 invalid: [{ code: "foo", errors: [{ message: "something" }] }]
1187 });
1188 }, /Avoid using variables named/u);
1189
1190 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
1191 valid: [],
1192 invalid: [{ code: "foo", errors: [{ message: "Avoid using variables named 'foo'." }] }]
1193 });
1194 });
1195
1196 it("should assert match between messageId if provided in both test and result.", () => {
1197 assert.throws(() => {
1198 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1199 valid: [],
1200 invalid: [{ code: "foo", errors: [{ messageId: "unused" }] }]
1201 });
1202 }, "messageId 'avoidFoo' does not match expected messageId 'unused'.");
1203
1204 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1205 valid: [],
1206 invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
1207 });
1208 });
1209 it("should assert match between resulting message output if messageId and data provided in both test and result", () => {
1210 assert.throws(() => {
1211 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1212 valid: [],
1213 invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo", data: { name: "notFoo" } }] }]
1214 });
1215 }, "Hydrated message \"Avoid using variables named 'notFoo'.\" does not match \"Avoid using variables named 'foo'.\"");
1216 });
1217
1218 // messageId/message misconfiguration cases
1219 it("should throw if user tests for both message and messageId", () => {
1220 assert.throws(() => {
1221 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1222 valid: [],
1223 invalid: [{ code: "foo", errors: [{ message: "something", messageId: "avoidFoo" }] }]
1224 });
1225 }, "Error should not specify both 'message' and a 'messageId'.");
1226 });
1227 it("should throw if user tests for messageId but the rule doesn't use the messageId meta syntax.", () => {
1228 assert.throws(() => {
1229 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMessageOnly, {
1230 valid: [],
1231 invalid: [{ code: "foo", errors: [{ messageId: "avoidFoo" }] }]
1232 });
1233 }, "Error can not use 'messageId' if rule under test doesn't define 'meta.messages'");
1234 });
1235 it("should throw if user tests for messageId not listed in the rule's meta syntax.", () => {
1236 assert.throws(() => {
1237 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1238 valid: [],
1239 invalid: [{ code: "foo", errors: [{ messageId: "useFoo" }] }]
1240 });
1241 }, /Invalid messageId 'useFoo'/u);
1242 });
1243 it("should throw if data provided without messageId.", () => {
1244 assert.throws(() => {
1245 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/messageId").withMetaWithData, {
1246 valid: [],
1247 invalid: [{ code: "foo", errors: [{ data: "something" }] }]
1248 });
1249 }, "Error must specify 'messageId' if 'data' is used.");
1250 });
1251
6f036462
TL
1252 // fixable rules with or without `meta` property
1253 it("should not throw an error if a rule that has `meta.fixable` produces fixes", () => {
1254 const replaceProgramWith5Rule = {
1255 meta: {
1256 fixable: "code"
1257 },
1258 create(context) {
1259 return {
1260 Program(node) {
1261 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
1262 }
1263 };
1264 }
1265 };
1266
1267 ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
1268 valid: [],
1269 invalid: [
1270 { code: "var foo = bar;", output: "5", errors: 1 }
1271 ]
1272 });
1273 });
1274 it("should throw an error if a new-format rule that doesn't have `meta` produces fixes", () => {
1275 const replaceProgramWith5Rule = {
1276 create(context) {
1277 return {
1278 Program(node) {
1279 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
1280 }
1281 };
1282 }
1283 };
1284
1285 assert.throws(() => {
1286 ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
1287 valid: [],
1288 invalid: [
1289 { code: "var foo = bar;", output: "5", errors: 1 }
1290 ]
1291 });
1292 }, "Fixable rules should export a `meta.fixable` property.");
1293 });
1294 it("should throw an error if a legacy-format rule produces fixes", () => {
1295
1296 /**
1297 * Legacy-format rule (a function instead of an object with `create` method).
1298 * @param {RuleContext} context The ESLint rule context object.
1299 * @returns {Object} Listeners.
1300 */
1301 function replaceProgramWith5Rule(context) {
1302 return {
1303 Program(node) {
1304 context.report({ node, message: "bad", fix: fixer => fixer.replaceText(node, "5") });
1305 }
1306 };
1307 }
1308
1309 assert.throws(() => {
1310 ruleTester.run("replaceProgramWith5", replaceProgramWith5Rule, {
1311 valid: [],
1312 invalid: [
1313 { code: "var foo = bar;", output: "5", errors: 1 }
1314 ]
1315 });
1316 }, "Fixable rules should export a `meta.fixable` property.");
1317 });
1318
eb39fafa
DC
1319 describe("suggestions", () => {
1320 it("should pass with valid suggestions (tested using desc)", () => {
1321 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1322 valid: [
1323 "var boo;"
1324 ],
1325 invalid: [{
1326 code: "var foo;",
1327 errors: [{
1328 suggestions: [{
1329 desc: "Rename identifier 'foo' to 'bar'",
1330 output: "var bar;"
1331 }]
1332 }]
1333 }]
1334 });
1335 });
1336
1337 it("should pass with suggestions on multiple lines", () => {
1338 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1339 valid: [],
1340 invalid: [
1341 {
1342 code: "function foo() {\n var foo = 1;\n}",
1343 errors: [{
1344 suggestions: [{
1345 desc: "Rename identifier 'foo' to 'bar'",
1346 output: "function bar() {\n var foo = 1;\n}"
1347 }]
1348 }, {
1349 suggestions: [{
1350 desc: "Rename identifier 'foo' to 'bar'",
1351 output: "function foo() {\n var bar = 1;\n}"
1352 }]
1353 }]
1354 }
1355 ]
1356 });
1357 });
1358
1359 it("should pass with valid suggestions (tested using messageIds)", () => {
1360 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1361 valid: [],
1362 invalid: [{
1363 code: "var foo;",
1364 errors: [{
1365 suggestions: [{
1366 messageId: "renameFoo",
1367 output: "var bar;"
1368 }, {
1369 messageId: "renameFoo",
1370 output: "var baz;"
1371 }]
1372 }]
1373 }]
1374 });
1375 });
1376
1377 it("should pass with valid suggestions (one tested using messageIds, the other using desc)", () => {
1378 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1379 valid: [],
1380 invalid: [{
1381 code: "var foo;",
1382 errors: [{
1383 suggestions: [{
1384 messageId: "renameFoo",
1385 output: "var bar;"
1386 }, {
1387 desc: "Rename identifier 'foo' to 'baz'",
1388 output: "var baz;"
1389 }]
1390 }]
1391 }]
1392 });
1393 });
1394
1395 it("should pass with valid suggestions (tested using both desc and messageIds for the same suggestion)", () => {
1396 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1397 valid: [],
1398 invalid: [{
1399 code: "var foo;",
1400 errors: [{
1401 suggestions: [{
1402 desc: "Rename identifier 'foo' to 'bar'",
1403 messageId: "renameFoo",
1404 output: "var bar;"
1405 }, {
1406 desc: "Rename identifier 'foo' to 'baz'",
1407 messageId: "renameFoo",
1408 output: "var baz;"
1409 }]
1410 }]
1411 }]
1412 });
1413 });
1414
1415 it("should pass with valid suggestions (tested using only desc on a rule that utilizes meta.messages)", () => {
1416 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1417 valid: [],
1418 invalid: [{
1419 code: "var foo;",
1420 errors: [{
1421 suggestions: [{
1422 desc: "Rename identifier 'foo' to 'bar'",
1423 output: "var bar;"
1424 }, {
1425 desc: "Rename identifier 'foo' to 'baz'",
1426 output: "var baz;"
1427 }]
1428 }]
1429 }]
1430 });
1431 });
1432
1433 it("should pass with valid suggestions (tested using messageIds and data)", () => {
1434 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1435 valid: [],
1436 invalid: [{
1437 code: "var foo;",
1438 errors: [{
1439 suggestions: [{
1440 messageId: "renameFoo",
1441 data: { newName: "bar" },
1442 output: "var bar;"
1443 }, {
1444 messageId: "renameFoo",
1445 data: { newName: "baz" },
1446 output: "var baz;"
1447 }]
1448 }]
1449 }]
1450 });
1451 });
1452
1453
1454 it("should pass when tested using empty suggestion test objects if the array length is correct", () => {
1455 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1456 valid: [],
1457 invalid: [{
1458 code: "var foo;",
1459 errors: [{
1460 suggestions: [{}, {}]
1461 }]
1462 }]
1463 });
1464 });
1465
1466 it("should support explicitly expecting no suggestions", () => {
1467 [void 0, null, false, []].forEach(suggestions => {
1468 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/no-eval"), {
1469 valid: [],
1470 invalid: [{
1471 code: "eval('var foo');",
1472 errors: [{
1473 suggestions
1474 }]
1475 }]
1476 });
1477 });
1478 });
1479
1480 it("should fail when expecting no suggestions and there are suggestions", () => {
1481 [void 0, null, false, []].forEach(suggestions => {
1482 assert.throws(() => {
1483 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1484 valid: [],
1485 invalid: [{
1486 code: "var foo;",
1487 errors: [{
1488 suggestions
1489 }]
1490 }]
1491 });
1492 }, "Error should have no suggestions on error with message: \"Avoid using identifiers named 'foo'.\"");
1493 });
1494 });
1495
1496 it("should fail when testing for suggestions that don't exist", () => {
1497 assert.throws(() => {
1498 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
1499 valid: [],
1500 invalid: [{
1501 code: "var foo;",
1502 errors: [{
1503 suggestions: [{
1504 messageId: "this-does-not-exist"
1505 }]
1506 }]
1507 }]
1508 });
1509 }, "Error should have an array of suggestions. Instead received \"undefined\" on error with message: \"Bad var.\"");
1510 });
1511
1512 it("should fail when there are a different number of suggestions", () => {
1513 assert.throws(() => {
1514 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1515 valid: [],
1516 invalid: [{
1517 code: "var foo;",
1518 errors: [{
1519 suggestions: [{
1520 desc: "Rename identifier 'foo' to 'bar'",
1521 output: "var bar;"
1522 }, {
1523 desc: "Rename identifier 'foo' to 'baz'",
1524 output: "var baz;"
1525 }]
1526 }]
1527 }]
1528 });
1529 }, "Error should have 2 suggestions. Instead found 1 suggestions");
1530 });
1531
1532 it("should throw if the suggestion description doesn't match", () => {
1533 assert.throws(() => {
1534 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1535 valid: [],
1536 invalid: [{
1537 code: "var foo;",
1538 errors: [{
1539 suggestions: [{
1540 desc: "not right",
1541 output: "var baz;"
1542 }]
1543 }]
1544 }]
1545 });
1546 }, "Error Suggestion at index 0 : desc should be \"not right\" but got \"Rename identifier 'foo' to 'bar'\" instead.");
1547 });
1548
1549 it("should throw if the suggestion description doesn't match (although messageIds match)", () => {
1550 assert.throws(() => {
1551 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1552 valid: [],
1553 invalid: [{
1554 code: "var foo;",
1555 errors: [{
1556 suggestions: [{
1557 desc: "Rename identifier 'foo' to 'bar'",
1558 messageId: "renameFoo",
1559 output: "var bar;"
1560 }, {
1561 desc: "Rename id 'foo' to 'baz'",
1562 messageId: "renameFoo",
1563 output: "var baz;"
1564 }]
1565 }]
1566 }]
1567 });
1568 }, "Error Suggestion at index 1 : desc should be \"Rename id 'foo' to 'baz'\" but got \"Rename identifier 'foo' to 'baz'\" instead.");
1569 });
1570
1571 it("should throw if the suggestion messageId doesn't match", () => {
1572 assert.throws(() => {
1573 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1574 valid: [],
1575 invalid: [{
1576 code: "var foo;",
1577 errors: [{
1578 suggestions: [{
1579 messageId: "unused",
1580 output: "var bar;"
1581 }, {
1582 messageId: "renameFoo",
1583 output: "var baz;"
1584 }]
1585 }]
1586 }]
1587 });
1588 }, "Error Suggestion at index 0 : messageId should be 'unused' but got 'renameFoo' instead.");
1589 });
1590
1591 it("should throw if the suggestion messageId doesn't match (although descriptions match)", () => {
1592 assert.throws(() => {
1593 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1594 valid: [],
1595 invalid: [{
1596 code: "var foo;",
1597 errors: [{
1598 suggestions: [{
1599 desc: "Rename identifier 'foo' to 'bar'",
1600 messageId: "renameFoo",
1601 output: "var bar;"
1602 }, {
1603 desc: "Rename identifier 'foo' to 'baz'",
1604 messageId: "avoidFoo",
1605 output: "var baz;"
1606 }]
1607 }]
1608 }]
1609 });
1610 }, "Error Suggestion at index 1 : messageId should be 'avoidFoo' but got 'renameFoo' instead.");
1611 });
1612
1613 it("should throw if test specifies messageId for a rule that doesn't have meta.messages", () => {
1614 assert.throws(() => {
1615 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1616 valid: [],
1617 invalid: [{
1618 code: "var foo;",
1619 errors: [{
1620 suggestions: [{
1621 messageId: "renameFoo",
1622 output: "var bar;"
1623 }]
1624 }]
1625 }]
1626 });
1627 }, "Error Suggestion at index 0 : Test can not use 'messageId' if rule under test doesn't define 'meta.messages'.");
1628 });
1629
1630 it("should throw if test specifies messageId that doesn't exist in the rule's meta.messages", () => {
1631 assert.throws(() => {
1632 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1633 valid: [],
1634 invalid: [{
1635 code: "var foo;",
1636 errors: [{
1637 suggestions: [{
1638 messageId: "renameFoo",
1639 output: "var bar;"
1640 }, {
1641 messageId: "removeFoo",
1642 output: "var baz;"
1643 }]
1644 }]
1645 }]
1646 });
1647 }, "Error Suggestion at index 1 : Test has invalid messageId 'removeFoo', the rule under test allows only one of ['avoidFoo', 'unused', 'renameFoo'].");
1648 });
1649
1650 it("should throw if hydrated desc doesn't match (wrong data value)", () => {
1651 assert.throws(() => {
1652 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1653 valid: [],
1654 invalid: [{
1655 code: "var foo;",
1656 errors: [{
1657 suggestions: [{
1658 messageId: "renameFoo",
1659 data: { newName: "car" },
1660 output: "var bar;"
1661 }, {
1662 messageId: "renameFoo",
1663 data: { newName: "baz" },
1664 output: "var baz;"
1665 }]
1666 }]
1667 }]
1668 });
1669 }, "Error Suggestion at index 0 : Hydrated test desc \"Rename identifier 'foo' to 'car'\" does not match received desc \"Rename identifier 'foo' to 'bar'\".");
1670 });
1671
1672 it("should throw if hydrated desc doesn't match (wrong data key)", () => {
1673 assert.throws(() => {
1674 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1675 valid: [],
1676 invalid: [{
1677 code: "var foo;",
1678 errors: [{
1679 suggestions: [{
1680 messageId: "renameFoo",
1681 data: { newName: "bar" },
1682 output: "var bar;"
1683 }, {
1684 messageId: "renameFoo",
1685 data: { name: "baz" },
1686 output: "var baz;"
1687 }]
1688 }]
1689 }]
1690 });
1691 }, "Error Suggestion at index 1 : Hydrated test desc \"Rename identifier 'foo' to '{{ newName }}'\" does not match received desc \"Rename identifier 'foo' to 'baz'\".");
1692 });
1693
1694 it("should throw if test specifies both desc and data", () => {
1695 assert.throws(() => {
1696 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1697 valid: [],
1698 invalid: [{
1699 code: "var foo;",
1700 errors: [{
1701 suggestions: [{
1702 desc: "Rename identifier 'foo' to 'bar'",
1703 messageId: "renameFoo",
1704 data: { newName: "bar" },
1705 output: "var bar;"
1706 }, {
1707 messageId: "renameFoo",
1708 data: { newName: "baz" },
1709 output: "var baz;"
1710 }]
1711 }]
1712 }]
1713 });
1714 }, "Error Suggestion at index 0 : Test should not specify both 'desc' and 'data'.");
1715 });
1716
1717 it("should throw if test uses data but doesn't specify messageId", () => {
1718 assert.throws(() => {
1719 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1720 valid: [],
1721 invalid: [{
1722 code: "var foo;",
1723 errors: [{
1724 suggestions: [{
1725 messageId: "renameFoo",
1726 data: { newName: "bar" },
1727 output: "var bar;"
1728 }, {
1729 data: { newName: "baz" },
1730 output: "var baz;"
1731 }]
1732 }]
1733 }]
1734 });
1735 }, "Error Suggestion at index 1 : Test must specify 'messageId' if 'data' is used.");
1736 });
1737
1738 it("should throw if the resulting suggestion output doesn't match", () => {
1739 assert.throws(() => {
1740 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1741 valid: [],
1742 invalid: [{
1743 code: "var foo;",
1744 errors: [{
1745 suggestions: [{
1746 desc: "Rename identifier 'foo' to 'bar'",
1747 output: "var baz;"
1748 }]
1749 }]
1750 }]
1751 });
1752 }, "Expected the applied suggestion fix to match the test suggestion output");
1753 });
1754
1755 it("should fail when specified suggestion isn't an object", () => {
1756 assert.throws(() => {
1757 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1758 valid: [],
1759 invalid: [{
1760 code: "var foo;",
1761 errors: [{
1762 suggestions: [null]
1763 }]
1764 }]
1765 });
1766 }, "Test suggestion in 'suggestions' array must be an object.");
1767
1768 assert.throws(() => {
1769 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1770 valid: [],
1771 invalid: [{
1772 code: "var foo;",
1773 errors: [{
1774 suggestions: [
1775 {
1776 messageId: "renameFoo",
1777 output: "var bar;"
1778 },
1779 "Rename identifier 'foo' to 'baz'"
1780 ]
1781 }]
1782 }]
1783 });
1784 }, "Test suggestion in 'suggestions' array must be an object.");
1785 });
1786
1787 it("should fail when the suggestion is an object with an unknown property name", () => {
1788 assert.throws(() => {
1789 ruleTester.run("suggestions-basic", require("../../fixtures/testers/rule-tester/suggestions").basic, {
1790 valid: [
1791 "var boo;"
1792 ],
1793 invalid: [{
1794 code: "var foo;",
1795 errors: [{
1796 suggestions: [{
1797 message: "Rename identifier 'foo' to 'bar'"
1798 }]
1799 }]
1800 }]
1801 });
1802 }, /Invalid suggestion property name 'message'/u);
1803 });
1804
1805 it("should fail when any of the suggestions is an object with an unknown property name", () => {
1806 assert.throws(() => {
1807 ruleTester.run("suggestions-messageIds", require("../../fixtures/testers/rule-tester/suggestions").withMessageIds, {
1808 valid: [],
1809 invalid: [{
1810 code: "var foo;",
1811 errors: [{
1812 suggestions: [{
1813 messageId: "renameFoo",
1814 output: "var bar;"
1815 }, {
1816 messageId: "renameFoo",
1817 outpt: "var baz;"
1818 }]
1819 }]
1820 }]
1821 });
1822 }, /Invalid suggestion property name 'outpt'/u);
1823 });
1824 });
1825
1826 describe("naming test cases", () => {
1827
1828 /**
1829 * Asserts that a particular value will be emitted from an EventEmitter.
1830 * @param {EventEmitter} emitter The emitter that should emit a value
1831 * @param {string} emitType The type of emission to listen for
1832 * @param {*} expectedValue The value that should be emitted
1833 * @returns {Promise} A Promise that fulfills if the value is emitted, and rejects if something else is emitted.
1834 * The Promise will be indefinitely pending if no value is emitted.
1835 */
1836 function assertEmitted(emitter, emitType, expectedValue) {
1837 return new Promise((resolve, reject) => {
1838 emitter.once(emitType, emittedValue => {
1839 if (emittedValue === expectedValue) {
1840 resolve();
1841 } else {
1842 reject(new Error(`Expected ${expectedValue} to be emitted but ${emittedValue} was emitted instead.`));
1843 }
1844 });
1845 });
1846 }
1847
1848 it("should use the first argument as the name of the test suite", () => {
1849 const assertion = assertEmitted(ruleTesterTestEmitter, "describe", "this-is-a-rule-name");
1850
1851 ruleTester.run("this-is-a-rule-name", require("../../fixtures/testers/rule-tester/no-var"), {
1852 valid: [],
1853 invalid: []
1854 });
1855
1856 return assertion;
1857 });
1858
1859 it("should use the test code as the name of the tests for valid code (string form)", () => {
1860 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);");
1861
1862 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
1863 valid: [
1864 "valid(code);"
1865 ],
1866 invalid: []
1867 });
1868
1869 return assertion;
1870 });
1871
1872 it("should use the test code as the name of the tests for valid code (object form)", () => {
1873 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "valid(code);");
1874
1875 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
1876 valid: [
1877 {
1878 code: "valid(code);"
1879 }
1880 ],
1881 invalid: []
1882 });
1883
1884 return assertion;
1885 });
1886
1887 it("should use the test code as the name of the tests for invalid code", () => {
1888 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "var x = invalid(code);");
1889
1890 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
1891 valid: [],
1892 invalid: [
1893 {
1894 code: "var x = invalid(code);",
1895 output: " x = invalid(code);",
1896 errors: 1
1897 }
1898 ]
1899 });
1900
1901 return assertion;
1902 });
1903
1904 // https://github.com/eslint/eslint/issues/8142
1905 it("should use the empty string as the name of the test if the test case is an empty string", () => {
1906 const assertion = assertEmitted(ruleTesterTestEmitter, "it", "");
1907
1908 ruleTester.run("foo", require("../../fixtures/testers/rule-tester/no-var"), {
1909 valid: [
1910 {
1911 code: ""
1912 }
1913 ],
1914 invalid: []
1915 });
1916
1917 return assertion;
1918 });
1919 });
1920
1921 // https://github.com/eslint/eslint/issues/11615
1922 it("should fail the case if autofix made a syntax error.", () => {
1923 assert.throw(() => {
1924 ruleTester.run(
1925 "foo",
1926 context => ({
1927 Identifier(node) {
1928 context.report({
1929 node,
1930 message: "make a syntax error",
1931 fix(fixer) {
1932 return fixer.replaceText(node, "one two");
1933 }
1934 });
1935 }
1936 }),
1937 {
1938 valid: ["one()"],
1939 invalid: []
1940 }
1941 );
56c4a2cb 1942 }, /A fatal parsing error occurred in autofix.\nError: .+\nAutofix output:\n.+/u);
eb39fafa
DC
1943 });
1944
1945 describe("sanitize test cases", () => {
1946 let originalRuleTesterIt;
1947 let spyRuleTesterIt;
1948
1949 before(() => {
1950 originalRuleTesterIt = RuleTester.it;
1951 spyRuleTesterIt = sinon.spy();
1952 RuleTester.it = spyRuleTesterIt;
1953 });
1954 after(() => {
1955 RuleTester.it = originalRuleTesterIt;
1956 });
1957 beforeEach(() => {
1958 spyRuleTesterIt.resetHistory();
1959 ruleTester = new RuleTester();
1960 });
1961 it("should present newline when using back-tick as new line", () => {
1962 const code = `
1963 var foo = bar;`;
1964
1965 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
1966 valid: [],
1967 invalid: [
1968 {
1969 code,
1970 errors: [/^Bad var/u]
1971 }
1972 ]
1973 });
1974 sinon.assert.calledWith(spyRuleTesterIt, code);
1975 });
1976 it("should present \\u0000 as a string", () => {
1977 const code = "\u0000";
1978
1979 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
1980 valid: [],
1981 invalid: [
1982 {
1983 code,
1984 errors: [/^Bad var/u]
1985 }
1986 ]
1987 });
1988 sinon.assert.calledWith(spyRuleTesterIt, "\\u0000");
1989 });
1990 it("should present the pipe character correctly", () => {
1991 const code = "var foo = bar || baz;";
1992
1993 ruleTester.run("no-var", require("../../fixtures/testers/rule-tester/no-var"), {
1994 valid: [],
1995 invalid: [
1996 {
1997 code,
1998 errors: [/^Bad var/u]
1999 }
2000 ]
2001 });
2002 sinon.assert.calledWith(spyRuleTesterIt, code);
2003 });
2004
2005 });
6f036462 2006
eb39fafa 2007});