From 5333b35ad1644141b37f6d8b421620c852e81a35 Mon Sep 17 00:00:00 2001 From: Yoann Moinet Date: Fri, 4 Oct 2024 12:08:48 +0200 Subject: [PATCH] Add tests --- .../src/commands/create-plugin/ask.test.ts | 179 ++++++++++++++++++ .../tools/src/commands/create-plugin/ask.ts | 10 +- 2 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 packages/tests/src/tools/src/commands/create-plugin/ask.test.ts diff --git a/packages/tests/src/tools/src/commands/create-plugin/ask.test.ts b/packages/tests/src/tools/src/commands/create-plugin/ask.test.ts new file mode 100644 index 00000000..685fab50 --- /dev/null +++ b/packages/tests/src/tools/src/commands/create-plugin/ask.test.ts @@ -0,0 +1,179 @@ +import { + getCodeowners, + getDescription, + getHooksToInclude, + getName, + getTypeOfPlugin, + listHooks, + sanitizeCodeowners, + validateHooks, +} from '@dd/tools/commands/create-plugin/ask'; +import { bundlerHookNames, universalHookNames } from '@dd/tools/commands/create-plugin/constants'; +import { bundlerHooks, universalHooks } from '@dd/tools/commands/create-plugin/hooks'; +import type { AnyHook, TypeOfPlugin } from '@dd/tools/commands/create-plugin/types'; +import checkbox from '@inquirer/checkbox'; +import input from '@inquirer/input'; +import select from '@inquirer/select'; + +jest.mock('@inquirer/checkbox', () => jest.fn()); +jest.mock('@inquirer/input', () => jest.fn()); +jest.mock('@inquirer/select', () => jest.fn()); + +const checkboxMocked = jest.mocked(checkbox); +const inputMocked = jest.mocked(input); +const selectMocked = jest.mocked(select); + +describe('ask.ts', () => { + describe('getName', () => { + test.each([ + ['Testing #1', 'testing-1'], + ['Some, Name. With punctuation.', 'some-name-with-punctuation'], + ['Âñy Wëîrd Nâmë', 'any-weird-name'], + ])('Should slugify "%s" into "%s".', async (nameInput, expectation) => { + const name = await getName(nameInput); + expect(name).toBe(expectation); + }); + + test('Should ask for a name if none is provided.', async () => { + inputMocked.mockResolvedValueOnce('Testing #1'); + const name = await getName(); + expect(name).toBe('testing-1'); + expect(inputMocked).toHaveBeenCalledTimes(1); + }); + }); + + describe('getDescription', () => { + test('Should return the description if provided.', async () => { + const description = await getDescription('Some description.'); + expect(description).toBe('Some description.'); + }); + + test('Should ask for a description if none is provided.', async () => { + inputMocked.mockResolvedValueOnce('Some description.'); + const description = await getDescription(); + expect(description).toBe('Some description.'); + expect(inputMocked).toHaveBeenCalledTimes(1); + }); + }); + + describe('sanitizeCodeowners', () => { + test.each([ + ['@codeowners-1 @codeowners-2', '@codeowners-1 @codeowners-2'], + ['codeowners-1 codeowners-2', '@codeowners-1 @codeowners-2'], + ['@codeowners-1 , codeowners-2', '@codeowners-1 @codeowners-2'], + ['@codeowners-1,codeowners-2', '@codeowners-1 @codeowners-2'], + ])('Should sanitize "%s" into "%s".', (stringInput, expectation) => { + const codeowners = sanitizeCodeowners(stringInput); + expect(codeowners).toBe(expectation); + }); + }); + + describe('getCodeowners', () => { + test('Should return the codeowners if provided.', async () => { + const codeowners = await getCodeowners(['@codeowners-1', '@codeowners-2']); + expect(codeowners).toBe('@codeowners-1 @codeowners-2'); + }); + + test('Should ask for codeowners if none are provided.', async () => { + inputMocked.mockResolvedValueOnce('@codeowners-1 @codeowners-2'); + const codeowners = await getCodeowners(); + expect(codeowners).toBe('@codeowners-1 @codeowners-2'); + expect(inputMocked).toHaveBeenCalledTimes(1); + }); + + test('Should sanitize the codeowners.', async () => { + const codeowners = await getCodeowners(['codeowners-1', 'codeowners-2']); + expect(codeowners).toBe('@codeowners-1 @codeowners-2'); + }); + }); + + describe('listHooks', () => { + test.each([ + [bundlerHooks, bundlerHookNames], + [universalHooks, universalHookNames], + ])('Should return the hooks in a formated list.', (hooksInput, expectedHookNames) => { + const hooksReturned = listHooks(hooksInput); + expect(hooksReturned).toEqual( + expectedHookNames.map((hook) => ({ + name: expect.any(String), + value: hook, + checked: false, + })), + ); + }); + }); + + describe('getTypeOfPlugin', () => { + test('Should return the type if provided.', async () => { + const type = await getTypeOfPlugin('universal'); + expect(type).toBe('universal'); + }); + + test('Should ask for a type if an invalid one is provided.', async () => { + selectMocked.mockResolvedValueOnce('universal'); + // @ts-expect-error We are testing the error case. + const type = await getTypeOfPlugin('invalid'); + expect(type).toBe('universal'); + expect(selectMocked).toHaveBeenCalledTimes(1); + }); + + test('Should ask for a type if none is provided.', async () => { + selectMocked.mockResolvedValueOnce('universal'); + const type = await getTypeOfPlugin(); + expect(type).toBe('universal'); + expect(selectMocked).toHaveBeenCalledTimes(1); + }); + }); + + describe('validateHooks', () => { + test.each<[TypeOfPlugin, AnyHook[], string[]]>([ + ['universal', ['enforce', 'buildStart'], ['enforce', 'buildStart']], + ['universal', ['enforce', 'buildStart', 'webpack'], ['enforce', 'buildStart']], + ['bundler', ['webpack', 'esbuild'], ['webpack', 'esbuild']], + ['bundler', ['enforce', 'buildStart', 'webpack'], ['webpack']], + ['bundler', ['enforce', 'buildStart'], []], + ])( + 'Should return the the expected set of hooks for "%s" plugin.', + (pluginType, hooksInput, expectation) => { + const hooksReturned = validateHooks(pluginType, hooksInput); + expect(hooksReturned).toEqual(expectation); + }, + ); + }); + + describe('getHooksToInclude', () => { + test('Should return the hooks if provided.', async () => { + const hooksReturned = await getHooksToInclude('universal', ['enforce', 'buildStart']); + expect(hooksReturned).toEqual(['enforce', 'buildStart']); + }); + + test('Should filter out invalid hooks when provided.', async () => { + const hooksReturned = await getHooksToInclude('bundler', [ + 'enforce', + 'buildStart', + 'webpack', + ]); + expect(hooksReturned).toEqual(['webpack']); + }); + + test('Should ask for hooks if none are provided.', async () => { + checkboxMocked.mockResolvedValueOnce(['enforce', 'buildStart']); + const hooksReturned = await getHooksToInclude('universal'); + expect(hooksReturned).toEqual(['enforce', 'buildStart']); + expect(checkboxMocked).toHaveBeenCalledTimes(1); + }); + + test.each<[TypeOfPlugin, any[]]>([ + ['universal', listHooks(universalHooks)], + ['bundler', listHooks(bundlerHooks)], + ])( + 'Should offer a specific list of hooks for "%s" plugins.', + async (pluginType, expectedHooks) => { + await getHooksToInclude(pluginType); + expect(checkboxMocked).toHaveBeenCalledWith( + expect.objectContaining({ choices: expectedHooks }), + ); + }, + ); + }); +}); diff --git a/packages/tools/src/commands/create-plugin/ask.ts b/packages/tools/src/commands/create-plugin/ask.ts index e3df7b8a..4eedef15 100644 --- a/packages/tools/src/commands/create-plugin/ask.ts +++ b/packages/tools/src/commands/create-plugin/ask.ts @@ -35,7 +35,7 @@ export const getDescription = async (descriptionInput?: string) => { }); }; -const sanitizeCodeowners = (codeowners: string) => { +export const sanitizeCodeowners = (codeowners: string) => { return ( codeowners // Remove potential commas and spaces @@ -59,7 +59,7 @@ export const getCodeowners = async (codeownersInput?: string[]) => { return sanitizeCodeowners(codeowners); }; -const listHooks = (list: EitherHookList) => { +export const listHooks = (list: EitherHookList) => { return (Object.entries(list) as [AnyHook, Hook][]).map(([value, hook]) => ({ name: `${bold(hook.name)}\n ${dim(hook.descriptions.join('\n '))}`, value, @@ -70,10 +70,10 @@ const listHooks = (list: EitherHookList) => { export const getTypeOfPlugin = async (typeInput?: TypeOfPlugin) => { if (typeInput) { if (!typesOfPlugin.includes(typeInput)) { - throw new Error(`Invalid plugin type: ${red(typeInput)}`); + console.error(`Invalid plugin type: ${red(typeInput)}`); + } else { + return typeInput; } - - return typeInput; } return select({