2 * @fileoverview Tests for ConfigArray class.
3 * @author Toru Nagashima <https://github.com/mysticatea>
7 const path
= require("path");
8 const { assert
} = require("chai");
9 const { ConfigArray
, OverrideTester
, getUsedExtractedConfigs
} = require("../../../../lib/cli-engine/config-array");
11 describe("ConfigArray", () => {
12 it("should be a sub class of Array.", () => {
13 assert(new ConfigArray() instanceof Array
);
16 describe("'constructor(...elements)' should adopt the elements as array elements.", () => {
19 { elements
: [{ value
: 1 }] },
20 { elements
: [{ value
: 2 }, { value
: 3 }] },
21 { elements
: [{ value
: 4 }, { value
: 5 }, { value
: 6 }] }
24 for (const { elements
} of patterns
) {
25 describe(`if it gave ${JSON.stringify(elements)} then`, () => {
29 configArray
= new ConfigArray(...elements
);
32 it(`should have ${elements.length} as the length.`, () => {
33 assert
.strictEqual(configArray
.length
, elements
.length
);
36 for (let i
= 0; i
< elements
.length
; ++i
) {
37 it(`should have ${JSON.stringify(elements[i])} at configArray[${i}].`, () => { // eslint-disable-line no-loop-func
38 assert
.strictEqual(configArray
[i
], elements
[i
]);
45 describe("'isRoot()' method should be the value of the last element which has 'root' property.", () => {
47 { elements
: [], expected
: false },
48 { elements
: [{}], expected
: false },
49 { elements
: [{}, {}], expected
: false },
50 { elements
: [{ root
: false }], expected
: false },
51 { elements
: [{ root
: true }], expected
: true },
52 { elements
: [{ root
: true }, { root
: false }], expected
: false },
53 { elements
: [{ root
: false }, { root
: true }], expected
: true },
54 { elements
: [{ root
: false }, { root
: true }, { rules
: {} }], expected
: true }, // ignore undefined.
55 { elements
: [{ root
: true }, { root
: 1 }], expected
: true } // ignore non-boolean value
58 for (const { elements
, expected
} of patterns
) {
59 it(`should be ${expected} if the elements are ${JSON.stringify(elements)}.`, () => {
60 assert
.strictEqual(new ConfigArray(...elements
).isRoot(), expected
);
65 describe("'pluginEnvironments' property should be the environments of all plugins.", () => {
73 configArray
= new ConfigArray(
86 criteria
: OverrideTester
.create(["*.ts"], [], process
.cwd()),
100 it("should return null for built-in env", () => {
101 assert
.strictEqual(configArray
.pluginEnvironments
.get("node"), void 0);
104 it("should return 'aaa/xxx' if it exists.", () => {
105 assert
.strictEqual(configArray
.pluginEnvironments
.get("aaa/xxx"), env
["aaa/xxx"]);
108 it("should return 'bbb/xxx' if it exists.", () => {
109 assert
.strictEqual(configArray
.pluginEnvironments
.get("bbb/xxx"), env
["bbb/xxx"]);
112 it("should throw an error if it tried to mutate.", () => {
113 assert
.throws(() => {
114 configArray
.pluginEnvironments
.set("ccc/xxx", {});
119 describe("'pluginProcessors' property should be the processors of all plugins.", () => {
127 configArray
= new ConfigArray(
133 ".xxx": processors
["aaa/.xxx"]
140 criteria
: OverrideTester
.create(["*.ts"], [], process
.cwd()),
145 ".xxx": processors
["bbb/.xxx"]
154 it("should return 'aaa/.xxx' if it exists.", () => {
155 assert
.strictEqual(configArray
.pluginProcessors
.get("aaa/.xxx"), processors
["aaa/.xxx"]);
158 it("should return 'bbb/.xxx' if it exists.", () => {
159 assert
.strictEqual(configArray
.pluginProcessors
.get("bbb/.xxx"), processors
["bbb/.xxx"]);
162 it("should throw an error if it tried to mutate.", () => {
163 assert
.throws(() => {
164 configArray
.pluginProcessors
.set("ccc/.xxx", {});
169 describe("'pluginRules' property should be the rules of all plugins.", () => {
177 configArray
= new ConfigArray(
183 xxx
: rules
["aaa/xxx"]
190 criteria
: OverrideTester
.create(["*.ts"], [], process
.cwd()),
195 xxx
: rules
["bbb/xxx"]
204 it("should return null for built-in rules", () => {
205 assert
.strictEqual(configArray
.pluginRules
.get("eqeqeq"), void 0);
208 it("should return 'aaa/xxx' if it exists.", () => {
209 assert
.strictEqual(configArray
.pluginRules
.get("aaa/xxx"), rules
["aaa/xxx"]);
212 it("should return 'bbb/xxx' if it exists.", () => {
213 assert
.strictEqual(configArray
.pluginRules
.get("bbb/xxx"), rules
["bbb/xxx"]);
216 it("should throw an error if it tried to mutate.", () => {
217 assert
.throws(() => {
218 configArray
.pluginRules
.set("ccc/xxx", {});
223 describe("'extractConfig(filePath)' method should retrieve the merged config for a given file.", () => {
224 it("should throw an error if a 'parser' has the loading error.", () => {
225 assert
.throws(() => {
228 parser
: { error
: new Error("Failed to load a parser.") }
230 ).extractConfig(__filename
);
231 }, "Failed to load a parser.");
234 it("should not throw if the errored 'parser' was not used; overwriten", () => {
235 const parser
= { id
: "a parser" };
236 const config
= new ConfigArray(
238 parser
: { error
: new Error("Failed to load a parser.") }
243 ).extractConfig(__filename
);
245 assert
.strictEqual(config
.parser
, parser
);
248 it("should not throw if the errored 'parser' was not used; not matched", () => {
249 const config
= new ConfigArray(
251 criteria
: OverrideTester
.create(["*.ts"], [], process
.cwd()),
252 parser
: { error
: new Error("Failed to load a parser.") }
254 ).extractConfig(__filename
);
256 assert
.strictEqual(config
.parser
, null);
259 it("should throw an error if a 'plugins' value has the loading error.", () => {
260 assert
.throws(() => {
264 foo
: { error
: new Error("Failed to load a plugin.") }
267 ).extractConfig(__filename
);
268 }, "Failed to load a plugin.");
271 it("should not throw if the errored 'plugins' value was not used; not matched", () => {
272 const config
= new ConfigArray(
274 criteria
: OverrideTester
.create(["*.ts"], [], process
.cwd()),
276 foo
: { error
: new Error("Failed to load a plugin.") }
279 ).extractConfig(__filename
);
281 assert
.deepStrictEqual(config
.plugins
, {});
284 it("should not merge the elements which were not matched.", () => {
285 const config
= new ConfigArray(
288 "no-redeclare": "error"
292 criteria
: OverrideTester
.create(["*.js"], [], process
.cwd()),
298 criteria
: OverrideTester
.create(["*.js"], [path
.basename(__filename
)], process
.cwd()),
300 "no-use-before-define": "error"
304 criteria
: OverrideTester
.create(["*.ts"], [], process
.cwd()),
306 "no-unused-vars": "error"
309 ).extractConfig(__filename
);
311 assert
.deepStrictEqual(config
.rules
, {
312 "no-redeclare": ["error"],
313 "no-undef": ["error"]
317 it("should return the same instance for every the same matching.", () => {
318 const configArray
= new ConfigArray(
321 "no-redeclare": "error"
325 criteria
: OverrideTester
.create(["*.js"], [], process
.cwd()),
331 criteria
: OverrideTester
.create(["*.js"], [path
.basename(__filename
)], process
.cwd()),
333 "no-use-before-define": "error"
337 criteria
: OverrideTester
.create(["*.ts"], [], process
.cwd()),
339 "no-unused-vars": "error"
345 configArray
.extractConfig(path
.join(__dirname
, "a.js")),
346 configArray
.extractConfig(path
.join(__dirname
, "b.js"))
351 * Merge two config data.
353 * The test cases which depend on this function were moved from
354 * 'tests/lib/config/config-ops.js' when refactoring to keep the
355 * cumulated test cases.
357 * Previously, the merging logic of multiple config data had been
358 * implemented in `ConfigOps.merge()` function. But currently, it's
359 * implemented in `ConfigArray#extractConfig()` method.
360 * @param {Object} target A config data.
361 * @param {Object} source Another config data.
362 * @returns {Object} The merged config data.
364 function merge(target
, source
) {
365 return new ConfigArray(target
, source
).extractConfig(__filename
);
368 it("should combine two objects when passed two objects with different top-level properties", () => {
370 { env
: { browser
: true } },
371 { globals
: { foo
: "bar" } }
374 const result
= merge(config
[0], config
[1]);
376 assert
.strictEqual(result
.globals
.foo
, "bar");
377 assert
.isTrue(result
.env
.browser
);
380 it("should combine without blowing up on null values", () => {
382 { env
: { browser
: true } },
383 { env
: { node
: null } }
386 const result
= merge(config
[0], config
[1]);
388 assert
.strictEqual(result
.env
.node
, null);
389 assert
.isTrue(result
.env
.browser
);
392 it("should combine two objects with parser when passed two objects with different top-level properties", () => {
394 { env
: { browser
: true }, parser
: "espree" },
395 { globals
: { foo
: "bar" } }
398 const result
= merge(config
[0], config
[1]);
400 assert
.strictEqual(result
.parser
, "espree");
403 it("should combine configs and override rules when passed configs with the same rules", () => {
405 { rules
: { "no-mixed-requires": [0, false] } },
406 { rules
: { "no-mixed-requires": [1, true] } }
409 const result
= merge(config
[0], config
[1]);
411 assert
.isArray(result
.rules
["no-mixed-requires"]);
412 assert
.strictEqual(result
.rules
["no-mixed-requires"][0], 1);
413 assert
.strictEqual(result
.rules
["no-mixed-requires"][1], true);
416 it("should combine configs when passed configs with parserOptions", () => {
418 { parserOptions
: { ecmaFeatures
: { jsx
: true } } },
419 { parserOptions
: { ecmaFeatures
: { globalReturn
: true } } }
422 const result
= merge(config
[0], config
[1]);
424 assert
.deepStrictEqual(result
, {
425 configNameOfNoInlineConfig
: "",
429 noInlineConfig
: void 0,
439 reportUnusedDisableDirectives
: void 0,
444 // double-check that originals were not changed
445 assert
.deepStrictEqual(config
[0], { parserOptions
: { ecmaFeatures
: { jsx
: true } } });
446 assert
.deepStrictEqual(config
[1], { parserOptions
: { ecmaFeatures
: { globalReturn
: true } } });
449 it("should override configs when passed configs with the same ecmaFeatures", () => {
451 { parserOptions
: { ecmaFeatures
: { globalReturn
: false } } },
452 { parserOptions
: { ecmaFeatures
: { globalReturn
: true } } }
455 const result
= merge(config
[0], config
[1]);
457 assert
.deepStrictEqual(result
, {
458 configNameOfNoInlineConfig
: "",
462 noInlineConfig
: void 0,
471 reportUnusedDisableDirectives
: void 0,
477 it("should combine configs and override rules when merging two configs with arrays and int", () => {
480 { rules
: { "no-mixed-requires": [0, false] } },
481 { rules
: { "no-mixed-requires": 1 } }
484 const result
= merge(config
[0], config
[1]);
486 assert
.isArray(result
.rules
["no-mixed-requires"]);
487 assert
.strictEqual(result
.rules
["no-mixed-requires"][0], 1);
488 assert
.strictEqual(result
.rules
["no-mixed-requires"][1], false);
489 assert
.deepStrictEqual(config
[0], { rules
: { "no-mixed-requires": [0, false] } });
490 assert
.deepStrictEqual(config
[1], { rules
: { "no-mixed-requires": 1 } });
493 it("should combine configs and override rules options completely", () => {
496 { rules
: { "no-mixed-requires1": [1, { event
: ["evt", "e"] }] } },
497 { rules
: { "no-mixed-requires1": [1, { err
: ["error", "e"] }] } }
500 const result
= merge(config
[0], config
[1]);
502 assert
.isArray(result
.rules
["no-mixed-requires1"]);
503 assert
.deepStrictEqual(result
.rules
["no-mixed-requires1"][1], { err
: ["error", "e"] });
504 assert
.deepStrictEqual(config
[0], { rules
: { "no-mixed-requires1": [1, { event
: ["evt", "e"] }] } });
505 assert
.deepStrictEqual(config
[1], { rules
: { "no-mixed-requires1": [1, { err
: ["error", "e"] }] } });
508 it("should combine configs and override rules options without array or object", () => {
511 { rules
: { "no-mixed-requires1": ["warn", "nconf", "underscore"] } },
512 { rules
: { "no-mixed-requires1": [2, "requirejs"] } }
515 const result
= merge(config
[0], config
[1]);
517 assert
.strictEqual(result
.rules
["no-mixed-requires1"][0], 2);
518 assert
.strictEqual(result
.rules
["no-mixed-requires1"][1], "requirejs");
519 assert
.isUndefined(result
.rules
["no-mixed-requires1"][2]);
520 assert
.deepStrictEqual(config
[0], { rules
: { "no-mixed-requires1": ["warn", "nconf", "underscore"] } });
521 assert
.deepStrictEqual(config
[1], { rules
: { "no-mixed-requires1": [2, "requirejs"] } });
524 it("should combine configs and override rules options without array or object but special case", () => {
527 { rules
: { "no-mixed-requires1": [1, "nconf", "underscore"] } },
528 { rules
: { "no-mixed-requires1": "error" } }
531 const result
= merge(config
[0], config
[1]);
533 assert
.strictEqual(result
.rules
["no-mixed-requires1"][0], "error");
534 assert
.strictEqual(result
.rules
["no-mixed-requires1"][1], "nconf");
535 assert
.strictEqual(result
.rules
["no-mixed-requires1"][2], "underscore");
536 assert
.deepStrictEqual(config
[0], { rules
: { "no-mixed-requires1": [1, "nconf", "underscore"] } });
537 assert
.deepStrictEqual(config
[1], { rules
: { "no-mixed-requires1": "error" } });
540 it("should combine configs correctly", () => {
545 "no-mixed-requires1": [1, { event
: ["evt", "e"] }],
548 quotes1
: [2, { exception
: ["hi"] }],
549 smile
: [1, ["hi", "bye"]]
552 ecmaFeatures
: { jsx
: true }
554 env
: { browser
: true },
555 globals
: { foo
: false }
559 "no-mixed-requires1": [1, { err
: ["error", "e"] }],
562 smile
: [1, ["xxx", "yyy"]]
565 ecmaFeatures
: { globalReturn
: true }
567 env
: { browser
: false },
568 globals
: { foo
: true }
572 const result
= merge(config
[0], config
[1]);
574 assert
.deepStrictEqual(result
, {
575 configNameOfNoInlineConfig
: "",
591 "no-mixed-requires1": [1,
607 smile
: [1, ["xxx", "yyy"]],
613 noInlineConfig
: void 0,
614 reportUnusedDisableDirectives
: void 0,
617 assert
.deepStrictEqual(config
[0], {
619 "no-mixed-requires1": [1, { event
: ["evt", "e"] }],
622 quotes1
: [2, { exception
: ["hi"] }],
623 smile
: [1, ["hi", "bye"]]
626 ecmaFeatures
: { jsx
: true }
628 env
: { browser
: true },
629 globals
: { foo
: false }
631 assert
.deepStrictEqual(config
[1], {
633 "no-mixed-requires1": [1, { err
: ["error", "e"] }],
636 smile
: [1, ["xxx", "yyy"]]
639 ecmaFeatures
: { globalReturn
: true }
641 env
: { browser
: false },
642 globals
: { foo
: true }
646 it("should copy deeply if there is not the destination's property", () => {
648 const b
= { settings
: { bar
: 1 } };
650 const result
= merge(a
, b
);
652 assert(a
.settings
=== void 0);
653 assert(b
.settings
.bar
=== 1);
654 assert(result
.settings
.bar
=== 1);
656 result
.settings
.bar
= 2;
657 assert(b
.settings
.bar
=== 1);
658 assert(result
.settings
.bar
=== 2);
662 describe("'getUsedExtractedConfigs(instance)' function should retrieve used extracted configs from the instance's internal cache.", () => {
666 configArray
= new ConfigArray(
669 "no-redeclare": "error"
673 criteria
: OverrideTester
.create(["*.js"], [], process
.cwd()),
679 criteria
: OverrideTester
.create(["*.js"], [path
.basename(__filename
)], process
.cwd()),
681 "no-use-before-define": "error"
685 criteria
: OverrideTester
.create(["*.ts"], [], process
.cwd()),
687 "no-unused-vars": "error"
693 it("should return empty array before it called 'extractConfig(filePath)'.", () => {
694 assert
.deepStrictEqual(getUsedExtractedConfigs(configArray
), []);
697 for (const { filePaths
} of [
698 { filePaths
: [__filename
] },
699 { filePaths
: [__filename
, `${__filename}.ts`] },
700 { filePaths
: [__filename
, `${__filename}.ts`, path
.join(__dirname
, "foo.js")] }
702 describe(`after it called 'extractConfig(filePath)' ${filePaths.length} time(s) with ${JSON.stringify(filePaths, null, 4)}, the returned array`, () => { // eslint-disable-line no-loop-func
707 configs
= filePaths
.map(filePath
=> configArray
.extractConfig(filePath
));
708 usedConfigs
= getUsedExtractedConfigs(configArray
);
711 it(`should have ${filePaths.length} as the length.`, () => {
712 assert
.strictEqual(usedConfigs
.length
, configs
.length
);
715 for (let i
= 0; i
< filePaths
.length
; ++i
) {
716 it(`should contain 'configs[${i}]'.`, () => { // eslint-disable-line no-loop-func
717 assert(usedConfigs
.includes(configs
[i
]));
723 it("should not contain duplicate values.", () => {
725 // Call some times, including with the same arguments.
726 configArray
.extractConfig(__filename
);
727 configArray
.extractConfig(`${__filename}.ts`);
728 configArray
.extractConfig(path
.join(__dirname
, "foo.js"));
729 configArray
.extractConfig(__filename
);
730 configArray
.extractConfig(path
.join(__dirname
, "foo.js"));
731 configArray
.extractConfig(path
.join(__dirname
, "bar.js"));
732 configArray
.extractConfig(path
.join(__dirname
, "baz.js"));
734 const usedConfigs
= getUsedExtractedConfigs(configArray
);
736 assert
.strictEqual(new Set(usedConfigs
).size
, usedConfigs
.length
);