Skip to content

Commit

Permalink
setting up an addon-creating helper so we can more easily manage the …
Browse files Browse the repository at this point in the history
…creation of addons

will need to propagate through the existing tests
but that can happen in other PRs
  • Loading branch information
NullVoxPopuli committed Jul 17, 2023
1 parent 6ea52bc commit 0e99425
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 58 deletions.
3 changes: 2 additions & 1 deletion tests/assertions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
81 changes: 81 additions & 0 deletions tests/fixtures.ts
Original file line number Diff line number Diff line change
@@ -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);
}
24 changes: 8 additions & 16 deletions tests/smoke-tests/--addon-only.test.ts
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
4 changes: 2 additions & 2 deletions tests/smoke-tests/within-existing-monorepo/defaults.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
100 changes: 100 additions & 0 deletions tests/test-helpers.ts
Original file line number Diff line number Diff line change
@@ -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 });
}
}
37 changes: 0 additions & 37 deletions tests/utils.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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);
Expand Down

0 comments on commit 0e99425

Please sign in to comment.