diff --git a/tests/assertions.ts b/tests/assertions.ts index 94783f4b..4c865f40 100644 --- a/tests/assertions.ts +++ b/tests/assertions.ts @@ -3,7 +3,8 @@ import fs from 'node:fs/promises'; import path from 'node:path'; import { expect } from 'vitest'; -import { packageJsonAt, readFixture } from './utils.js'; +import { readFixture } from './fixtures.js'; +import { packageJsonAt } from './utils.js'; interface AssertGeneratedOptions { projectRoot: string; diff --git a/tests/fixtures.ts b/tests/fixtures.ts new file mode 100644 index 00000000..cc814a07 --- /dev/null +++ b/tests/fixtures.ts @@ -0,0 +1,81 @@ +import fse from 'fs-extra'; +import assert from 'node:assert'; +import fs from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + +const fixturesPath = path.join(__dirname, 'fixtures'); + +/** + * Returns the contents of a file from the "tests/fixtures" directory. + * The "tests/fixtures" directory contains sub-directories, "scenarios". + * This is we can have different sets of fixtures, depending on what we're testing. + * + * The default scenario is "default", and represents the the file contents when we provide + * no arguments to the blueprint + */ +export async function readFixture( + /** + * Which file within in the fixture-set / scenario to read + */ + file: string, + options?: { + /** + * Which fixture set to use + */ + scenario?: string; + } +) { + let scenario = options?.scenario ?? 'default'; + let fixtureFilePath = path.isAbsolute(file) ? file : path.join(fixturesPath, scenario, file); + + let exists = await fse.pathExists(fixtureFilePath); + + assert( + exists, + `Fixture file '${file}' does not exist. To make this work, place a new file '${file}' in the 'tests/fixtures/${scenario}' directory. Checked the absolute path: '${fixtureFilePath}'.` + ); + + let contents = await fs.readFile(fixtureFilePath); + + return contents.toString(); +} + +export async function copyFixture( + /** + * Which file within the fixture-set / scenario to copy + */ + newFile: string, + options?: { + /** + * Which fixture set to use + */ + scenario?: string; + /** + * By default, the file used will be the same as the testFilePath, but + * in the fixtures directory under the (maybe) specified scenario. + * this can be overridden, if needed. + * (like if you're testFilePath is deep with in an existing monorepo, and wouldn't + * inherently match our default-project structure used in the fixtures) + */ + file?: string; + /** + * The working directory to use for the relative paths. Defaults to process.cwd() (node default) + */ + cwd?: string; + } +) { + let scenario = options?.scenario ?? 'default'; + let fixtureFile = options?.file ?? newFile; + + if (options?.cwd) { + newFile = path.join(options.cwd, newFile); + } + + let fixtureContents = await readFixture(fixtureFile, { scenario }); + + await fse.mkdir(path.dirname(newFile), { recursive: true }); + await fs.writeFile(newFile, fixtureContents); +} diff --git a/tests/smoke-tests/--addon-only.test.ts b/tests/smoke-tests/--addon-only.test.ts index 5605f17a..1a9ca889 100644 --- a/tests/smoke-tests/--addon-only.test.ts +++ b/tests/smoke-tests/--addon-only.test.ts @@ -1,34 +1,26 @@ import fse from 'fs-extra'; -import fs from 'node:fs/promises'; import path from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; -import { createAddon, createTmp, install, runScript } from '../utils.js'; +import { AddonHelper } from '../test-helpers.js'; +import { runScript } from '../utils.js'; describe('--addon-only', () => { + let helper = new AddonHelper({ packageManager: 'pnpm', args: ['--addon-only'] }); let cwd = ''; - let tmpDir = ''; beforeAll(async () => { - tmpDir = await createTmp(); - - let { name } = await createAddon({ - args: ['--addon-only', '--pnpm=true'], - options: { cwd: tmpDir }, - }); - - cwd = path.join(tmpDir, name); - - await install({ cwd, packageManager: 'pnpm' }); + await helper.setup(); + await helper.installDeps(); }); afterAll(async () => { - fs.rm(tmpDir, { recursive: true, force: true }); + await helper.clean(); }); it('is not a monorepo', async () => { - let hasPnpmWorkspace = await fse.pathExists(path.join(cwd, 'pnpm-workspace.yaml')); - let packageJson = await fse.readJson(path.join(cwd, 'package.json')); + let hasPnpmWorkspace = await fse.pathExists(path.join(helper.cwd, 'pnpm-workspace.yaml')); + let packageJson = await fse.readJson(path.join(helper.cwd, 'package.json')); expect(hasPnpmWorkspace).toBe(false); // Pnpm doesn't use this field, but it's good that it doesn't exist. diff --git a/tests/smoke-tests/within-existing-monorepo/custom-locations.test.ts b/tests/smoke-tests/within-existing-monorepo/custom-locations.test.ts index 09a99435..4a5becd1 100644 --- a/tests/smoke-tests/within-existing-monorepo/custom-locations.test.ts +++ b/tests/smoke-tests/within-existing-monorepo/custom-locations.test.ts @@ -4,10 +4,11 @@ import path from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { assertGeneratedCorrectly } from '../../assertions.js'; -import { createAddon, createTmp, fixture, install, runScript } from '../../utils.js'; +import { readFixture } from '../../fixtures.js'; +import { createAddon, createTmp, install, runScript } from '../../utils.js'; let commonFixtures = { - '.prettierrc.js': await fixture('.prettierrc.js'), + '.prettierrc.js': await readFixture('.prettierrc.js'), }; describe('custom locations', () => { diff --git a/tests/smoke-tests/within-existing-monorepo/defaults.test.ts b/tests/smoke-tests/within-existing-monorepo/defaults.test.ts index b543eef2..b507f088 100644 --- a/tests/smoke-tests/within-existing-monorepo/defaults.test.ts +++ b/tests/smoke-tests/within-existing-monorepo/defaults.test.ts @@ -4,17 +4,17 @@ import path from 'node:path'; import { afterAll, beforeAll, describe, expect, it } from 'vitest'; import { assertGeneratedCorrectly } from '../../assertions.js'; +import { readFixture } from '../../fixtures.js'; import { createAddon, createTmp, - fixture, install, runScript, SUPPORTED_PACKAGE_MANAGERS, } from '../../utils.js'; let commonFixtures = { - '.prettierrc.js': await fixture('.prettierrc.js'), + '.prettierrc.js': await readFixture('.prettierrc.js'), }; for (let packageManager of SUPPORTED_PACKAGE_MANAGERS) { diff --git a/tests/test-helpers.ts b/tests/test-helpers.ts new file mode 100644 index 00000000..d6dcf1b8 --- /dev/null +++ b/tests/test-helpers.ts @@ -0,0 +1,100 @@ +import assert from 'node:assert'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +import { matchesFixture } from './assertions.js'; +import { copyFixture } from './fixtures.js'; +import { createAddon, createTmp, install, runScript } from './utils.js'; + +const DEBUG = process.env.DEBUG === 'true'; + +/** + * Helps with common addon testing concerns. + * + * To DEBUG the intermediate output (in tmp), + * re-start your tests with `DEBUG=true`, and the tmpdir will be printed + * as well as the `clean` function will not run so that if a test finishes, + * you can still inspect the folder contents + * + */ +export class AddonHelper { + #cwd?: string; + #tmpDir?: string; + #scenario: string; + #packageManager: 'npm' | 'pnpm' | 'yarn'; + #args: string[]; + + constructor(options: { + args?: string[]; + scenario?: string; + packageManager: 'pnpm' | 'npm' | 'yarn'; + }) { + this.#args = options.args || []; + this.#scenario = options.scenario || 'default'; + this.#packageManager = options.packageManager; + } + + async setup() { + this.#tmpDir = await createTmp(); + + if (DEBUG) { + console.debug(`Debug test repo at ${this.#tmpDir}`); + } + + let { name } = await createAddon({ + args: this.#args, + options: { cwd: this.#tmpDir }, + }); + + this.#cwd = path.join(this.#tmpDir, name); + } + + async clean() { + if (DEBUG) return; + + assert( + this.#tmpDir, + "Cannot clean without a tmpDir. Was the Helper's `setup` method called to generate the addon?" + ); + + await fs.rm(this.#tmpDir, { recursive: true, force: true }); + } + + async installDeps() { + await install({ cwd: this.cwd, packageManager: this.#packageManager, skipPrepare: true }); + } + + get cwd() { + assert(this.#cwd, "Cannot get cwd. Was the Helper's `setup` method called?"); + + return this.#cwd; + } +} + +export class AddonFixtureHelper { + #cwd: string; + #scenario: string; + #packageManager: 'npm' | 'pnpm' | 'yarn'; + + constructor(options: { + cwd: string; + scenario?: string; + packageManager: 'pnpm' | 'npm' | 'yarn'; + }) { + this.#cwd = options.cwd; + this.#scenario = options.scenario || 'default'; + this.#packageManager = options.packageManager; + } + + async use(file: string) { + await copyFixture(file, { scenario: this.#scenario, cwd: this.#cwd }); + } + + async build() { + await runScript({ cwd: this.#cwd, script: 'build', packageManager: this.#packageManager }); + } + + async matches(outputFile: string) { + await matchesFixture(outputFile, { scenario: this.#scenario, cwd: this.#cwd }); + } +} diff --git a/tests/utils.ts b/tests/utils.ts index 69b32aab..2067da71 100644 --- a/tests/utils.ts +++ b/tests/utils.ts @@ -1,6 +1,5 @@ import { type Options, execa } from 'execa'; import fse from 'fs-extra'; -import assert from 'node:assert'; import fs from 'node:fs/promises'; import os from 'node:os'; import path from 'node:path'; @@ -9,45 +8,9 @@ import { fileURLToPath } from 'node:url'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const blueprintPath = path.join(__dirname, '..'); -const fixturesPath = path.join(__dirname, 'fixtures'); export const SUPPORTED_PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm'] as const; -/** - * Returns the contents of a file from the "tests/fixtures" directory. - * The "tests/fixtures" directory contains sub-directories, "scenarios". - * This is we can have different sets of fixtures, depending on what we're testing. - * - * The default scenario is "default", and represents the the file contents when we provide - * no arguments to the blueprint - */ -export async function readFixture( - /** - * Which file within in the fixture-set / scenario to read - */ - file: string, - options?: { - /** - * Which fixture set to use - */ - scenario?: string; - } -) { - let scenario = options?.scenario ?? 'default'; - let fixtureFilePath = path.isAbsolute(file) ? file : path.join(fixturesPath, scenario, file); - - let exists = await fse.pathExists(fixtureFilePath); - - assert( - exists, - `Fixture file '${file}' does not exist. To make this work, place a new file '${file}' in the 'tests/fixtures/${scenario}' directory. Checked the absolute path: '${fixtureFilePath}'.` - ); - - let contents = await fs.readFile(fixtureFilePath); - - return contents.toString(); -} - export async function createTmp() { let prefix = 'v2-addon-blueprint--'; let prefixPath = path.join(os.tmpdir(), prefix);