diff --git a/packages/angular/cli/src/command-builder/utilities/json-schema.ts b/packages/angular/cli/src/command-builder/utilities/json-schema.ts index e9fbd7e0e27a..06728f1ee9ac 100644 --- a/packages/angular/cli/src/command-builder/utilities/json-schema.ts +++ b/packages/angular/cli/src/command-builder/utilities/json-schema.ts @@ -257,18 +257,18 @@ function getEnumValues( */ function getDefaultValue( current: json.JsonObject, - type: string, + type: ReadonlyArray, ): string | number | boolean | unknown[] | undefined { const defaultValue = current.default; if (defaultValue === undefined) { return undefined; } - if (type === 'array') { + if (type.includes('array')) { return Array.isArray(defaultValue) && defaultValue.length > 0 ? defaultValue : undefined; } - if (typeof defaultValue === type) { + if (type.includes(typeof defaultValue)) { return defaultValue as string | number | boolean; } @@ -343,7 +343,12 @@ export async function parseJsonSchemaToOptions( return; } - const [type] = types; + // Allow Yargs to infer the option type for string AND boolean options + const type = + types.length === 2 && types.includes('string') && types.includes('boolean') + ? undefined + : types[0]; + const $default = current.$default; const $defaultIndex = isJsonObject($default) && $default['$source'] === 'argv' ? $default['index'] : undefined; @@ -362,7 +367,7 @@ export async function parseJsonSchemaToOptions( const option: Option = { name, description: String(current.description ?? ''), - default: getDefaultValue(current, type), + default: getDefaultValue(current, types), choices: enumValues?.length ? enumValues : undefined, required, alias: getAliases(current), diff --git a/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts b/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts index d311373d69f0..df5c624ef2f0 100644 --- a/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts +++ b/packages/angular/cli/src/command-builder/utilities/json-schema_spec.ts @@ -257,6 +257,29 @@ describe('parseJsonSchemaToOptions', () => { }); }); + it(`should not set 'type' when it is a 'string' and a 'boolean'`, async () => { + const registry = new schema.CoreSchemaRegistry(); + const options = await parseJsonSchemaToOptions( + registry, + { + 'type': 'object', + 'properties': { + 'runner': { + 'type': ['string', 'boolean'], + }, + }, + }, + false, + ); + + expect(options).toEqual([ + jasmine.objectContaining({ + name: 'runner', + type: undefined, + }), + ]); + }); + describe('with required positional argument', () => { it('marks the required argument as required', async () => { const jsonSchema = { diff --git a/tests/legacy-cli/e2e/tests/vitest/runner-config-option.ts b/tests/legacy-cli/e2e/tests/vitest/runner-config-option.ts new file mode 100644 index 000000000000..92fe754d9b24 --- /dev/null +++ b/tests/legacy-cli/e2e/tests/vitest/runner-config-option.ts @@ -0,0 +1,50 @@ +import { ng } from '../../utils/process'; +import { writeMultipleFiles } from '../../utils/fs'; +import { applyVitestBuilder } from '../../utils/vitest'; +import assert from 'node:assert'; + +export default async function () { + await applyVitestBuilder(); + + // Test the boolean `true` usage of `--runner-config`. + // It should log that it is searching for a config file. + const { stdout: boolTrueStdout } = await ng('test', '--runner-config'); + assert.match( + boolTrueStdout, + /Automatically searching/, + 'Expected an automatic search message for --runner-config', + ); + assert.match(boolTrueStdout, /1 passed/, 'Expected 1 test to pass with --runner-config'); + + // Test the string usage of `--runner-config`. + // It should log that it is using the specified config file. + const customConfigPath = 'vitest.custom.mjs'; + await writeMultipleFiles({ + [customConfigPath]: ` + import { defineConfig } from 'vitest/config'; + export default defineConfig({ + test: { + + }, + }); + `, + }); + + const { stdout: stringStdout } = await ng('test', `--runner-config=${customConfigPath}`); + assert.match( + stringStdout, + /vitest\.custom\.mjs/, + 'Expected a message confirming the use of the custom config file.', + ); + assert.match(stringStdout, /1 passed/, 'Expected all tests to pass with string config.'); + + // Test the boolean `false` usage of `--runner-config`. + // It should not log any messages about searching for or using a config file. + const { stdout: boolFalseStdout } = await ng('test', '--no-runner-config'); + assert.doesNotMatch( + boolFalseStdout, + /Automatically searching/, + 'Should not search for a config with --no-runner-config', + ); + assert.match(boolFalseStdout, /1 passed/, 'Expected 1 test to pass with --no-runner-config'); +}