Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start testing component compilation #154

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ jobs:
# so we could be sure we don't miss anything
# and then generate this list from a previous C.I. job
slow-test:
- components

- defaults with npm
- defaults with yarn
- defaults with pnpm
Expand Down
3 changes: 3 additions & 0 deletions tests/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Editors may run prettier per-workspace, which would ignore
# the top-level workspaces' prettier config.
fixtures/
106 changes: 106 additions & 0 deletions tests/build-tests/components.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { execa } from 'execa';
import fs from 'node:fs/promises';
import path from 'node:path';
import { afterEach, beforeEach, describe, it } from 'vitest';

import { AddonFixtureHelper } from '../fixture-helper.js';
import { createAddon, createTmp, install } from '../utils.js';

let packageManager = 'pnpm';

describe('components (build)', () => {
describe('co-located JS', () => {
let cwd = '';
let tmpDir = '';
let addonOnlyJS: AddonFixtureHelper;

beforeEach(async () => {
tmpDir = await createTmp();

console.debug(`Debug test repo at ${tmpDir}`);

let { name } = await createAddon({
args: [`--pnpm=true`, '--addon-only'],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we doing this for addon-only here? When setting up new tests, shouldn't we just start with the default scenario first?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

related to here i was focusing on compiled output, rather than e2e "workingness", if that makes sense -- I'm going to shift to the default scenario tho, and make sure that each type of component can be rendered in the test-app

options: { cwd: tmpDir },
});

cwd = path.join(tmpDir, name);
Comment on lines +22 to +27
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, and unrelated to this PR really, but as I'm seeing this code here... createAddon receives tmpDir, and returns name, so has everything to compute cwd, so could we do this to simplify this a bit?

Suggested change
let { name } = await createAddon({
args: [`--pnpm=true`, '--addon-only'],
options: { cwd: tmpDir },
});
cwd = path.join(tmpDir, name);
let { name, addonDir: cwd } = await createAddon({
args: [`--pnpm=true`, '--addon-only'],
options: { cwd: tmpDir },
});


addonOnlyJS = new AddonFixtureHelper({
cwd,
packageManager: 'pnpm',
scenario: 'addon-only-js',
});

await install({ cwd, packageManager, skipPrepare: true });

// NOTE: This isn't needed to make the tests pass atm, but it would be
// required when consumers try to use these compiled components
await execa('pnpm', ['add', '--save-peer', '@glimmer/component'], { cwd });
});

afterEach(async () => {
fs.rm(tmpDir, { recursive: true, force: true });
});

it('generates correct files with no template', async () => {
await addonOnlyJS.use('src/components/no-template.js');
await addonOnlyJS.build();
await addonOnlyJS.matches('dist/components/no-template.js');
Copy link
Collaborator

@simonihmig simonihmig Jul 16, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh I am not sure if I would want these kind of snapshot tests against dist-output. Wouldn't these test become super brittle? Like you could update something rollup or babel dependency, which makes the out put slightly different but still perfectly valid, but still you would have tests failing. Especially annoying if you have to fix renovate PRs by hand all the time...

I do see value for fixture-based tests for all the blueprint-generated files, especially those whose content cannot be covered by smoke tests, like e.g. correct path, package manager calls etc. in the generated CONTRIBUTING.md.

But for the dist-output we don't have to care what the content really is, we only need to care if it works as expected. And we could cover this by making our smoke tests more detailed, like having different forms of components (TO, colocated, <template> and whatnot), and having a test (by means of a fixture?) in the generated addon that would cover this when running the smoke test, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, these are good points -- my thinking with these tests is that they'd be faster than full smoke tests.

But maybe it's still worth it?
Because ultimately we do still want to make sure that each compiled component can be rendered.

I will adjust to more e2e-style

});

it('generates correct files with a template', async () => {
await addonOnlyJS.use('src/components/co-located.js');
await addonOnlyJS.use('src/components/co-located.hbs');
await addonOnlyJS.build();
await addonOnlyJS.matches('dist/components/co-located.js');
});
});

describe('co-located TS', () => {
let cwd = '';
let tmpDir = '';
let addonOnlyTS: AddonFixtureHelper;

beforeEach(async () => {
tmpDir = await createTmp();

console.debug(`Debug test repo at ${tmpDir}`);

let { name } = await createAddon({
args: [`--pnpm=true`, '--addon-only', '--typescript'],
options: { cwd: tmpDir },
});

cwd = path.join(tmpDir, name);

addonOnlyTS = new AddonFixtureHelper({
cwd,
packageManager: 'pnpm',
scenario: 'addon-only-ts',
});

await install({ cwd, packageManager, skipPrepare: true });
await execa('pnpm', ['add', '--save-peer', '@glimmer/component'], { cwd });
});

afterEach(async () => {
fs.rm(tmpDir, { recursive: true, force: true });
});

it('generates correct files with no template', async () => {
await addonOnlyTS.use('src/components/no-template.ts');
await addonOnlyTS.build();
await addonOnlyTS.matches('dist/components/no-template.js');
await addonOnlyTS.matches('declarations/components/no-template.d.ts');
});

it('generates correct files with a template', async () => {
await addonOnlyTS.use('src/components/co-located.ts');
await addonOnlyTS.use('src/components/co-located.hbs');
await addonOnlyTS.build();
await addonOnlyTS.matches('dist/components/co-located.js');
await addonOnlyTS.matches('declarations/components/co-located.d.ts');
});
});
});
30 changes: 30 additions & 0 deletions tests/fixture-helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { matchesFixture } from './assertions.js';
import { copyFixture, runScript } from './utils.js';

export class AddonFixtureHelper {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to overcomplicate things, but with #151 providing some testing infrastructure, should't this file and copyFixture ideally been part of that PR rather? 😅

Another though here, and I am totally fine ignoring this for now, like doing later or not doing at all, but given we have a more elaborate testing class here (i.e. it own some state), does it make sense to integrate createAddon into this? Like rename to e.g. TestAddonBuilder and do the addon creation as part of the constructor? Just thinking aloud here...

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should't this file and copyFixture ideally been part of that PR rather?

yes! I will move it over there -- I have a fear of making PRs too big -- especially when utils-prs really need usages to see how the utils will be used / what their purpose is.

does it make sense to integrate createAddon into this? Like rename to e.g. TestAddonBuilder and do the addon creation as part of the constructor?

I really like this idea 🎉 will do

#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 });
}
}
13 changes: 13 additions & 0 deletions tests/fixtures/addon-only-js/dist/components/co-located.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions tests/fixtures/addon-only-js/dist/components/no-template.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tests/fixtures/addon-only-js/src/components/co-located.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{{this.someGetter}}
7 changes: 7 additions & 0 deletions tests/fixtures/addon-only-js/src/components/co-located.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Component from '@glimmer/component';

export class CoLocated extends Component {
get someGetter() {
return 'someGetter';
}
}
7 changes: 7 additions & 0 deletions tests/fixtures/addon-only-js/src/components/no-template.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import Component from '@glimmer/component';

export class NoTemplate extends Component {
get someGetter() {
return 'someGetter';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Component from '@glimmer/component';
interface Signature {
Args: {};
Blocks: {
default: [];
};
}
export default class CoLocated extends Component<Signature> {
get someGetter(): string;
}
export {};
//# sourceMappingURL=co-located.d.ts.map
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Component from '@glimmer/component';
interface Signature {
Args: {};
Blocks: {};
}
export declare class NoTemplate extends Component<Signature> {
get someGetter(): string;
}
export {};
//# sourceMappingURL=no-template.d.ts.map
15 changes: 15 additions & 0 deletions tests/fixtures/addon-only-ts/dist/components/co-located.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions tests/fixtures/addon-only-ts/dist/components/no-template.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions tests/fixtures/addon-only-ts/src/components/co-located.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{{this.someGetter}}

{{yield}}
12 changes: 12 additions & 0 deletions tests/fixtures/addon-only-ts/src/components/co-located.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Component from '@glimmer/component';

interface Signature {
Args: {},
Blocks: { default: [] }
}

export default class CoLocated extends Component<Signature> {
get someGetter() {
return 'someGetter';
}
}
12 changes: 12 additions & 0 deletions tests/fixtures/addon-only-ts/src/components/no-template.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import Component from '@glimmer/component';

interface Signature {
Args: {},
Blocks: {}
}

export class NoTemplate extends Component<Signature> {
get someGetter() {
return 'someGetter';
}
}
3 changes: 2 additions & 1 deletion tests/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@

// Vite's internal types aren't relevant to us
"skipLibCheck": true
}
},
"exclude": ["fixtures/"]
}
37 changes: 37 additions & 0 deletions tests/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,43 @@ const fixturesPath = path.join(__dirname, 'fixtures');

export const SUPPORTED_PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm'] as const;

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 fixture(fixtureFile, { scenario });

await fse.mkdir(path.dirname(newFile), { recursive: true });
await fs.writeFile(newFile, fixtureContents);
}

export async function fixture(
/**
* Which file within in the fixture-set / scenario to read
Expand Down