diff --git a/.changeset/hip-rules-serve.md b/.changeset/hip-rules-serve.md new file mode 100644 index 00000000000..686fbdf0d2c --- /dev/null +++ b/.changeset/hip-rules-serve.md @@ -0,0 +1,5 @@ +--- +"create-fuels": patch +--- + +chore: `create fuels` tests clean up the filesystem \ No newline at end of file diff --git a/packages/create-fuels/.gitignore b/packages/create-fuels/.gitignore index f10e14abd57..8ac2fcd4a9a 100644 --- a/packages/create-fuels/.gitignore +++ b/packages/create-fuels/.gitignore @@ -1 +1,2 @@ -templates \ No newline at end of file +templates +test/__temp__* \ No newline at end of file diff --git a/packages/create-fuels/src/cli.ts b/packages/create-fuels/src/cli.ts index 98d7c040949..2f747e785bd 100644 --- a/packages/create-fuels/src/cli.ts +++ b/packages/create-fuels/src/cli.ts @@ -1,14 +1,12 @@ import toml from '@iarna/toml'; import chalk from 'chalk'; import { execSync } from 'child_process'; -import { Command } from 'commander'; +import type { Command } from 'commander'; import { existsSync, readFileSync, rmSync, writeFileSync } from 'fs'; import { cp, mkdir, rename } from 'fs/promises'; import ora from 'ora'; import { join } from 'path'; -import packageJson from '../package.json'; - import { tryInstallFuelUp } from './lib'; import { promptForProgramsToInclude, @@ -17,6 +15,8 @@ import { } from './prompts'; import { error, log } from './utils/logger'; +export { setupProgram } from './lib/setupProgram'; + export type ProgramsToInclude = { contract: boolean; predicate: boolean; @@ -52,21 +52,6 @@ function writeEnvFile(envFilePath: string, programsToInclude: ProgramsToInclude) writeFileSync(envFilePath, newFileContents); } -export const setupProgram = () => { - const program = new Command(packageJson.name) - .version(packageJson.version) - .arguments('[projectDirectory]') - .option('-c, --contract', 'Include contract program') - .option('-p, --predicate', 'Include predicate program') - .option('-s, --script', 'Include script program') - .option('--pnpm', 'Use pnpm as the package manager') - .option('--npm', 'Use npm as the package manager') - .option('--verbose', 'Enable verbose logging') - .addHelpCommand() - .showHelpAfterError(true); - return program; -}; - export const runScaffoldCli = async ({ program, args = process.argv, diff --git a/packages/create-fuels/src/lib/setupProgram.test.ts b/packages/create-fuels/src/lib/setupProgram.test.ts new file mode 100644 index 00000000000..85df84267a0 --- /dev/null +++ b/packages/create-fuels/src/lib/setupProgram.test.ts @@ -0,0 +1,33 @@ +import { setupProgram } from './setupProgram'; + +/** + * @group node + */ +describe('setupProgram', () => { + test('setupProgram takes in args properly', () => { + const program = setupProgram(); + program.parse(['', '', 'test-project-name', '-c', '-p', '-s', '--pnpm', '--npm']); + expect(program.args[0]).toBe('test-project-name'); + expect(program.opts().contract).toBe(true); + expect(program.opts().predicate).toBe(true); + expect(program.opts().script).toBe(true); + expect(program.opts().pnpm).toBe(true); + expect(program.opts().npm).toBe(true); + }); + + test('setupProgram takes in combined args properly', () => { + const program = setupProgram(); + program.parse(['', '', '-cps']); + expect(program.opts().contract).toBe(true); + expect(program.opts().predicate).toBe(true); + expect(program.opts().script).toBe(true); + }); + + test('setupProgram - no args', () => { + const program = setupProgram(); + program.parse([]); + expect(program.opts().contract).toBe(undefined); + expect(program.opts().predicate).toBe(undefined); + expect(program.opts().script).toBe(undefined); + }); +}); diff --git a/packages/create-fuels/src/lib/setupProgram.ts b/packages/create-fuels/src/lib/setupProgram.ts new file mode 100644 index 00000000000..3c70f2c0a50 --- /dev/null +++ b/packages/create-fuels/src/lib/setupProgram.ts @@ -0,0 +1,18 @@ +import { Command } from 'commander'; + +import packageJson from '../../package.json'; + +export const setupProgram = () => { + const program = new Command(packageJson.name) + .version(packageJson.version) + .arguments('[projectDirectory]') + .option('-c, --contract', 'Include contract program') + .option('-p, --predicate', 'Include predicate program') + .option('-s, --script', 'Include script program') + .option('--pnpm', 'Use pnpm as the package manager') + .option('--npm', 'Use npm as the package manager') + .option('--verbose', 'Enable verbose logging') + .addHelpCommand() + .showHelpAfterError(true); + return program; +}; diff --git a/packages/create-fuels/test/cli.test.ts b/packages/create-fuels/test/cli.test.ts index efd374c6aa4..39ba46471d3 100644 --- a/packages/create-fuels/test/cli.test.ts +++ b/packages/create-fuels/test/cli.test.ts @@ -1,10 +1,11 @@ -import fs, { cp } from 'fs/promises'; +import { mkdirSync } from 'fs'; import { glob } from 'glob'; -import { join } from 'path'; import type { ProgramsToInclude } from '../src/cli'; import { runScaffoldCli, setupProgram } from '../src/cli'; +import type { ProjectPaths } from './utils/bootstrapProject'; +import { bootstrapProject, cleanupFilesystem, resetFilesystem } from './utils/bootstrapProject'; import { mockLogger } from './utils/mockLogger'; const getAllFiles = async (pathToDir: string) => { @@ -70,63 +71,63 @@ const filterOriginalTemplateFiles = (files: string[], programsToInclude: Program return newFiles; }; -beforeEach(async () => { - // move the templates folder from the root of the project to the root of the create-fuels package temporarily. - // this is needed because of the way the create-fuels package is setup. - // it expects the templates folder to be in the root of the create-fuels package. we move it there in the prepublishOnly script - await cp(join(__dirname, '../../../templates'), join(__dirname, '../templates'), { - recursive: true, - }); -}); - -afterEach(async () => { - await fs.rm(join(__dirname, '../templates'), { recursive: true }); -}); - /** * @group node */ describe('CLI', () => { + const { error } = mockLogger(); + let paths: ProjectPaths; + + beforeEach(() => { + paths = bootstrapProject(__filename); + }); + + afterEach(() => { + resetFilesystem(paths.root); + resetFilesystem(paths.template); + vi.resetAllMocks(); + }); + + afterAll(() => { + cleanupFilesystem(); + }); + test.each(possibleProgramsToInclude)( 'create-fuels extracts the template to the specified directory', async (programsToInclude) => { - const args = generateArgs(programsToInclude, 'test-project'); - const program = setupProgram(); - program.parse(args); + const args = generateArgs(programsToInclude, paths.root); await runScaffoldCli({ - program, + program: setupProgram(), args, shouldInstallDeps: false, }); - let originalTemplateFiles = await getAllFiles(join(__dirname, '../templates/nextjs')); + let originalTemplateFiles = await getAllFiles(paths.template); originalTemplateFiles = filterOriginalTemplateFiles(originalTemplateFiles, programsToInclude); - const testProjectFiles = await getAllFiles('test-project'); + const testProjectFiles = await getAllFiles(paths.root); expect(originalTemplateFiles.sort()).toEqual(testProjectFiles.sort()); - - await fs.rm('test-project', { recursive: true }); } ); test('create-fuels reports an error if the project directory already exists', async () => { - await fs.mkdir('test-project-2'); - const { error } = mockLogger(); const args = generateArgs( { contract: true, predicate: true, script: true, }, - 'test-project-2' + paths.root ); - const program = setupProgram(); - program.parse(args); + // Generate the project once + mkdirSync(paths.root, { recursive: true }); + + // Generate the project again await runScaffoldCli({ - program, + program: setupProgram(), args, shouldInstallDeps: false, }).catch((e) => { @@ -134,27 +135,22 @@ describe('CLI', () => { }); expect(error).toHaveBeenCalledWith( - expect.stringContaining('A folder already exists at test-project-2') + expect.stringContaining(`A folder already exists at ${paths.root}`) ); - - await fs.rm('test-project-2', { recursive: true }); }); test('create-fuels reports an error if no programs are chosen to be included', async () => { - const { error } = mockLogger(); const args = generateArgs( { contract: false, predicate: false, script: false, }, - 'test-project-3' + paths.root ); - const program = setupProgram(); - program.parse(args); await runScaffoldCli({ - program, + program: setupProgram(), args, shouldInstallDeps: false, forceDisablePrompts: true, @@ -166,31 +162,4 @@ describe('CLI', () => { expect.stringContaining('You must include at least one Sway program.') ); }); - - test('setupProgram takes in args properly', () => { - const program = setupProgram(); - program.parse(['', '', 'test-project-name', '-c', '-p', '-s', '--pnpm', '--npm']); - expect(program.args[0]).toBe('test-project-name'); - expect(program.opts().contract).toBe(true); - expect(program.opts().predicate).toBe(true); - expect(program.opts().script).toBe(true); - expect(program.opts().pnpm).toBe(true); - expect(program.opts().npm).toBe(true); - }); - - test('setupProgram takes in combined args properly', () => { - const program = setupProgram(); - program.parse(['', '', '-cps']); - expect(program.opts().contract).toBe(true); - expect(program.opts().predicate).toBe(true); - expect(program.opts().script).toBe(true); - }); - - test('setupProgram - no args', () => { - const program = setupProgram(); - program.parse([]); - expect(program.opts().contract).toBe(undefined); - expect(program.opts().predicate).toBe(undefined); - expect(program.opts().script).toBe(undefined); - }); }); diff --git a/packages/create-fuels/test/utils/bootstrapProject.ts b/packages/create-fuels/test/utils/bootstrapProject.ts new file mode 100644 index 00000000000..ba0828c6b3e --- /dev/null +++ b/packages/create-fuels/test/utils/bootstrapProject.ts @@ -0,0 +1,54 @@ +import { cpSync, existsSync, readdirSync, rmSync } from 'fs'; +import { basename, join } from 'path'; + +export type ProjectPaths = { + root: string; + template: string; +}; + +/** + * Path and Directory utils + */ +const testDir = join(__dirname, '..'); +const createFuelsDir = join(__dirname, '../..'); +const testTemplateDir = join(createFuelsDir, 'templates'); +const templatesDir = join(__dirname, '../../../../templates'); + +export const bootstrapProject = ( + testFilepath: string, + templateName: string = 'nextjs' +): ProjectPaths => { + // Template paths + const templateDir = join(templatesDir, templateName); + const localTemplateDir = join(testTemplateDir, templateName); + if (!existsSync(localTemplateDir)) { + cpSync(templateDir, localTemplateDir, { recursive: true }); + } + + // Unique name + const testFilename = basename(testFilepath.replace(/\./g, '-')); + const projectName = `__temp__${testFilename}_${new Date().getTime()}`; + + // Test paths + const root = join(testDir, projectName); + + return { + root, + template: localTemplateDir, + }; +}; + +export const resetFilesystem = (dirPath: string) => { + if (existsSync(dirPath)) { + rmSync(dirPath, { recursive: true }); + } +}; + +export const cleanupFilesystem = (dirPath: string = testDir) => { + const dirs = readdirSync(dirPath).filter((dir) => dir.startsWith('__temp__')); + dirs.forEach((dir) => { + resetFilesystem(join(dirPath, dir)); + }); + + resetFilesystem(testTemplateDir); +};