Skip to content

Commit

Permalink
testing: improve types, fix options, and improve api (#27430)
Browse files Browse the repository at this point in the history
* types:
  • Loading branch information
mshima authored Sep 27, 2024
1 parent 05bd6be commit 88850e0
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 28 deletions.
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

0 comments on commit 88850e0

Please sign in to comment.