diff --git a/.gitignore b/.gitignore index 52543cefe..abb084932 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ **/coverage **/dist +**/.tmp # We use tsconfig.base.json, tsconfig.json is generated by vite and may accidentally be left behind sometimes apps/**/tsconfig.json diff --git a/apps/app-builder/src/__tests__/constants.ts b/apps/app-builder/src/__tests__/constants.ts new file mode 100644 index 000000000..e9a4303a1 --- /dev/null +++ b/apps/app-builder/src/__tests__/constants.ts @@ -0,0 +1,5 @@ +import path from 'path'; + +const dirname = new URL('.', import.meta.url).pathname; + +export const MOCK_ROOT_DIR = path.resolve(dirname, '../..', '.tmp'); diff --git a/apps/app-builder/src/__tests__/createTestContext.ts b/apps/app-builder/src/__tests__/createTestContext.ts index 6de49bdbf..7de392127 100644 --- a/apps/app-builder/src/__tests__/createTestContext.ts +++ b/apps/app-builder/src/__tests__/createTestContext.ts @@ -1,31 +1,36 @@ import {Command} from 'commander'; +import {type RecursivePartial} from '@myparcel/ts-utils'; import {createDebugger, mergeDefaultConfig} from '../utils'; -import {type PdkBuilderContext, PdkPlatformName} from '../types'; +import {type CommandArgs, type PdkBuilderContext, PdkPlatformName} from '../types'; +import {MOCK_ROOT_DIR} from './constants'; -export const createTestContext = (context?: Partial): PdkBuilderContext => { - const verbose = 0; +type Input = RecursivePartial; + +export const createTestContext = (context?: Input): PdkBuilderContext => { + const args = { + arguments: [], + command: new Command(), + dryRun: false, + parallel: false, + quiet: true, + verbose: 0, + version: '1.0.0', + ...context?.args, + } as CommandArgs; return { - args: { - arguments: [], - command: new Command(), - dryRun: false, - parallel: false, - quiet: true, - verbose, - version: '1.0.0', - ...context?.args, - }, + args, config: mergeDefaultConfig({ name: 'test', platforms: [PdkPlatformName.MyParcelNl, PdkPlatformName.MyParcelBe, PdkPlatformName.Flespakket], source: [], + // @ts-expect-error todo versionSource: [], ...context?.config, }), - debug: createDebugger('test', {verbose}), + debug: createDebugger('test', args), env: { - cwd: 'CWD', + cwd: MOCK_ROOT_DIR, config: {}, configFiles: {}, modulePackage: {}, @@ -38,5 +43,5 @@ export const createTestContext = (context?: Partial): PdkBuil ...context?.env, }, ...context, - }; + } as PdkBuilderContext; }; diff --git a/apps/app-builder/src/__tests__/mockFileSystem.ts b/apps/app-builder/src/__tests__/mockFileSystem.ts new file mode 100644 index 000000000..a07fb6e06 --- /dev/null +++ b/apps/app-builder/src/__tests__/mockFileSystem.ts @@ -0,0 +1,56 @@ +import path from 'path'; +import fs from 'fs'; +import {vi} from 'vitest'; +import {isObject, merge} from 'lodash-unified'; +import {createDirectory} from '../utils'; +import {MOCK_ROOT_DIR} from './constants'; + +type Directories = Record; + +const recursiveCreate = async (entries: Record, rootDir = MOCK_ROOT_DIR): Promise => { + return Promise.all( + Object.entries(entries).map(async ([filePath, contents]) => { + const fullPath = path.resolve(rootDir, filePath); + const directory = path.dirname(fullPath); + + await createDirectory(directory, {recursive: true}); + + if (isObject(contents)) { + return recursiveCreate(contents as Record, fullPath); + } + + if (typeof contents === 'string') { + await fs.promises.writeFile(fullPath, contents); + } + }), + ); +}; + +export const mockFileSystem = async (fileSystem?: Directories): Promise => { + const base = { + config: { + 'pdk.php': ' => {}; diff --git a/apps/app-builder/src/__tests__/spies/fs.ts b/apps/app-builder/src/__tests__/spies/fs.ts new file mode 100644 index 000000000..6a4c91d4d --- /dev/null +++ b/apps/app-builder/src/__tests__/spies/fs.ts @@ -0,0 +1,36 @@ +// make a spy for all methods on fs.promises that modify the file system +import fs from 'fs'; +import {vi} from 'vitest'; + +const fsAppendFile = vi.spyOn(fs, 'appendFile'); +const fsCopyFile = vi.spyOn(fs, 'copyFile'); +const fsMkdir = vi.spyOn(fs, 'mkdir'); +const fsRm = vi.spyOn(fs, 'rm'); +const fsRmdir = vi.spyOn(fs, 'rmdir'); +const fsUnlink = vi.spyOn(fs, 'unlink'); +const fsWriteFile = vi.spyOn(fs, 'writeFile'); + +const fsPromisesAppendFile = vi.spyOn(fs.promises, 'appendFile'); +const fsPromisesCopyFile = vi.spyOn(fs.promises, 'copyFile'); +const fsPromisesMkdir = vi.spyOn(fs.promises, 'mkdir'); +const fsPromisesRm = vi.spyOn(fs.promises, 'rm'); +const fsPromisesRmdir = vi.spyOn(fs.promises, 'rmdir'); +const fsPromisesUnlink = vi.spyOn(fs.promises, 'unlink'); +const fsPromisesWriteFile = vi.spyOn(fs.promises, 'writeFile'); + +export const fsModifyingMethodSpies = [ + fsAppendFile, + fsCopyFile, + fsMkdir, + fsRm, + fsRmdir, + fsUnlink, + fsWriteFile, + fsPromisesAppendFile, + fsPromisesCopyFile, + fsPromisesMkdir, + fsPromisesRm, + fsPromisesRmdir, + fsPromisesUnlink, + fsPromisesWriteFile, +] as const; diff --git a/apps/app-builder/src/commands/clean.spec.ts b/apps/app-builder/src/commands/clean.spec.ts new file mode 100644 index 000000000..3bae73d32 --- /dev/null +++ b/apps/app-builder/src/commands/clean.spec.ts @@ -0,0 +1,50 @@ +import {afterEach, describe, expect, it, vi} from 'vitest'; +import {exists} from '../utils'; +import {fsModifyingMethodSpies} from '../__tests__/spies/fs'; +import {mockFileSystem, restoreFileSystem} from '../__tests__/mockFileSystem'; +import {createTestContext} from '../__tests__/createTestContext'; +import {MOCK_ROOT_DIR} from '../__tests__/constants'; +import clean from './clean'; + +describe('command: clean', () => { + afterEach(async () => { + await restoreFileSystem(); + vi.restoreAllMocks(); + }); + + it('does nothing when dry run is passed', async () => { + expect.assertions(fsModifyingMethodSpies.length); + + await mockFileSystem({dist: {'text.txt': ''}}); + + await clean(createTestContext({args: {dryRun: true}})); + + fsModifyingMethodSpies.forEach((spy) => { + expect(spy).not.toHaveBeenCalled(); + }); + }); + + it('does nothing when outDir does not exist', async () => { + expect.assertions(fsModifyingMethodSpies.length); + + await mockFileSystem(); + + await clean(createTestContext()); + + fsModifyingMethodSpies.forEach((spy) => { + expect(spy).not.toHaveBeenCalled(); + }); + }); + + it('cleans dir', async () => { + expect.assertions(2); + + await mockFileSystem({dist: {'text.txt': ''}}); + + expect(await exists(`${MOCK_ROOT_DIR}/dist/text.txt`)).toBe(true); + + await clean(createTestContext({args: {dryRun: false}})); + + expect(await exists(`${MOCK_ROOT_DIR}/dist/text.txt`)).toBe(false); + }); +}); diff --git a/apps/app-builder/src/utils/createDirectory.ts b/apps/app-builder/src/utils/createDirectory.ts new file mode 100644 index 000000000..a4901e5ab --- /dev/null +++ b/apps/app-builder/src/utils/createDirectory.ts @@ -0,0 +1,11 @@ +import {type MakeDirectoryOptions} from 'node:fs'; +import fs from 'fs'; +import {exists} from './exists'; + +export const createDirectory = async (directory: string, options: MakeDirectoryOptions): Promise => { + if (await exists(directory)) { + return; + } + + await fs.promises.mkdir(directory, options); +}; diff --git a/apps/app-builder/src/utils/index.ts b/apps/app-builder/src/utils/index.ts index 6ee33fa92..67ae432b0 100644 --- a/apps/app-builder/src/utils/index.ts +++ b/apps/app-builder/src/utils/index.ts @@ -24,3 +24,4 @@ export * from './resolveFileName'; export * from './resolveString'; export * from './transformer'; export * from './validateDistPath'; +export {createDirectory} from './createDirectory';