/
githubmirror
/
webpack
Обзор
Документация
Войти
/
githubmirror
/
webpack
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
test/Cli.basictest.js
940 строк
22 KB
Alexander Akait
Reduce skipped tests across Node, Deno and Bun and prune dead WebAssembly filters (#21257)
24 июн 2026, 01:02
Не верифицирован
24 июн 2026, 01:02
efeb5ce
Код
Авторство
О чём код?
"use strict"; const { createColors, getArguments, isColorSupported, processArguments } = require("../").cli; describe("Cli", () => { describe("getArguments", () => { it("should generate the correct cli flags", () => { expect(getArguments()).toMatchSnapshot(); }); it("should generate the correct cli flags with custom schema", () => { const schema = { title: "custom CLI options", type: "object", additionalProperties: false, properties: { "with-reset-description": { type: "array", items: { type: "string" }, description: "original description", cli: { resetDescription: "custom reset" } }, "with-cli-description": { type: "string", description: "original description", cli: { description: "description for CLI option" } }, "with-negative-description": { type: "boolean", description: "original description", cli: { negatedDescription: "custom negative description" } }, "with-both-cli-and-negative-description": { type: "boolean", description: "original description", cli: { description: "description for CLI option", negatedDescription: "custom negative description" } } } }; expect( getArguments( /** @type {Parameters<typeof getArguments>[0]} */ ( /** @type {unknown} */ (schema) ) ) ).toMatchSnapshot(); }); it("should generate flags for a schema using `const`", () => { const schema = { title: "custom CLI options", type: "object", additionalProperties: false, properties: { mode: { const: "production", description: "the const mode value" } } }; expect( getArguments( /** @type {Parameters<typeof getArguments>[0]} */ ( /** @type {unknown} */ (schema) ) ) ).toMatchSnapshot(); }); it("should generate flags for both branches of an `if/then/else` schema", () => { const schema = { title: "custom CLI options", type: "object", additionalProperties: false, properties: { mode: { enum: ["a", "b"], description: "the mode" } }, if: { properties: { mode: { const: "a" } } }, // eslint-disable-next-line unicorn/no-thenable then: { type: "object", properties: { "only-when-a": { type: "string", description: "only when a" } } }, else: { type: "object", properties: { "only-when-b": { type: "boolean", description: "only when b" } } } }; expect( getArguments( /** @type {Parameters<typeof getArguments>[0]} */ ( /** @type {unknown} */ (schema) ) ) ).toMatchSnapshot(); }); it("reports the schema origin path in conflicting-schema errors", () => { const schema = { type: "object", additionalProperties: false, properties: { foo: { oneOf: [ { type: "string" }, { type: "array", items: { type: "string" } } ] } } }; expect(() => getArguments( /** @type {Parameters<typeof getArguments>[0]} */ ( /** @type {unknown} */ (schema) ) ) ).toThrow( "Conflicting schema for foo[] (#/properties/foo/oneOf/1/items) with string type" ); }); }); describe("processArguments", () => { const test = ( /** @type {string} */ name, /** @type {Record<string, EXPECTED_ANY>} */ values, /** @type {import("../").Configuration} */ config, /** @type {(e: EXPECTED_ANY) => void} */ fn ) => { it(`should correctly process arguments for ${name}`, () => { const args = getArguments(); const problems = processArguments( args, config, /** @type {import("../lib/cli").Values} */ (values) ); fn(expect(problems || config)); }); }; test("none", {}, {}, (e) => e.toMatchInlineSnapshot("Object {}")); test("root boolean", { bail: true }, {}, (e) => e.toMatchInlineSnapshot(` Object { "bail": true, } `) ); test("root single item of multiple", { entry: "./a.js" }, {}, (e) => e.toMatchInlineSnapshot(` Object { "entry": Array [ "./a.js", ], } `) ); test( "root single item of multiple with existing item", { entry: "./a.js" }, { entry: "./old.js" }, (e) => e.toMatchInlineSnapshot(` Object { "entry": Array [ "./old.js", "./a.js", ], } `) ); test( "root single item of multiple with existing items", { entry: "./a.js" }, { entry: ["./old1.js", "./old2.js"] }, (e) => e.toMatchInlineSnapshot(` Object { "entry": Array [ "./old1.js", "./old2.js", "./a.js", ], } `) ); test("root multiple items", { entry: ["./a.js", "./b.js"] }, {}, (e) => e.toMatchInlineSnapshot(` Object { "entry": Array [ "./a.js", "./b.js", ], } `) ); test( "root multiple items with existing item", { entry: ["./a.js", "./b.js"] }, { entry: "./old.js" }, (e) => e.toMatchInlineSnapshot(` Object { "entry": Array [ "./old.js", "./a.js", "./b.js", ], } `) ); test( "root multiple items with existing items", { entry: ["./a.js", "./b.js"] }, { entry: ["./old1.js", "./old2.js"] }, (e) => e.toMatchInlineSnapshot(` Object { "entry": Array [ "./old1.js", "./old2.js", "./a.js", "./b.js", ], } `) ); test("nested boolean", { "experiments-top-level-await": true }, {}, (e) => e.toMatchInlineSnapshot(` Array [ Object { "argument": "experiments-top-level-await", "path": "", "type": "unknown-argument", }, ] `) ); test( "nested regexp", { "stats-warnings-filter": ["/module/", "path"] }, {}, (e) => e.toMatchInlineSnapshot(` Object { "stats": Object { "warningsFilter": Array [ /module/, "path", ], }, } `) ); test( "nested multiple", { "module-rules-test": ["/\\.css$/", "/\\.js$/"], "module-rules-use": ["css-loader", "babel-loader"] }, {}, (e) => e.toMatchInlineSnapshot(` Object { "module": Object { "rules": Array [ Object { "test": /\\\\\\.css\\$/, "use": "css-loader", }, Object { "test": /\\\\\\.js\\$/, "use": "babel-loader", }, ], }, } `) ); test( "reset array", { "stats-warnings-filter-reset": true, "stats-warnings-filter": "path", "module-rules-reset": true, "module-rules-test": ["/\\.css$/", "/\\.js$/"], "module-rules-use": ["css-loader", "babel-loader"] }, { stats: { warningsFilter: [/a/, /b/] }, module: { rules: [ { test: /\.js$/, use: "typescript-loader" } ] } }, (e) => e.toMatchInlineSnapshot(` Object { "module": Object { "rules": Array [ Object { "test": /\\\\\\.css\\$/, "use": "css-loader", }, Object { "test": /\\\\\\.js\\$/, "use": "babel-loader", }, ], }, "stats": Object { "warningsFilter": Array [ "path", ], }, } `) ); test( "numbers", { "watch-options-aggregate-timeout": 100, "watch-options-poll": "100", "output-chunk-load-timeout": "20000" }, {}, (e) => e.toMatchInlineSnapshot(` Object { "output": Object { "chunkLoadTimeout": 20000, }, "watchOptions": Object { "aggregateTimeout": 100, "poll": 100, }, } `) ); test( "numbers decimal and scientific notation strings", { "watch-options-aggregate-timeout": ".25", "watch-options-poll": "1e2", "output-chunk-load-timeout": "5.", parallelism: "1e+2", "performance-max-asset-size": "1.5e3", "performance-max-entrypoint-size": "2e-1", "stats-assets-space": "10E+1", "stats-errors-space": "+.5" }, {}, (e) => e.toMatchInlineSnapshot(` Object { "output": Object { "chunkLoadTimeout": 5, }, "parallelism": 100, "performance": Object { "maxAssetSize": 1500, "maxEntrypointSize": 0.2, }, "stats": Object { "assetsSpace": 100, "errorsSpace": 0.5, }, "watchOptions": Object { "aggregateTimeout": 0.25, "poll": 100, }, } `) ); test( "invalid number strings (decimal and exponent edge cases)", { parallelism: "1e", "performance-max-asset-size": "e10", "performance-max-entrypoint-size": "1e2.5", "stats-assets-space": "1.2.3", "output-chunk-load-timeout": "7e-", "stats-warnings-space": "++1" }, {}, (e) => e.toMatchInlineSnapshot(` Array [ Object { "argument": "parallelism", "expected": "number", "index": undefined, "path": "parallelism", "type": "invalid-value", "value": "1e", }, Object { "argument": "performance-max-asset-size", "expected": "number", "index": undefined, "path": "performance.maxAssetSize", "type": "invalid-value", "value": "e10", }, Object { "argument": "performance-max-entrypoint-size", "expected": "number", "index": undefined, "path": "performance.maxEntrypointSize", "type": "invalid-value", "value": "1e2.5", }, Object { "argument": "stats-assets-space", "expected": "number", "index": undefined, "path": "stats.assetsSpace", "type": "invalid-value", "value": "1.2.3", }, Object { "argument": "output-chunk-load-timeout", "expected": "number", "index": undefined, "path": "output.chunkLoadTimeout", "type": "invalid-value", "value": "7e-", }, Object { "argument": "stats-warnings-space", "expected": "number", "index": undefined, "path": "stats.warningsSpace", "type": "invalid-value", "value": "++1", }, ] `) ); test( "booleans and enums", { "optimization-used-exports": true, "output-compare-before-emit": false, "output-iife": "true", "output-library-name": ["hello", "world"], "output-library-umd-named-define": "false", "stats-logging": "verbose", amd: "false" }, {}, (e) => e.toMatchInlineSnapshot(` Object { "amd": false, "optimization": Object { "usedExports": true, }, "output": Object { "compareBeforeEmit": false, "iife": true, "library": Object { "name": Array [ "hello", "world", ], "umdNamedDefine": false, }, }, "stats": Object { "logging": "verbose", }, } `) ); // cspell:ignore filsystem test( "errors", { "output-library-name": "non-object", "resolve-loader-unsafe-cache": [true, false], "output-chunk-load-timeout": "20000x", "watch-options-aggregate-timeout": "Infinity", "watch-options-poll": "0x10", "cache-type": "filsystem", "entry-reset": false, "module-unknown-context-reg-exp": "ab?c*", "module-wrapped-context-reg-exp": 123, "my-argument": true }, { output: { library: "hello" } }, (e) => e.toMatchInlineSnapshot(` Array [ Object { "argument": "output-library-name", "index": undefined, "path": "output", "type": "unexpected-non-object-in-path", "value": "non-object", }, Object { "argument": "resolve-loader-unsafe-cache", "index": 0, "path": "resolveLoader.unsafeCache", "type": "multiple-values-unexpected", "value": true, }, Object { "argument": "resolve-loader-unsafe-cache", "index": 1, "path": "resolveLoader.unsafeCache", "type": "multiple-values-unexpected", "value": false, }, Object { "argument": "output-chunk-load-timeout", "expected": "number", "index": undefined, "path": "output.chunkLoadTimeout", "type": "invalid-value", "value": "20000x", }, Object { "argument": "watch-options-aggregate-timeout", "expected": "number", "index": undefined, "path": "watchOptions.aggregateTimeout", "type": "invalid-value", "value": "Infinity", }, Object { "argument": "watch-options-poll", "expected": "number", "index": undefined, "path": "watchOptions.poll", "type": "invalid-value", "value": "0x10", }, Object { "argument": "watch-options-poll", "expected": "true | false", "index": undefined, "path": "watchOptions.poll", "type": "invalid-value", "value": "0x10", }, Object { "argument": "cache-type", "expected": "memory", "index": undefined, "path": "cache.type", "type": "invalid-value", "value": "filsystem", }, Object { "argument": "cache-type", "expected": "filesystem", "index": undefined, "path": "cache.type", "type": "invalid-value", "value": "filsystem", }, Object { "argument": "entry-reset", "expected": "true (will reset the previous value to an empty array)", "index": undefined, "path": "entry", "type": "invalid-value", "value": false, }, Object { "argument": "module-unknown-context-reg-exp", "expected": "regular expression (example: /ab?c*/)", "index": undefined, "path": "module.unknownContextRegExp", "type": "invalid-value", "value": "ab?c*", }, Object { "argument": "module-unknown-context-reg-exp", "expected": "true | false", "index": undefined, "path": "module.unknownContextRegExp", "type": "invalid-value", "value": "ab?c*", }, Object { "argument": "module-wrapped-context-reg-exp", "expected": "regular expression (example: /ab?c*/)", "index": undefined, "path": "module.wrappedContextRegExp", "type": "invalid-value", "value": 123, }, Object { "argument": "my-argument", "path": "", "type": "unknown-argument", }, ] `) ); for (const key of ["__proto__", "constructor", "prototype"]) { it(`should not pollute the prototype through a "${key}" path segment`, () => { /** @type {import("../lib/cli").Flags} */ const args = { "evil-flag": { description: undefined, configs: [ { type: /** @type {import("../lib/cli").SimpleType} */ ("string"), multiple: false, path: `${key}.polluted` } ], simpleType: /** @type {import("../lib/cli").SimpleType} */ ( "string" ), multiple: false } }; const config = {}; const problems = processArguments( args, config, /** @type {import("../lib/cli").Values} */ ( /** @type {unknown} */ ({ "evil-flag": "PWNED" }) ) ); expect( /** @type {Record<string, unknown>} */ ({}).polluted ).toBeUndefined(); expect(problems).toEqual([ expect.objectContaining({ type: "prototype-pollution-in-path" }) ]); }); it(`should not pollute the prototype through a trailing "${key}" path segment`, () => { /** @type {import("../lib/cli").Flags} */ const args = { "evil-flag": { description: undefined, configs: [ { type: /** @type {import("../lib/cli").SimpleType} */ ("string"), multiple: false, path: key } ], simpleType: /** @type {import("../lib/cli").SimpleType} */ ( "string" ), multiple: false } }; const config = {}; const problems = processArguments( args, config, /** @type {import("../lib/cli").Values} */ ( /** @type {unknown} */ ({ "evil-flag": "PWNED" }) ) ); expect( /** @type {Record<string, unknown>} */ ({}).polluted ).toBeUndefined(); expect(problems).toEqual([ expect.objectContaining({ type: "prototype-pollution-in-path" }) ]); }); } }); describe("isColorSupported", () => { const OLD_ENV = process.env; beforeEach(() => { // Most important - it clears the cache jest.resetModules(); process.env = { ...OLD_ENV }; // Drop the values the CI runner/jest-worker inject (FORCE_COLOR, and the // CI provider vars) so each test exercises exactly the signal it sets. for (const key of [ "FORCE_COLOR", "TERM", "CI", "GITHUB_ACTIONS", "GITLAB_CI", "CIRCLECI" ]) { delete process.env[key]; } }); afterAll(() => { process.env = OLD_ENV; }); it("env NO_COLOR", () => { process.env.NO_COLOR = "1"; expect(isColorSupported()).toBe(false); }); it("env FORCE_COLOR", () => { process.env.FORCE_COLOR = "1"; expect(isColorSupported()).toBe(true); }); it("env TERM", () => { const isCI = process.env.CI !== undefined && (process.env.GITHUB_ACTIONS !== undefined || process.env.GITLAB_CI !== undefined || process.env.CIRCLECI !== undefined); process.env.TERM = "dumb"; expect(isColorSupported()).toBe(isCI); }); it("env GITHUB_ACTIONS", () => { process.env.CI = "1"; process.env.GITHUB_ACTIONS = "1"; expect(isColorSupported()).toBe(true); }); it("env GITLAB_CI", () => { process.env.CI = "1"; process.env.GITLAB_CI = "1"; expect(isColorSupported()).toBe(true); }); it("env CIRCLECI", () => { process.env.CI = "1"; process.env.CIRCLECI = "1"; expect(isColorSupported()).toBe(true); }); it("argv --color", () => { const originalArgv = process.argv; process.argv = [...process.argv, "--color"]; try { expect(isColorSupported()).toBe(true); } finally { process.argv = originalArgv; } }); it("argv --no-color", () => { const originalArgv = process.argv; process.argv = [...process.argv, "--no-color"]; try { expect(isColorSupported()).toBe(false); } finally { process.argv = originalArgv; } }); }); describe("createColors", () => { const colorsMap = [ ["reset", "\u001B[0m", "\u001B[0m"], ["bold", "\u001B[1m", "\u001B[22m"], ["dim", "\u001B[2m", "\u001B[22m"], ["italic", "\u001B[3m", "\u001B[23m"], ["underline", "\u001B[4m", "\u001B[24m"], ["inverse", "\u001B[7m", "\u001B[27m"], ["hidden", "\u001B[8m", "\u001B[28m"], ["strikethrough", "\u001B[9m", "\u001B[29m"], ["black", "\u001B[30m", "\u001B[39m"], ["red", "\u001B[31m", "\u001B[39m"], ["green", "\u001B[32m", "\u001B[39m"], ["yellow", "\u001B[33m", "\u001B[39m"], ["blue", "\u001B[34m", "\u001B[39m"], ["magenta", "\u001B[35m", "\u001B[39m"], ["cyan", "\u001B[36m", "\u001B[39m"], ["white", "\u001B[37m", "\u001B[39m"], ["gray", "\u001B[90m", "\u001B[39m"], ["bgBlack", "\u001B[40m", "\u001B[49m"], ["bgRed", "\u001B[41m", "\u001B[49m"], ["bgGreen", "\u001B[42m", "\u001B[49m"], ["bgYellow", "\u001B[43m", "\u001B[49m"], ["bgBlue", "\u001B[44m", "\u001B[49m"], ["bgMagenta", "\u001B[45m", "\u001B[49m"], ["bgCyan", "\u001B[46m", "\u001B[49m"], ["bgWhite", "\u001B[47m", "\u001B[49m"], ["blackBright", "\u001B[90m", "\u001B[39m"], ["redBright", "\u001B[91m", "\u001B[39m"], ["greenBright", "\u001B[92m", "\u001B[39m"], ["yellowBright", "\u001B[93m", "\u001B[39m"], ["blueBright", "\u001B[94m", "\u001B[39m"], ["magentaBright", "\u001B[95m", "\u001B[39m"], ["cyanBright", "\u001B[96m", "\u001B[39m"], ["whiteBright", "\u001B[97m", "\u001B[39m"], ["bgBlackBright", "\u001B[100m", "\u001B[49m"], ["bgRedBright", "\u001B[101m", "\u001B[49m"], ["bgGreenBright", "\u001B[102m", "\u001B[49m"], ["bgYellowBright", "\u001B[103m", "\u001B[49m"], ["bgBlueBright", "\u001B[104m", "\u001B[49m"], ["bgMagentaBright", "\u001B[105m", "\u001B[49m"], ["bgCyanBright", "\u001B[106m", "\u001B[49m"], ["bgWhiteBright", "\u001B[107m", "\u001B[49m"] ]; const colors = createColors({ useColor: true }); it("simple", () => { for (const [name, open, close] of colorsMap) { expect( /** @type {Record<string, import("../lib/cli").PrintFunction>} */ ( /** @type {unknown} */ (colors) )[name](name) ).toBe(open + name + close); } }); it("nesting", () => { expect( colors.bold(`bold ${colors.red(`red ${colors.dim("dim")} red`)} bold`) ).toBe( /* cspell:disable-next-line */ "\u001B[1mbold \u001B[31mred \u001B[2mdim\u001B[22m\u001B[1m red\u001B[39m bold\u001B[22m" ); expect( colors.magenta( `magenta ${colors.yellow( `yellow ${colors.cyan("cyan")} ${colors.red("red")} ${colors.green( "green" )} yellow` )} magenta` ) ).toBe( /* cspell:disable-next-line */ "\u001B[35mmagenta \u001B[33myellow \u001B[36mcyan\u001B[33m \u001B[31mred\u001B[33m \u001B[32mgreen\u001B[33m yellow\u001B[35m magenta\u001B[39m" ); }); it("numbers & others", () => { for (const n of [new Date(), -1e10, -1, -0.1, 0, 0.1, 1, 1e10]) { expect(colors.red(n)).toBe(`\u001B[31m${n}\u001B[39m`); } }); it("empty & falsy values", () => { expect(colors.blue()).toBe(""); expect(colors.blue("")).toBe(""); expect(colors.blue(undefined)).toBe(""); expect(colors.blue(0)).toBe("\u001B[34m0\u001B[39m"); // eslint-disable-next-line unicorn/prefer-number-properties expect(colors.blue(NaN)).toBe("\u001B[34mNaN\u001B[39m"); expect(colors.blue(Number.NaN)).toBe("\u001B[34mNaN\u001B[39m"); /* cspell:disable-next-line */ expect(colors.blue(null)).toBe("\u001B[34mnull\u001B[39m"); /* cspell:disable-next-line */ expect(colors.blue(true)).toBe("\u001B[34mtrue\u001B[39m"); /* cspell:disable-next-line */ expect(colors.blue(false)).toBe("\u001B[34mfalse\u001B[39m"); expect(colors.blue(Infinity)).toBe("\u001B[34mInfinity\u001B[39m"); }); const noColors = createColors({ useColor: false }); it("simple (no colors)", () => { for (const [name] of colorsMap) { expect( /** @type {Record<string, import("../lib/cli").PrintFunction>} */ ( /** @type {unknown} */ (noColors) )[name](name) ).toBe(name); } }); const defaultColors = createColors(); it("simple (colors by default)", () => { for (const [name, open, close] of colorsMap) { expect( /** @type {Record<string, import("../lib/cli").PrintFunction>} */ ( /** @type {unknown} */ (defaultColors) )[name](name) ).toBe(open + name + close); } }); }); });