From 16a4df9e2f11a8f51f94e611b79c3bbbf8baa56b Mon Sep 17 00:00:00 2001 From: Austin Fahsl Date: Wed, 28 Dec 2022 12:00:59 -0600 Subject: [PATCH] chore(watch): add e2e tests --- e2e/watch/.eslintrc.json | 18 ++ e2e/watch/jest.config.ts | 18 ++ e2e/watch/project.json | 52 ++++++ e2e/watch/src/test-setup.ts | 1 + e2e/watch/src/watch.spec.ts | 282 ++++++++++++++++++++++++++++++ e2e/watch/tsconfig.json | 16 ++ e2e/watch/tsconfig.lib.json | 10 ++ e2e/watch/tsconfig.spec.json | 9 + libs/e2e-utils/src/index.ts | 1 + libs/e2e-utils/src/lib/fixture.ts | 62 +++++++ libs/e2e-utils/src/lib/wait.ts | 1 + 11 files changed, 470 insertions(+) create mode 100644 e2e/watch/.eslintrc.json create mode 100644 e2e/watch/jest.config.ts create mode 100644 e2e/watch/project.json create mode 100644 e2e/watch/src/test-setup.ts create mode 100644 e2e/watch/src/watch.spec.ts create mode 100644 e2e/watch/tsconfig.json create mode 100644 e2e/watch/tsconfig.lib.json create mode 100644 e2e/watch/tsconfig.spec.json create mode 100644 libs/e2e-utils/src/lib/wait.ts diff --git a/e2e/watch/.eslintrc.json b/e2e/watch/.eslintrc.json new file mode 100644 index 00000000000..9d9c0db55bb --- /dev/null +++ b/e2e/watch/.eslintrc.json @@ -0,0 +1,18 @@ +{ + "extends": ["../../.eslintrc.json"], + "ignorePatterns": ["!**/*"], + "overrides": [ + { + "files": ["*.ts", "*.tsx", "*.js", "*.jsx"], + "rules": {} + }, + { + "files": ["*.ts", "*.tsx"], + "rules": {} + }, + { + "files": ["*.js", "*.jsx"], + "rules": {} + } + ] +} diff --git a/e2e/watch/jest.config.ts b/e2e/watch/jest.config.ts new file mode 100644 index 00000000000..96570116283 --- /dev/null +++ b/e2e/watch/jest.config.ts @@ -0,0 +1,18 @@ +/* eslint-disable */ +export default { + displayName: "e2e-watch", + preset: "../../jest.preset.js", + globals: { + "ts-jest": { + tsconfig: "/tsconfig.spec.json", + }, + }, + transform: { + "^.+\\.[tj]s$": "ts-jest", + }, + moduleFileExtensions: ["ts", "js", "html"], + coverageDirectory: "../../coverage/e2e/watch", + maxWorkers: 1, + testTimeout: 60000, + setupFiles: ["/src/test-setup.ts"], +}; diff --git a/e2e/watch/project.json b/e2e/watch/project.json new file mode 100644 index 00000000000..4ec503bf79c --- /dev/null +++ b/e2e/watch/project.json @@ -0,0 +1,52 @@ +{ + "name": "e2e-watch", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "tags": [], + "targets": { + "e2e": { + "executor": "nx:run-commands", + "options": { + "commands": [ + { + "command": "npm run e2e-start-local-registry" + }, + { + "command": "npm run e2e-build-package-publish" + }, + { + "command": "E2E_ROOT=$(npx ts-node scripts/set-e2e-root.ts) nx run-e2e-tests e2e-watch" + } + ], + "parallel": false + } + }, + "run-e2e-tests-process": { + "executor": "nx:run-commands", + "options": { + "commands": [ + { + "command": "E2E_ROOT=$(npx ts-node scripts/set-e2e-root.ts) nx run-e2e-tests e2e-watch", + "description": "This additional wrapper target exists so that we can ensure that the e2e tests run in a dedicated process with enough memory" + } + ], + "parallel": false + } + }, + "run-e2e-tests": { + "executor": "@nrwl/jest:jest", + "options": { + "jestConfig": "e2e/watch/jest.config.ts", + "passWithNoTests": true, + "runInBand": true + }, + "outputs": ["{workspaceRoot}/coverage/e2e/watch"] + }, + "lint": { + "executor": "@nrwl/linter:eslint", + "outputs": ["{options.outputFile}"], + "options": { + "lintFilePatterns": ["e2e/watch/**/*.ts"] + } + } + } +} diff --git a/e2e/watch/src/test-setup.ts b/e2e/watch/src/test-setup.ts new file mode 100644 index 00000000000..bb0b4613b66 --- /dev/null +++ b/e2e/watch/src/test-setup.ts @@ -0,0 +1 @@ +jest.retryTimes(3); diff --git a/e2e/watch/src/watch.spec.ts b/e2e/watch/src/watch.spec.ts new file mode 100644 index 00000000000..08159b7c4de --- /dev/null +++ b/e2e/watch/src/watch.spec.ts @@ -0,0 +1,282 @@ +import { Fixture, normalizeEnvironment, wait } from "@lerna/e2e-utils"; +import { createFile } from "fs-extra"; + +expect.addSnapshotSerializer({ + serialize(str) { + return normalizeEnvironment(str); + }, + test(val) { + return val != null && typeof val === "string"; + }, +}); + +describe("lerna-watch", () => { + let fixture: Fixture; + + beforeEach(async () => { + fixture = await Fixture.create({ + e2eRoot: process.env.E2E_ROOT, + name: "lerna-watch", + packageManager: "npm", + initializeGit: true, + runLernaInit: true, + installDependencies: true, + }); + + await fixture.lerna("create package-a -y"); + await fixture.lerna("create package-b --dependencies package-a -y"); + await fixture.lerna("create package-c -y"); + + await fixture.createInitialGitCommit(); + }); + + afterAll(() => fixture.destroy()); + + it("should watch all packages by default", async () => { + const getWatchResult = await fixture.lernaWatch('-- "echo watch triggered"'); + + await createFile(fixture.getWorkspacePath("packages/package-a/my-file.txt")); + await wait(200); + + await createFile(fixture.getWorkspacePath("packages/package-b/my-file.txt")); + await wait(200); + + await createFile(fixture.getWorkspacePath("packages/package-c/my-file.txt")); + await wait(200); + + const output = await getWatchResult(); + + expect(output.combinedOutput).toMatchInlineSnapshot(` + lerna notice cli v999.9.9-e2e.0 + lerna verb rootPath /tmp/lerna-e2e/lerna-watch/lerna-workspace + lerna info watch Executing command "echo watch triggered" on changes in 3 packages. + + > NX running with args: {"command":"echo watch triggered","projectNameEnvName":"LERNA_PACKAGE_NAME","fileChangesEnvName":"LERNA_FILE_CHANGES","includeDependentProjects":false,"projects":["package-a","package-b","package-c"],"verbose":true} + + + > NX starting watch process + + + > NX watch process waiting... + + + > NX about to run commands with these environments: [{"LERNA_PACKAGE_NAME":"","LERNA_FILE_CHANGES":"packages/package-a/my-file.txt"}] + + watch triggered + + > NX running complete, processing the next batch + + + > NX no more commands to process + + + > NX about to run commands with these environments: [{"LERNA_PACKAGE_NAME":"","LERNA_FILE_CHANGES":"packages/package-b/my-file.txt"}] + + watch triggered + + > NX running complete, processing the next batch + + + > NX no more commands to process + + + > NX about to run commands with these environments: [{"LERNA_PACKAGE_NAME":"","LERNA_FILE_CHANGES":"packages/package-c/my-file.txt"}] + + watch triggered + + > NX running complete, processing the next batch + + + > NX no more commands to process + + + `); + }); + + describe("with --scope", () => { + it("should only watch only specified packages", async () => { + const getWatchResult = await fixture.lernaWatch( + '--scope="package-a" --scope="package-c" -- "echo watch triggered"' + ); + + await createFile(fixture.getWorkspacePath("packages/package-a/my-file.txt")); + await wait(200); + + await createFile(fixture.getWorkspacePath("packages/package-b/my-file.txt")); + await wait(200); + + await createFile(fixture.getWorkspacePath("packages/package-c/my-file.txt")); + await wait(200); + + const output = await getWatchResult(); + + expect(output.combinedOutput).toMatchInlineSnapshot(` + lerna notice cli v999.9.9-e2e.0 + lerna verb rootPath /tmp/lerna-e2e/lerna-watch/lerna-workspace + lerna notice filter including ["package-a","package-c"] + lerna info filter [ 'package-a', 'package-c' ] + lerna info watch Executing command "echo watch triggered" on changes in 2 packages. + + > NX running with args: {"command":"echo watch triggered","projectNameEnvName":"LERNA_PACKAGE_NAME","fileChangesEnvName":"LERNA_FILE_CHANGES","includeDependentProjects":false,"projects":["package-a","package-c"],"verbose":true} + + + > NX starting watch process + + + > NX watch process waiting... + + + > NX about to run commands with these environments: [{"LERNA_PACKAGE_NAME":"","LERNA_FILE_CHANGES":"packages/package-a/my-file.txt"}] + + watch triggered + + > NX running complete, processing the next batch + + + > NX no more commands to process + + + > NX about to run commands with these environments: [{"LERNA_PACKAGE_NAME":"","LERNA_FILE_CHANGES":"packages/package-c/my-file.txt"}] + + watch triggered + + > NX running complete, processing the next batch + + + > NX no more commands to process + + + `); + }); + describe("and --include-dependencies", () => { + it("should watch one package and its dependencies", async () => { + const getWatchResult = await fixture.lernaWatch( + '--scope="package-b" --include-dependencies -- "echo watch triggered"' + ); + + await createFile(fixture.getWorkspacePath("packages/package-a/my-file.txt")); + await wait(200); + + await createFile(fixture.getWorkspacePath("packages/package-b/my-file.txt")); + await wait(200); + + await createFile(fixture.getWorkspacePath("packages/package-c/my-file.txt")); + await wait(200); + + const output = await getWatchResult(); + + expect(output.combinedOutput).toMatchInlineSnapshot(` + lerna notice cli v999.9.9-e2e.0 + lerna verb rootPath /tmp/lerna-e2e/lerna-watch/lerna-workspace + lerna notice filter including "package-b" + lerna notice filter including dependencies + lerna info filter [ 'package-b' ] + lerna info watch Executing command "echo watch triggered" on changes in 2 packages. + + > NX running with args: {"command":"echo watch triggered","projectNameEnvName":"LERNA_PACKAGE_NAME","fileChangesEnvName":"LERNA_FILE_CHANGES","includeDependentProjects":false,"projects":["package-b","package-a"],"verbose":true} + + + > NX starting watch process + + + > NX watch process waiting... + + + > NX about to run commands with these environments: [{"LERNA_PACKAGE_NAME":"","LERNA_FILE_CHANGES":"packages/package-a/my-file.txt"}] + + watch triggered + + > NX running complete, processing the next batch + + + > NX no more commands to process + + + > NX about to run commands with these environments: [{"LERNA_PACKAGE_NAME":"","LERNA_FILE_CHANGES":"packages/package-b/my-file.txt"}] + + watch triggered + + > NX running complete, processing the next batch + + + > NX no more commands to process + + + `); + }); + }); + }); + + it("should replace package name and changed file names", async () => { + fixture.updateJson("lerna.json", (json) => ({ + ...json, + command: { + watch: { + // This workaround is necessary to prevent $LERNA_PACKAGE_NAME and $LERNA_FILE_CHANGES + // from being replaced by `child_process.spawn`. This is only needed for the e2e tests. + // This test case can be reproduced manually by running: + // `npx lerna watch -- echo $LERNA_PACKAGE_NAME: $LERNA_FILE_CHANGES` + command: "echo $LERNA_PACKAGE_NAME: $LERNA_FILE_CHANGES", + }, + }, + })); + const getWatchResult = await fixture.lernaWatch(""); + + await createFile(fixture.getWorkspacePath("packages/package-a/my-file.txt")); + await wait(200); + + await createFile(fixture.getWorkspacePath("packages/package-b/my-file.txt")); + await wait(200); + + await createFile(fixture.getWorkspacePath("packages/package-c/my-file.txt")); + await wait(200); + + const output = await getWatchResult(); + + expect(output.combinedOutput).toMatchInlineSnapshot(` + lerna notice cli v999.9.9-e2e.0 + lerna verb rootPath /tmp/lerna-e2e/lerna-watch/lerna-workspace + lerna info watch Executing command "echo $LERNA_PACKAGE_NAME: $LERNA_FILE_CHANGES" on changes in 3 packages. + + > NX running with args: {"command":"echo $LERNA_PACKAGE_NAME: $LERNA_FILE_CHANGES","projectNameEnvName":"LERNA_PACKAGE_NAME","fileChangesEnvName":"LERNA_FILE_CHANGES","includeDependentProjects":false,"projects":["package-a","package-b","package-c"],"verbose":true} + + + > NX starting watch process + + + > NX watch process waiting... + + + > NX about to run commands with these environments: [{"LERNA_PACKAGE_NAME":"package-a","LERNA_FILE_CHANGES":"packages/package-a/my-file.txt"}] + + package-a: packages/package-a/my-file.txt + + > NX running complete, processing the next batch + + + > NX no more commands to process + + + > NX about to run commands with these environments: [{"LERNA_PACKAGE_NAME":"package-b","LERNA_FILE_CHANGES":"packages/package-b/my-file.txt"}] + + package-b: packages/package-b/my-file.txt + + > NX running complete, processing the next batch + + + > NX no more commands to process + + + > NX about to run commands with these environments: [{"LERNA_PACKAGE_NAME":"package-c","LERNA_FILE_CHANGES":"packages/package-c/my-file.txt"}] + + package-c: packages/package-c/my-file.txt + + > NX running complete, processing the next batch + + + > NX no more commands to process + + + `); + }); +}); diff --git a/e2e/watch/tsconfig.json b/e2e/watch/tsconfig.json new file mode 100644 index 00000000000..19b9eece4df --- /dev/null +++ b/e2e/watch/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "module": "commonjs" + }, + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/e2e/watch/tsconfig.lib.json b/e2e/watch/tsconfig.lib.json new file mode 100644 index 00000000000..33eca2c2cdf --- /dev/null +++ b/e2e/watch/tsconfig.lib.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "declaration": true, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/e2e/watch/tsconfig.spec.json b/e2e/watch/tsconfig.spec.json new file mode 100644 index 00000000000..3feb8152150 --- /dev/null +++ b/e2e/watch/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts", "src/**/*.d.ts", "src/test-setup.ts"] +} diff --git a/libs/e2e-utils/src/index.ts b/libs/e2e-utils/src/index.ts index 5fa3d0ab327..4f933e9458c 100644 --- a/libs/e2e-utils/src/index.ts +++ b/libs/e2e-utils/src/index.ts @@ -1,2 +1,3 @@ export * from "./lib/fixture"; export * from "./lib/snapshot-serializer-utils"; +export * from "./lib/wait"; diff --git a/libs/e2e-utils/src/lib/fixture.ts b/libs/e2e-utils/src/lib/fixture.ts index 2d50c6b1e21..af880caf3db 100644 --- a/libs/e2e-utils/src/lib/fixture.ts +++ b/libs/e2e-utils/src/lib/fixture.ts @@ -265,6 +265,68 @@ export class Fixture { } } + async lernaWatch(inputArgs: string): Promise<(timeoutMs?: number) => Promise> { + return new Promise((resolve, reject) => { + const command = `npx --offline --no lerna watch --verbose ${inputArgs}`; + + let stdout = ""; + let stderr = ""; + let combinedOutput = ""; + let error: Error | null = null; + + const createResult = (): RunCommandResult => ({ + stdout: stripConsoleColors(stdout), + stderr: stripConsoleColors(stderr), + combinedOutput: stripConsoleColors(combinedOutput), + }); + + const childProcess = spawn(command, { + shell: true, + cwd: this.fixtureWorkspacePath, + env: { + ...process.env, + FORCE_COLOR: "false", + }, + }); + + childProcess.stdout.setEncoding("utf8"); + childProcess.stdout.on("data", (chunk) => { + stdout += chunk; + combinedOutput += chunk; + + if (chunk.toString().trim().includes("watch process waiting")) { + resolve( + (timeoutMs = 6000) => + new Promise((resolve) => { + setTimeout(() => { + childProcess.kill(); + resolve(createResult()); + }, timeoutMs); + }) + ); + } + }); + + childProcess.stderr.setEncoding("utf8"); + childProcess.stderr.on("data", (chunk) => { + stderr += chunk; + combinedOutput += chunk; + }); + + childProcess.on("error", (err) => { + error = err; + }); + + childProcess.on("close", () => { + if (error) { + reject(error); + } else if (stderr.includes("lerna ERR!")) { + reject(new Error(stderr)); + } + }); + }); + } + async addNxJsonToWorkspace(): Promise { writeJsonFile(this.getWorkspacePath("nx.json"), { extends: "nx/presets/npm.json", diff --git a/libs/e2e-utils/src/lib/wait.ts b/libs/e2e-utils/src/lib/wait.ts new file mode 100644 index 00000000000..3b7662bc399 --- /dev/null +++ b/libs/e2e-utils/src/lib/wait.ts @@ -0,0 +1 @@ +export const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));