diff --git a/tests/scenarios/watch-mode-test.ts b/tests/scenarios/watch-mode-test.ts index 8d87d5fbe..d235261eb 100644 --- a/tests/scenarios/watch-mode-test.ts +++ b/tests/scenarios/watch-mode-test.ts @@ -1,11 +1,11 @@ import { appScenarios } from './scenarios'; import type { PreparedApp } from 'scenario-tester'; import QUnit from 'qunit'; -import globby from 'globby'; import fs from 'fs/promises'; import { pathExists } from 'fs-extra'; import path from 'path'; import execa, { type Options, type ExecaChildProcess } from 'execa'; +import { expectFilesAt } from '@embroider/test-support/file-assertions/qunit'; const { module: Qmodule, test } = QUnit; @@ -16,14 +16,14 @@ let app = appScenarios.map('watch-mode', () => { */ }); -abstract class Waiter { - readonly promise: Promise; - protected _resolve!: () => void; +abstract class Waiter { + readonly promise: Promise; + protected _resolve!: (result: T) => void; protected _reject!: (error: unknown) => void; private _timeout = (timeout: number) => this.onTimeout(timeout); constructor(timeout: number | null = DEFAULT_TIMEOUT) { - this.promise = new Promise((resolve, reject) => { + this.promise = new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; }); @@ -33,14 +33,14 @@ abstract class Waiter { } } - abstract onOutputLine(data: string): boolean; + abstract onOutputLine(data: string): T; abstract onExit(code: number): void; abstract onTimeout(timeout: number): void; - protected resolve(): void { + protected resolve(resolveValue: T): void { const resolve = this._resolve; this._resolve = this._reject = this._timeout = () => {}; - resolve(); + resolve(resolveValue); } protected reject(error: unknown): void { @@ -52,15 +52,16 @@ abstract class Waiter { const DEFAULT_TIMEOUT = process.env.CI ? 90000 : 30000; -class OutputWaiter extends Waiter { - constructor(private process: EmberCLI, private output: string | RegExp, timeout?: number | null) { +class OutputWaiter extends Waiter { + constructor(private process: CommandWatcher, private output: string | RegExp, timeout?: number | null) { super(timeout); } - onOutputLine(line: string): boolean { - if (this.matchLine(line)) { - this.resolve(); - return true; + onOutputLine(line: string): boolean | RegExpExecArray { + let matchLine = this.matchLine(line); + if (matchLine) { + this.resolve(matchLine); + return matchLine; } else { return false; } @@ -98,31 +99,37 @@ class OutputWaiter extends Waiter { } } - private matchLine(line: string): boolean { + private matchLine(line: string): boolean | RegExpExecArray { if (typeof this.output === 'string') { return this.output === line; } else { - return this.output.test(line); + let result = this.output.exec(line); + if (!result) { + return false; + } + + return result; } } } type Status = { type: 'running' } | { type: 'errored'; error: unknown } | { type: 'completed' }; -class EmberCLI { - static launch(args: readonly string[], options: Options = {}): EmberCLI { - return new EmberCLI(execa('ember', args, { ...options, all: true })); +class CommandWatcher { + static launch(command: string, args: readonly string[], options: Options = {}): CommandWatcher { + return new CommandWatcher(execa('pnpm', [command, ...args], { ...options, all: true })); } readonly completed: Promise; private status: Status = { type: 'running' }; - private waiters: Waiter[] = []; + private waiters: Waiter[] = []; private lines: string[] = []; constructor(private process: ExecaChildProcess) { process.all!.on('data', data => { const lines = data.toString().split(/\r?\n/); + // console.log(lines); this.lines.push(...lines); for (const line of lines) { this.waiters = this.waiters.filter(waiter => !waiter.onOutputLine(line)); @@ -137,8 +144,8 @@ class EmberCLI { this.waiters = []; }); - const exit = new (class ExitWaiter extends Waiter { - constructor(private process: EmberCLI) { + const exit = new (class ExitWaiter extends Waiter { + constructor(private process: CommandWatcher) { super(null); } @@ -163,7 +170,7 @@ class EmberCLI { onTimeout() {} })(this); - this.waiters.push(exit); + this.waiters.push(exit as Waiter); this.completed = exit.promise .then(() => { @@ -191,17 +198,18 @@ class EmberCLI { return this.lines.join('\n'); } - async waitFor(output: string | RegExp, timeout?: number | null): Promise { + async waitFor(output: string | RegExp, timeout?: number | null): Promise { const waiter = new OutputWaiter(this, output, timeout); for (const line of this.lines) { - if (waiter.onOutputLine(line)) { - return; + let result = waiter.onOutputLine(line); + if (result) { + return result; } } - this.waiters.push(waiter); - await waiter.promise; + this.waiters.push(waiter as Waiter); + return waiter.promise; } clearOutput(): void { @@ -353,14 +361,14 @@ function deindent(s: string): string { app.forEachScenario(scenario => { Qmodule(scenario.name, function (hooks) { let app: PreparedApp; - let server: EmberCLI; + let server: CommandWatcher; function appFile(appPath: string): File { let fullPath = path.join(app.dir, ...appPath.split('/')); return new File(appPath, fullPath); } - async function waitFor(...args: Parameters): Promise { + async function waitFor(...args: Parameters): Promise { await server.waitFor(...args); } @@ -376,23 +384,13 @@ app.forEachScenario(scenario => { await waitFor(`file deleted ${path.join(...filePath.split('/'))}`); } - async function checkScripts(distPattern: RegExp, needle: string) { - let root = app.dir; - let available = await globby('**/*', { cwd: path.join(root, 'dist') }); - - let matchingFiles = available.filter((item: string) => distPattern.test(item)); - let matchingFileContents = await Promise.all( - matchingFiles.map(async (item: string) => { - return fs.readFile(path.join(app.dir, 'dist', item), 'utf8'); - }) - ); - return matchingFileContents.some((item: string) => item.includes(needle)); - } - hooks.beforeEach(async () => { app = await scenario.prepare(); - server = EmberCLI.launch(['serve', '--port', '0'], { cwd: app.dir }); - await waitFor(/Serving on http:\/\/localhost:[0-9]+\//, DEFAULT_TIMEOUT * 2); + server = CommandWatcher.launch('vite', ['--clearScreen', 'false'], { cwd: app.dir }); + console.log('waiting for serving'); + const result = await waitFor(/Local: http:\/\/127.0.0.1:(\d+)\//, DEFAULT_TIMEOUT * 2); + console.log(result); + console.log('got servig'); server.clearOutput(); }); @@ -400,28 +398,43 @@ app.forEachScenario(scenario => { await server.shutdown(); }); + // async function checkPath(path: string, needle: string) { + // let root = app.dir; + // let available = await globby('**/*', { cwd: path.join(root, 'dist') }); + + // let matchingFiles = available.filter((item: string) => distPattern.test(item)); + // let matchingFileContents = await Promise.all( + // matchingFiles.map(async (item: string) => { + // return fs.readFile(path.join(app.dir, 'dist', item), 'utf8'); + // }) + // ); + // return matchingFileContents.some((item: string) => item.includes(needle)); + // } + + function getFiles(assert: Assert) { + return expectFilesAt(path.join(app.dir, 'node_modules', '.embroider', 'rewritten-app'), { qunit: assert }); + } + test(`ember serve`, async function (assert) { const originalContent = 'TWO IS A GREAT NUMBER< I LKE IT A LOT< IT IS THE POWER OF ALL OF ELECTRONICS, MATH, ETC'; - assert.false(await checkScripts(/js$/, originalContent), 'file has not been created yet'); + + getFiles(assert)('./assets/app-template.js').doesNotMatch('app-template/simple-file.js'); await appFile('app/simple-file.js').write(`export const two = "${originalContent}";`); await added('simple-file.js'); await waitFor(/Build successful/); - assert.true(await checkScripts(/js$/, originalContent), 'the file now exists'); + getFiles(assert)('./assets/app-template.js').matches('app-template/simple-file.js'); + getFiles(assert)('./simple-file.js').matches(originalContent); server.clearOutput(); const updatedContent = 'THREE IS A GREAT NUMBER TWO'; - assert.false(await checkScripts(/js$/, updatedContent), 'file has not been created yet'); - await appFile('app/simple-file.js').write(`export const two = "${updatedContent}";`); await changed('simple-file.js'); await waitFor(/Build successful/); - // TODO: find a better way to test this; this seems to linger around - // assert.false(await checkScripts(/js$/, originalContent), 'the original file does not exists'); - assert.true(await checkScripts(/js$/, updatedContent), 'the updated file now exists'); + getFiles(assert)('./simple-file.js').matches(updatedContent); }); Qmodule('[GH#1619] co-located components regressions', function (hooks) { @@ -508,7 +521,9 @@ app.forEachScenario(scenario => { await assertRewrittenFile('tests/integration/hello-world-test.js').includesContent(''); server.clearOutput(); - let test = await EmberCLI.launch(['test', '--filter', 'hello-world'], { cwd: app.dir }); + let test = await CommandWatcher.launch('ember', ['test', '--filter', 'hello-world', '--path', 'dist'], { + cwd: app.dir, + }); await test.waitFor(/^not ok .+ Integration | hello-world: it renders/, DEFAULT_TIMEOUT * 2); await assert.rejects(test.completed); @@ -523,7 +538,9 @@ app.forEachScenario(scenario => { `); await assertRewrittenFile('tests/integration/hello-world-test.js').includesContent(''); - test = await EmberCLI.launch(['test', '--filter', 'hello-world'], { cwd: app.dir }); + test = await CommandWatcher.launch('ember', ['test', '--filter', 'hello-world', '--path', 'dist'], { + cwd: app.dir, + }); await test.waitFor(/^ok .+ Integration | hello-world: it renders/); await test.completed; });