Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

testing: improve types, fix options, and improve api #27430

Merged
merged 5 commits into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cli/program.mts
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,10 @@ const addCommandGeneratorOptions = async (
}
}
try {
if (root || !generatorModule.command || generatorModule.command.loadGeneratorOptions) {
if (
generatorModule.command?.loadGeneratorOptions !== false &&
(root || !generatorModule.command || generatorModule.command.loadGeneratorOptions)
) {
const generator = await generatorMeta.instantiateHelp();
// Add basic yeoman generator options
command.addGeneratorOptions(generator._options, blueprintOptionDescription);
Expand Down
294 changes: 294 additions & 0 deletions lib/command/generator-command.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
import { describe, expect } from 'esmocha';
import type { GeneratorMeta } from '@yeoman/types';
import { defaultHelpers as helpers, runResult } from '../testing/index.js';
import BaseApplicationGenerator from '../../generators/base-application/generator.js';
import type { JHipsterCommandDefinition, JHipsterConfig } from './types.js';

const notImplementedCallback = (methodName: string) => {
return () => {
throw new Error(`${methodName} not implemented`);
};
};

const dummyMeta = {
packageNamespace: 'jhipster',
resolved: 'dummy',
importModule: () => Promise.resolve({ command: { loadGeneratorOptions: false } }),
importGenerator: notImplementedCallback('importGenerator'),
instantiateHelp: notImplementedCallback('instantiateHelp'),
instantiate: notImplementedCallback('instantiate'),
};

class CommandGenerator extends BaseApplicationGenerator {
context = {};

constructor(args, opts, features) {
super(args, opts, { ...features, queueCommandTasks: true, jhipsterBootstrap: false });
this.customLifecycle = true;
}
}

const runDummyCli = (cliArgs: string, config: JHipsterConfig<any>) => {
return helpers
.runCli(cliArgs.startsWith('jdl ') ? cliArgs : `dummy ${cliArgs}`.trim(), {
useEnvironmentBuilder: false,
entrypointGenerator: 'dummy',
commands: {
dummy: { desc: 'dummy Generator' },
},
})
.withJHipsterConfig()
.onEnvironment(env => {
if (!config) {
throw new Error('command not set');
}

const metaStore: Record<string, GeneratorMeta> = (env as any).store._meta;
metaStore['jhipster:dummy'] = {
...dummyMeta,
namespace: 'jhipster:dummy',
importModule: () =>
Promise.resolve({
command: { configs: { testOption: config }, loadGeneratorOptions: false } satisfies JHipsterCommandDefinition,
}),
importGenerator: () => Promise.resolve(CommandGenerator as any),
};
metaStore['jhipster:bootstrap'] = {
...dummyMeta,
namespace: 'jhipster:bootstrap',
};
});
};

const expectGeneratorOptionsTestOption = () => expect((runResult.generator.options as any).testOption);
const expectGeneratorTestOption = () => expect((runResult.generator as any).testOption);
const expectContextTestOption = () => expect(runResult.generator.context!.testOption);
const expectJHipsterConfigTestOption = () => expect(runResult.generator.jhipsterConfig.testOption);
const expectBlueprintConfigTestOption = () => expect((runResult.generator as any).blueprintConfig.testOption);
const expectApplicationTestOption = () => expect(runResult.generator.sharedData.getApplication().testOption);

describe('generator commands', () => {
for (const scope of ['generator', 'context', 'storage', 'blueprint', 'none'] as const) {
describe(`${scope} scoped`, () => {
const checkOptions = (value: any, argument = false) => {
if (argument) {
// Argument is passed through positionalArguments option.
expectGeneratorOptionsTestOption().toBeUndefined();
} else if (typeof value === 'number') {
// Option value is not converted to number yet.
expectGeneratorOptionsTestOption().toEqual(String(value));
} else if (Array.isArray(value)) {
expectGeneratorOptionsTestOption().toEqual(value);
} else {
expectGeneratorOptionsTestOption().toBe(value);
}

if (scope !== 'generator') {
expectGeneratorTestOption().toBeUndefined();
} else if (Array.isArray(value)) {
expectGeneratorTestOption().toEqual(value);
} else {
expectGeneratorTestOption().toBe(value);
}

if (scope !== 'context') {
expectContextTestOption().toBeUndefined();
} else if (Array.isArray(value)) {
expectContextTestOption().toEqual(value);
} else {
expectContextTestOption().toBe(value);
}

if (scope !== 'blueprint') {
expectBlueprintConfigTestOption().toBeUndefined();
} else if (Array.isArray(value)) {
expectBlueprintConfigTestOption().toEqual(value);
} else {
expectBlueprintConfigTestOption().toBe(value);
}

if (!['application', 'storage', 'blueprint'].includes(scope)) {
expectApplicationTestOption().toBeUndefined();
} else if (Array.isArray(value)) {
expectApplicationTestOption().toEqual(value);
} else {
expectApplicationTestOption().toBe(value);
}

// Storage scope is same as application scope with storage.
if (scope !== 'storage') {
expectJHipsterConfigTestOption().toBeUndefined();
} else if (Array.isArray(value)) {
expectJHipsterConfigTestOption().toEqual(value);
} else {
expectJHipsterConfigTestOption().toBe(value);
}
};

describe('cli option', () => {
describe('boolean', () => {
const config: JHipsterConfig = {
cli: {
type: Boolean,
},
scope,
};

it('without options', async () => {
await runDummyCli('', config);
checkOptions(undefined);
});
it('with true option', async () => {
await runDummyCli('--test-option', config);
checkOptions(true);
});
it('with false option', async () => {
await runDummyCli('--no-test-option', config);
checkOptions(false);
});
});

describe('string', () => {
const config: JHipsterConfig = {
cli: {
type: String,
},
scope,
};

it('without options', async () => {
await runDummyCli('', config);
checkOptions(undefined);
});
it('with option value', async () => {
await runDummyCli('--test-option 1', config);
checkOptions('1');
});
});

describe('number', () => {
const config: JHipsterConfig = {
cli: {
type: Number,
},
scope,
};

it('without options', async () => {
await runDummyCli('', config);
checkOptions(undefined);
});
it('with option value', async () => {
await runDummyCli('--test-option 1', config);
checkOptions(1);
});
});

describe('array', () => {
const config: JHipsterConfig = {
cli: {
type: Array,
},
scope,
};

it('without options', async () => {
await runDummyCli('', config);
checkOptions(undefined);
});
it('with option value', async () => {
await runDummyCli('--test-option 1', config);
checkOptions(['1']);
});
it('with option values', async () => {
await runDummyCli('--test-option 1 2', config);
checkOptions(['1', '2']);
});
});
});
describe('cli argument', () => {
describe('string', () => {
const config: JHipsterConfig = {
argument: {
type: String,
},
scope,
};

it('without argument', async () => {
await runDummyCli('', config);
checkOptions(undefined, true);
});
it('with argument value', async () => {
await runDummyCli('1', config);
checkOptions('1', true);
});
});

describe('array', () => {
const config: JHipsterConfig = {
argument: {
type: Array,
},
scope,
};

it('without arguments', async () => {
await runDummyCli('', config);
checkOptions(undefined, true);
});
it('with argument value', async () => {
await runDummyCli('1', config);
checkOptions(['1'], true);
});
it('with arguments values', async () => {
await runDummyCli('1 2', config);
checkOptions(['1', '2'], true);
});
});
});

describe.skip('prompt', () => {
describe('input', () => {
const config: JHipsterConfig = {
prompt: {
message: 'testOption',
type: 'input',
},
scope,
};

it('with option', async () => {
await runDummyCli('', config).withAnswers({ testOption: '1' });
checkOptions('1');
});
});
});

describe.skip('jdl', () => {
describe('boolean jdl option', () => {
const config: JHipsterConfig = {
jdl: {
type: 'boolean',
tokenType: 'BOOLEAN',
},
scope,
};

it('without options', async () => {
await runDummyCli('jdl --inline ""', config);
checkOptions(undefined);
});
it('with true option', async () => {
await runDummyCli('--test-option', config);
checkOptions(true);
});
it('with false option', async () => {
await runDummyCli('--no-test-option', config);
checkOptions(false);
});
});
});
});
}
});
7 changes: 2 additions & 5 deletions lib/command/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export type PromptSpec = {

type JHipsterArgumentConfig = SetOptional<ArgumentSpec, 'name'> & { scope?: CommandConfigScope };

type CliSpec = SetOptional<CliOptionSpec, 'name'> & {
type CliSpec = Omit<SetOptional<CliOptionSpec, 'name'>, 'storage'> & {
env?: string;
/**
* Imply other options.
Expand All @@ -48,9 +48,6 @@ export type ConfigSpec<ConfigContext> = {
| PromptSpec
| ((gen: ConfigContext & { jhipsterConfigWithDefaults: Record<string, any> }, config: ConfigSpec<ConfigContext>) => PromptSpec);
readonly jdl?: Omit<JHipsterOptionDefinition, 'name' | 'knownChoices'>;
readonly storage?: {
readonly type?: typeof Boolean | typeof String | typeof Number | typeof Array;
};
readonly scope?: CommandConfigScope;
/**
* The callback receives the generator as input for 'generator' scope.
Expand All @@ -76,7 +73,7 @@ export type JHipsterArguments = Record<string, JHipsterArgumentConfig>;

export type JHipsterOptions = Record<string, JHipsterOption>;

export type JHipsterConfig<ConfigContext = any> = RequireAtLeastOne<ConfigSpec<ConfigContext>, 'argument' | 'cli' | 'prompt' | 'storage'>;
export type JHipsterConfig<ConfigContext = any> = RequireAtLeastOne<ConfigSpec<ConfigContext>, 'argument' | 'cli' | 'prompt' | 'jdl'>;

export type JHipsterConfigs<ConfigContext = any> = Record<string, JHipsterConfig<ConfigContext>>;

Expand Down
8 changes: 4 additions & 4 deletions lib/testing/helpers.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe('helpers', () => {
});
it('should register not jhipster generators namespaces', () => {
expect(
Object.keys(runResult.env.store._meta)
Object.keys((runResult.env as any).store._meta)
.filter(ns => ns !== DUMMY_NAMESPACE)
.sort(),
).toHaveLength(0);
Expand All @@ -22,7 +22,7 @@ describe('helpers', () => {
});
it('should register jhipster generators namespaces', () => {
expect(
Object.keys(runResult.env.store._meta)
Object.keys((runResult.env as any).store._meta)
.filter(ns => ns !== DUMMY_NAMESPACE)
.sort(),
).toMatchSnapshot();
Expand All @@ -34,7 +34,7 @@ describe('helpers', () => {
});
it('should register jhipster generators namespaces', () => {
expect(
Object.keys(runResult.env.store._meta)
Object.keys((runResult.env as any).store._meta)
.filter(ns => ns !== DUMMY_NAMESPACE)
.sort(),
).toMatchSnapshot();
Expand All @@ -48,7 +48,7 @@ describe('helpers', () => {
});
it('should register jhipster generators namespaces', () => {
expect(
Object.keys(runResult.env.store._meta)
Object.keys((runResult.env as any).store._meta)
.filter(ns => ns !== DUMMY_NAMESPACE)
.sort(),
).toMatchSnapshot();
Expand Down
Loading
Loading