Skip to content

Commit

Permalink
Add a test for testing the watch mode behavior and expectations of our
Browse files Browse the repository at this point in the history
rollup plugins
  • Loading branch information
NullVoxPopuli committed May 18, 2023
1 parent 7d18d20 commit d3a8b31
Showing 1 changed file with 312 additions and 0 deletions.
312 changes: 312 additions & 0 deletions tests/scenarios/v2-addon-dev-watch-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
import path from 'path';
import { baseV2Addon } from './scenarios';
import { PreparedApp, Scenarios } from 'scenario-tester';
import fs from 'node:fs/promises';
import { spawn } from 'node:child_process';
import QUnit from 'qunit';
import merge from 'lodash/merge';

const { module: Qmodule, test } = QUnit;

Scenarios.fromProject(() => baseV2Addon())
.map('v2-addon-dev-watch', async addon => {
addon.pkg.name = 'v2-addon';
addon.pkg.files = ['dist'];
addon.pkg.exports = {
'./*': './dist/*.js',
'./addon-main.js': './addon-main.js',
'./package.json': './package.json',
};
addon.pkg.scripts = {
build: 'node ./node_modules/rollup/dist/bin/rollup -c ./rollup.config.mjs',
start: 'node ./node_modules/rollup/dist/bin/rollup -c ./rollup.config.mjs --watch',
};

merge(addon.files, {
'babel.config.json': `
{
"presets": [
["@babel/preset-env", {
"targets": ["last 1 firefox versions"]
}]
],
"plugins": [
"@embroider/addon-dev/template-colocation-plugin",
["babel-plugin-ember-template-compilation"],
["@babel/plugin-proposal-decorators", { "legacy": true }],
[ "@babel/plugin-proposal-class-properties" ]
]
}
`,
'rollup.config.mjs': `
import { babel } from '@rollup/plugin-babel';
import { Addon } from '@embroider/addon-dev/rollup';
const addon = new Addon({
srcDir: 'src',
destDir: 'dist',
});
const reexportMappings = {
'components/demo/namespace-me.js': 'components/demo/namespace/namespace-me.js',
};
export default {
output: addon.output(),
plugins: [
addon.publicEntrypoints([
'components/**/*.js',
]),
addon.appReexports([
'components/demo/index.js',
'components/demo/out.js',
'components/demo/namespace-me.js',
], {
mapFilename: (name) => reexportMappings[name] || name,
}),
addon.hbs(),
addon.dependencies(),
babel({ babelHelpers: 'bundled' }),
addon.clean(),
],
};
`,
src: {
components: {
demo: {
'button.hbs': `
<button {{on 'click' @onClick}}>
flip
</button>
`,
'out.hbs': `
<out>{{yield}}</out>
`,
'namespace-me.hbs': `
namespaced component
`,
'index.js': `
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import FlipButton from './button';
import Out from './out';
export default class ExampleComponent extends Component {
Button = FlipButton;
Out = Out;
@tracked active = false;
flip = () => (this.active = !this.active);
}
`,
'index.hbs': `Hello there!
<this.Out>{{this.active}}</this.Out>
<this.Button @onClick={{this.flip}} />
<div data-test="should-transform">{{transformMe}}</div>
`,
},
},
},
});

addon.linkDependency('@embroider/addon-shim', { baseDir: __dirname });
addon.linkDependency('@embroider/addon-dev', { baseDir: __dirname });
addon.linkDependency('babel-plugin-ember-template-compilation', { baseDir: __dirname });
addon.linkDevDependency('@babel/core', { baseDir: __dirname });
addon.linkDevDependency('@babel/plugin-proposal-class-properties', { baseDir: __dirname });
addon.linkDevDependency('@babel/plugin-proposal-decorators', { baseDir: __dirname });
addon.linkDevDependency('@babel/preset-env', { baseDir: __dirname });
addon.linkDevDependency('@rollup/plugin-babel', { baseDir: __dirname });
addon.linkDevDependency('rollup', { baseDir: __dirname });
})
.forEachScenario(scenario => {
Qmodule(scenario.name, function (hooks) {
let addon: PreparedApp;
let watcher: DevWatcher | undefined;

hooks.before(async () => {
addon = await scenario.prepare();

// run the build *once* so we have a known stable state
await addon.execute('pnpm build');
});

hooks.afterEach(async () => {
watcher?.stop();
});

Qmodule('Watching the addon via rollup -c -w', function () {
test('the package.json is not updated since it would be the same', async function (assert) {
watcher = new DevWatcher(addon);

await watcher.start();

let someFile = path.join(addon.dir, 'src/components/demo/index.hbs');
let manifestPath = path.join(addon.dir, 'package.json');

await isNotModified({
filePath: manifestPath,
assert,
fn: async () => {
let someContent = await fs.readFile(someFile);

// generally it's bad to introduce time dependencies to a test, but we need to wait long enough
// to guess for how long it'll take for the file system to update our file.
//
// the `stat` is measured in `ms`, so waiting *a lot more*, should be pretty safe.
await aBit(100);
await fs.writeFile(someFile, someContent);
},
});
});

test('the package.json *is* updated, since app-js changed', async function (assert) {
watcher = new DevWatcher(addon);

await watcher.start();

let someFile = path.join(addon.dir, 'src/components/demo/index.hbs');
let manifestPath = path.join(addon.dir, 'package.json');

await becomesModified({
filePath: manifestPath,
assert,
fn: async () => {
let someContent = await fs.readFile(someFile);

await fs.rm(someFile);
await watcher?.settled();

// generally it's bad to introduce time dependencies to a test, but we need to wait long enough
// to guess for how long it'll take for the file system to update our file.
//
// the `stat` is measured in `ms`, so waiting *a lot more*, should be pretty safe.
await aBit(100);
await fs.writeFile(someFile, someContent);
},
});
});

test('the package.json *is* updated, since public assets changed', async function (assert) {
watcher = new DevWatcher(addon);

await watcher.start();

let someFile = path.join(addon.dir, 'src/components/demo/index.hbs');
let manifestPath = path.join(addon.dir, 'package.json');

await becomesModified({
filePath: manifestPath,
assert,
fn: async () => {
let someContent = await fs.readFile(someFile);

await fs.rm(someFile);
await watcher?.settled();

// generally it's bad to introduce time dependencies to a test, but we need to wait long enough
// to guess for how long it'll take for the file system to update our file.
//
// the `stat` is measured in `ms`, so waiting *a lot more*, should be pretty safe.
await aBit(100);
await fs.writeFile(someFile, someContent);
},
});
});
});
});
});

class DevWatcher {
#addon: PreparedApp;
#singletonAbort?: AbortController;
#waitForBuildPromise?: Promise<unknown>;
#lastBuild?: string;

constructor(addon: PreparedApp) {
this.#addon = addon;
}

start = () => {
if (this.#singletonAbort) this.#singletonAbort.abort();

this.#singletonAbort = new AbortController();

// Use execa, because addon.execute returns a normal promise, rather than an inspectable promise-like
// potential feature enhancement?
let process = spawn('pnpm', ['start'], { cwd: this.#addon.dir, signal: this.#singletonAbort.signal });

let settle: (...args: unknown[]) => void;
this.#waitForBuildPromise = new Promise((resolve, _reject) => {
settle = resolve;
});

if (!process.stdout) {
throw new Error(`Failed to start process, pnpm start`);
}

process.stdout.on('data', data => {
// TODO:
console.log('=> data', data.toString());
setTimeout(() => settle?.(), 7000);
});

return this.settled();
};

stop = () => this.#singletonAbort?.abort();
settled = () => this.#waitForBuildPromise;
get lastBuild() {
return this.#lastBuild;
}
}

async function becomesModified({
filePath,
assert,
fn,
}: {
filePath: string;
assert: Assert;
fn: () => Promise<void>;
}) {
let oldStat = (await fs.stat(filePath)).mtimeMs;

await fn();

let newStat = (await fs.stat(filePath)).mtimeMs;

assert.notStrictEqual(
oldStat,
newStat,
`Expected ${filePath} to be modified. Latest: ${newStat}, previously: ${oldStat}`
);
}

async function isNotModified({ filePath, assert, fn }: { filePath: string; assert: Assert; fn: () => Promise<void> }) {
let oldStat = (await fs.stat(filePath)).mtimeMs;

await fn();

let newStat = (await fs.stat(filePath)).mtimeMs;

assert.strictEqual(
oldStat,
newStat,
`Expected ${filePath} to be unchanged. Latest: ${newStat}, and pre-fn: ${oldStat}`
);
}

function aBit(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}

0 comments on commit d3a8b31

Please sign in to comment.