diff options
Diffstat (limited to 'remote/test/puppeteer/packages/ng-schematics')
32 files changed, 2608 insertions, 0 deletions
diff --git a/remote/test/puppeteer/packages/ng-schematics/.eslintignore b/remote/test/puppeteer/packages/ng-schematics/.eslintignore new file mode 100644 index 0000000000..69132ca336 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/.eslintignore @@ -0,0 +1,5 @@ +# Ignore File that will be copied to Angular +/files/ + +# Ignore sandbox enviroment +./sandbox/
\ No newline at end of file diff --git a/remote/test/puppeteer/packages/ng-schematics/.gitignore b/remote/test/puppeteer/packages/ng-schematics/.gitignore new file mode 100644 index 0000000000..93d9982369 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/.gitignore @@ -0,0 +1,3 @@ + +# Sandbox +sandbox/
\ No newline at end of file diff --git a/remote/test/puppeteer/packages/ng-schematics/.mocharc.cjs b/remote/test/puppeteer/packages/ng-schematics/.mocharc.cjs new file mode 100644 index 0000000000..be9bc29919 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/.mocharc.cjs @@ -0,0 +1,6 @@ +module.exports = { + logLevel: 'debug', + spec: 'test/build/**/*.spec.js', + exit: !!process.env.CI, + reporter: process.env.CI ? 'spec' : 'dot', +}; diff --git a/remote/test/puppeteer/packages/ng-schematics/CHANGELOG.md b/remote/test/puppeteer/packages/ng-schematics/CHANGELOG.md new file mode 100644 index 0000000000..3cc843df1e --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/CHANGELOG.md @@ -0,0 +1,19 @@ +# Changelog + +## [0.2.0](https://github.com/puppeteer/puppeteer/compare/ng-schematics-v0.1.0...ng-schematics-v0.2.0) (2023-05-02) + + +### ⚠ BREAKING CHANGES + +* drop support for node14 ([#10019](https://github.com/puppeteer/puppeteer/issues/10019)) + +### Features + +* drop support for node14 ([#10019](https://github.com/puppeteer/puppeteer/issues/10019)) ([7405d65](https://github.com/puppeteer/puppeteer/commit/7405d6585aa09b240fbab09aa360674d4442b3d9)) + +## 0.1.0 (2022-11-23) + + +### Features + +* **ng-schematics:** Release @puppeteer/ng-schematics ([#9244](https://github.com/puppeteer/puppeteer/issues/9244)) ([be33929](https://github.com/puppeteer/puppeteer/commit/be33929770e473992ad49029e6d038d36591e108)) diff --git a/remote/test/puppeteer/packages/ng-schematics/README.md b/remote/test/puppeteer/packages/ng-schematics/README.md new file mode 100644 index 0000000000..3efc89f17c --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/README.md @@ -0,0 +1,67 @@ +# Puppeteer Angular Schematic + +Adds Puppeteer-based e2e tests to your Angular project. + +## Usage + +Run the command below in an Angular CLI app directory and follow the prompts. +_Note this will add the schematic as a dependency to your project._ + +```bash +ng add @puppeteer/ng-schematics +``` + +Or you can use the same command followed by the [options](#options) below. + +Currently, this schematic supports the following test frameworks: + +- **Jasmine** [https://jasmine.github.io/] +- **Jest** [https://jestjs.io/] +- **Mocha** [https://mochajs.org/] +- **Node Test Runner** _(Experimental)_ [https://nodejs.org/api/test.html] + +With the schematics installed you can run E2E tests: + +```bash +ng e2e +``` + +> Note: Command spawns it's own server on the same port `ng serve` does. + +## Options + +When adding schematics to your project you can to provide following options: + +| Option | Description | Value | Required | +| -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------ | -------- | +| `--isDefaultTester` | When true, replaces default `ng e2e` command. | `boolean` | `true` | +| `--exportConfig` | When true, creates an empty [Puppeteer configuration](https://pptr.dev/guides/configuration) file. (`.puppeteerrc.cjs`) | `boolean` | `true` | +| `--testingFramework` | The testing framework to install along side Puppeteer. | `"jasmine"`, `"jest"`, `"mocha"`, `"node"` | `true` | + +## Contributing + +Check out our [contributing guide](https://pptr.dev/contributing) to get an overview of what you need to develop in the Puppeteer repo. + +### Sandbox + +For easier development we provide a script to auto-generate the Angular project to test against. Simply run: + +```bash +npm run sandbox -- --init +``` + +After that to run `@puppeteer/ng-schematics` against the Sandbox Angular project run: + +```bash +npm run sandbox +# or to auto-build and then run schematics +npm run sandbox -- --build +``` + +### Unit Testing + +The schematics utilize `@angular-devkit/schematics/testing` for verifying correct file creation and `package.json` updates. To execute the test suit: + +```bash +npm run test +``` diff --git a/remote/test/puppeteer/packages/ng-schematics/package-lock.json b/remote/test/puppeteer/packages/ng-schematics/package-lock.json new file mode 100644 index 0000000000..11dd119c2b --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/package-lock.json @@ -0,0 +1,1098 @@ +{ + "name": "angular", + "version": "0.2.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "angular", + "version": "0.2.0", + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "^14.2.6", + "@angular-devkit/schematics": "^14.2.6", + "typescript": "~4.7.2" + }, + "devDependencies": { + "@types/jasmine": "~4.0.0", + "@types/node": "^14.15.0", + "jasmine": "^4.0.0" + } + }, + "node_modules/@angular-devkit/core": { + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.6.tgz", + "integrity": "sha512-qtRSdRm/h7C3ya04PJTDgQXV6mM8Y4RakANX1GTSXetCf9AVSxg74NJX76DWUgiHT4JiPYnJgJU6Hr/L0H6JOQ==", + "dependencies": { + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.1.0", + "rxjs": "6.6.7", + "source-map": "0.7.4" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^3.5.2" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular-devkit/schematics": { + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-14.2.6.tgz", + "integrity": "sha512-mSFtc4M49mWrYsgJx/P6bA6SzXb8SeZqmppKRMoEQxiXI1bwFdGLNWzAmzEsGvS96h/nPIaOfcX5cKJSp++4FA==", + "dependencies": { + "@angular-devkit/core": "14.2.6", + "jsonc-parser": "3.1.0", + "magic-string": "0.26.2", + "ora": "5.4.1", + "rxjs": "6.6.7" + }, + "engines": { + "node": "^14.15.0 || >=16.10.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@types/jasmine": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "14.18.32", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.32.tgz", + "integrity": "sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow==", + "dev": true + }, + "node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jasmine": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.4.0.tgz", + "integrity": "sha512-xrbOyYkkCvgduNw7CKktDtNb+BwwBv/zvQeHpTkbxqQ37AJL5V4sY3jHoMIJPP/hTc3QxLVwOyxc87AqA+kw5g==", + "dev": true, + "dependencies": { + "glob": "^7.1.6", + "jasmine-core": "^4.4.0" + }, + "bin": { + "jasmine": "bin/jasmine.js" + } + }, + "node_modules/jasmine-core": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.4.0.tgz", + "integrity": "sha512-+l482uImx5BVd6brJYlaHe2UwfKoZBqQfNp20ZmdNfsjGFTemGfqHLsXjKEW23w9R/m8WYeFc9JmIgjj6dUtAA==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "node_modules/jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==" + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/magic-string": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", + "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", + "dependencies": { + "sourcemap-codec": "^1.4.8" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "dependencies": { + "tslib": "^1.9.0" + }, + "engines": { + "npm": ">=2.0.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + }, + "dependencies": { + "@angular-devkit/core": { + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-14.2.6.tgz", + "integrity": "sha512-qtRSdRm/h7C3ya04PJTDgQXV6mM8Y4RakANX1GTSXetCf9AVSxg74NJX76DWUgiHT4JiPYnJgJU6Hr/L0H6JOQ==", + "requires": { + "ajv": "8.11.0", + "ajv-formats": "2.1.1", + "jsonc-parser": "3.1.0", + "rxjs": "6.6.7", + "source-map": "0.7.4" + } + }, + "@angular-devkit/schematics": { + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-14.2.6.tgz", + "integrity": "sha512-mSFtc4M49mWrYsgJx/P6bA6SzXb8SeZqmppKRMoEQxiXI1bwFdGLNWzAmzEsGvS96h/nPIaOfcX5cKJSp++4FA==", + "requires": { + "@angular-devkit/core": "14.2.6", + "jsonc-parser": "3.1.0", + "magic-string": "0.26.2", + "ora": "5.4.1", + "rxjs": "6.6.7" + } + }, + "@types/jasmine": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-4.0.3.tgz", + "integrity": "sha512-Opp1LvvEuZdk8fSSvchK2mZwhVrsNT0JgJE9Di6MjnaIpmEXM8TLCPPrVtNTYh8+5MPdY8j9bAHMu2SSfwpZJg==", + "dev": true + }, + "@types/node": { + "version": "14.18.32", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.32.tgz", + "integrity": "sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow==", + "dev": true + }, + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "requires": { + "ajv": "^8.0.0" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==" + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "requires": { + "clone": "^1.0.2" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + }, + "jasmine": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-4.4.0.tgz", + "integrity": "sha512-xrbOyYkkCvgduNw7CKktDtNb+BwwBv/zvQeHpTkbxqQ37AJL5V4sY3jHoMIJPP/hTc3QxLVwOyxc87AqA+kw5g==", + "dev": true, + "requires": { + "glob": "^7.1.6", + "jasmine-core": "^4.4.0" + } + }, + "jasmine-core": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-4.4.0.tgz", + "integrity": "sha512-+l482uImx5BVd6brJYlaHe2UwfKoZBqQfNp20ZmdNfsjGFTemGfqHLsXjKEW23w9R/m8WYeFc9JmIgjj6dUtAA==", + "dev": true + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, + "jsonc-parser": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.1.0.tgz", + "integrity": "sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==" + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "magic-string": { + "version": "0.26.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.26.2.tgz", + "integrity": "sha512-NzzlXpclt5zAbmo6h6jNc8zl2gNRGHvmsZW4IvZhTC4W7k4OlLP+S5YLussa/r3ixNT66KOQfNORlXHSOy/X4A==", + "requires": { + "sourcemap-codec": "^1.4.8" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "source-map": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", + "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" + }, + "sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "requires": { + "defaults": "^1.0.3" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + } + } +} diff --git a/remote/test/puppeteer/packages/ng-schematics/package.json b/remote/test/puppeteer/packages/ng-schematics/package.json new file mode 100644 index 0000000000..e7f645d4d3 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/package.json @@ -0,0 +1,68 @@ +{ + "name": "@puppeteer/ng-schematics", + "version": "0.2.0", + "description": "Puppeteer Angular schematics", + "scripts": { + "build": "wireit", + "clean": "tsc -b --clean && rm -rf lib && rm -rf test/build", + "dev:test": "npm run test --watch", + "dev": "npm run build --watch", + "test": "wireit", + "sandbox": "node tools/sandbox.js" + }, + "wireit": { + "build": { + "command": "tsc -b && node tools/copySchemaFiles.js", + "clean": "if-file-deleted", + "files": [ + "tsconfig.json", + "tsconfig.spec.json", + "src/**", + "test/src/**" + ], + "output": [ + "lib/**", + "test/build/**" + ] + }, + "test": { + "command": "mocha", + "dependencies": [ + "build" + ] + } + }, + "keywords": [ + "angular", + "puppeteer", + "schematics" + ], + "repository": { + "type": "git", + "url": "https://github.com/puppeteer/puppeteer/tree/main/packages/ng-schematics" + }, + "author": "The Chromium Authors", + "license": "Apache-2.0", + "engines": { + "node": ">=16.0.0" + }, + "dependencies": { + "@angular-devkit/architect": "^0.1502.7", + "@angular-devkit/core": "^15.2.7", + "@angular-devkit/schematics": "^15.2.7" + }, + "devDependencies": { + "@types/node": "^14.15.0", + "@schematics/angular": "^14.2.8", + "@angular/cli": "^15.2.2" + }, + "files": [ + "lib", + "!*.tsbuildinfo" + ], + "ng-add": { + "save": "devDependencies" + }, + "schematics": "./lib/schematics/collection.json", + "builders": "./lib/builders/builders.json" +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/builders/builders.json b/remote/test/puppeteer/packages/ng-schematics/src/builders/builders.json new file mode 100644 index 0000000000..41079f7731 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/builders/builders.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../../../node_modules/@angular-devkit/architect/src/builders-schema.json", + "builders": { + "puppeteer": { + "implementation": "./puppeteer", + "schema": "./puppeteer/schema.json", + "description": "Run e2e test with Puppeteer" + } + } +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/builders/puppeteer/index.ts b/remote/test/puppeteer/packages/ng-schematics/src/builders/puppeteer/index.ts new file mode 100644 index 0000000000..45aec95152 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/builders/puppeteer/index.ts @@ -0,0 +1,138 @@ +import {spawn} from 'child_process'; + +import { + createBuilder, + BuilderContext, + BuilderOutput, + targetFromTargetString, + BuilderRun, +} from '@angular-devkit/architect'; +import {JsonObject} from '@angular-devkit/core'; + +import {PuppeteerBuilderOptions} from './types.js'; + +const terminalStyles = { + blue: '\u001b[34m', + green: '\u001b[32m', + bold: '\u001b[1m', + reverse: '\u001b[7m', + clear: '\u001b[0m', +}; + +function getError(executable: string, args: string[]) { + return ( + `Puppeteer E2E tests failed!` + + '\n' + + `Error running '${executable}' with arguments '${args.join(' ')}'.` + + `\n` + + 'Please look at the output above to determine the issue!' + ); +} + +function getExecutable(command: string[]) { + const executable = command.shift()!; + const error = getError(executable, command); + + if (executable === 'node') { + return { + executable: executable, + args: command, + error, + }; + } + + return { + executable: `./node_modules/.bin/${executable}`, + args: command, + error, + }; +} + +async function executeCommand(context: BuilderContext, command: string[]) { + await new Promise((resolve, reject) => { + context.logger.debug(`Trying to execute command - ${command.join(' ')}.`); + const {executable, args, error} = getExecutable(command); + + const child = spawn(executable, args, { + cwd: context.workspaceRoot, + stdio: 'inherit', + }); + + child.on('error', message => { + console.log(message); + reject(error); + }); + + child.on('exit', code => { + if (code === 0) { + resolve(true); + } else { + reject(error); + } + }); + }); +} + +function message( + message: string, + context: BuilderContext, + type: 'info' | 'success' = 'info' +): void { + const color = type === 'info' ? terminalStyles.blue : terminalStyles.green; + context.logger.info( + `${terminalStyles.bold}${terminalStyles.reverse}${color}${message}${terminalStyles.clear}` + ); +} + +async function startServer( + options: PuppeteerBuilderOptions, + context: BuilderContext +): Promise<BuilderRun> { + context.logger.debug('Trying to start server.'); + const target = targetFromTargetString(options.devServerTarget); + const defaultServerOptions = await context.getTargetOptions(target); + + const overrides = { + watch: false, + host: defaultServerOptions['host'], + port: defaultServerOptions['port'], + } as JsonObject; + + message('Spawning test server...\n', context); + const server = await context.scheduleTarget(target, overrides); + const result = await server.result; + if (!result.success) { + throw new Error('Failed to spawn server! Stopping tests...'); + } + + return server; +} + +async function executeE2ETest( + options: PuppeteerBuilderOptions, + context: BuilderContext +): Promise<BuilderOutput> { + let server: BuilderRun | null = null; + try { + server = await startServer(options, context); + + message('\nRunning tests...\n', context); + for (const command of options.commands) { + await executeCommand(context, command); + } + + message('\nTest ran successfully!', context, 'success'); + return {success: true}; + } catch (error) { + if (error instanceof Error) { + return {success: false, error: error.message}; + } + return {success: false, error: error as any}; + } finally { + if (server) { + await server.stop(); + } + } +} + +export default createBuilder<PuppeteerBuilderOptions>(executeE2ETest) as any; diff --git a/remote/test/puppeteer/packages/ng-schematics/src/builders/puppeteer/schema.json b/remote/test/puppeteer/packages/ng-schematics/src/builders/puppeteer/schema.json new file mode 100644 index 0000000000..42e80f46d0 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/builders/puppeteer/schema.json @@ -0,0 +1,22 @@ +{ + "title": "Puppeteer", + "description": "Options for Puppeteer Angular Schematics", + "type": "object", + "properties": { + "commands": { + "type": "array", + "items": { + "type": "array", + "item": { + "type": "string" + } + }, + "description": "Commands to execute in the repo. Commands prefixed with `./node_modules/bin` (Exception: 'node')." + }, + "devServerTarget": { + "type": "string", + "description": "Angular target that spawns the server." + } + }, + "additionalProperties": true +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/builders/puppeteer/types.ts b/remote/test/puppeteer/packages/ng-schematics/src/builders/puppeteer/types.ts new file mode 100644 index 0000000000..b25d51b5f1 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/builders/puppeteer/types.ts @@ -0,0 +1,24 @@ +/** + * Copyright 2022 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {JsonObject} from '@angular-devkit/core'; + +type Command = [string, ...string[]]; + +export interface PuppeteerBuilderOptions extends JsonObject { + commands: Command[]; + devServerTarget: string; +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/collection.json b/remote/test/puppeteer/packages/ng-schematics/src/schematics/collection.json new file mode 100644 index 0000000000..0cf38b799b --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/collection.json @@ -0,0 +1,10 @@ +{ + "$schema": "../../../../node_modules/@angular-devkit/schematics/collection-schema.json", + "schematics": { + "ng-add": { + "description": "Add Puppeteer to an Angular project", + "factory": "./ng-add/index#ngAdd", + "schema": "./ng-add/schema.json" + } + } +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/base/.puppeteerrc.cjs.template b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/base/.puppeteerrc.cjs.template new file mode 100644 index 0000000000..04f3f0d832 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/base/.puppeteerrc.cjs.template @@ -0,0 +1,4 @@ +/** + * @type {import("puppeteer").Configuration} + */ +module.exports = {}; diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/base/e2e/tests/app.e2e.ts.template b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/base/e2e/tests/app.e2e.ts.template new file mode 100644 index 0000000000..2f98ef7d46 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/base/e2e/tests/app.e2e.ts.template @@ -0,0 +1,59 @@ +import * as puppeteer from 'puppeteer'; +<% if(testingFramework == 'node') { %> +import { + describe, + it, + before, + beforeEach, + after, + afterEach, +} from 'node:test'; +<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %> +import * as assert from 'assert'; +<% } %> + +describe('App test', function () { + let browser: puppeteer.Browser; + let page: puppeteer.Page; + +<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %> + beforeAll(async () => { + browser = await puppeteer.launch(); + }); +<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %> + before(async () => { + browser = await puppeteer.launch(); + }); +<% } %> + + beforeEach(async () => { + page = await browser.newPage(); + await page.goto('<%= baseUrl %>'); + }); + + afterEach(async () => { + await page.close(); + }); + +<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %> + afterAll(async () => { + await browser.close(); + }); +<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %> + after(async () => { + await browser.close(); + }); +<% } %> + + it('is running', async function () { + const element = await page.waitForSelector( + 'text/<%= project %> app is running!' + ); + +<% if(testingFramework == 'jasmine' || testingFramework == 'jest') { %> + expect(element).not.toBeNull(); +<% } %><% if(testingFramework == 'mocha' || testingFramework == 'node') { %> + assert.ok(element); +<% } %> + }); +}); diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/base/e2e/tsconfig.json.template b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/base/e2e/tsconfig.json.template new file mode 100644 index 0000000000..438d04725f --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/base/e2e/tsconfig.json.template @@ -0,0 +1,15 @@ +/* To learn more about this file see: https://angular.io/config/tsconfig. */ +{ + "extends": "../tsconfig.json", + "compilerOptions": { +<% if(testingFramework == 'jest') { %> + "esModuleInterop": true, +<% } %><% if(testingFramework == 'node') { %> + "module": "CommonJS", + "rootDir": "tests/", + "outDir": "test/", +<% } %> + "types": ["<%= testingFramework %>"] + }, + "include": ["tests/**/*.e2e.ts"] +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/jasmine/e2e/helpers/babel.js b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/jasmine/e2e/helpers/babel.js new file mode 100644 index 0000000000..06259b39c8 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/jasmine/e2e/helpers/babel.js @@ -0,0 +1,4 @@ +require('@babel/register')({ + extensions: ['.js', '.ts'], + presets: ['@babel/preset-env', '@babel/preset-typescript'], +}); diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/jasmine/e2e/support/jasmine.json b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/jasmine/e2e/support/jasmine.json new file mode 100644 index 0000000000..7100e102f1 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/jasmine/e2e/support/jasmine.json @@ -0,0 +1,9 @@ +{ + "spec_dir": "e2e", + "spec_files": ["**/*[eE]2[eE].ts"], + "helpers": ["helpers/babel.js", "helpers/**/*.{js|ts}"], + "env": { + "stopSpecOnExpectationFailure": false, + "random": true + } +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/jest/e2e/jest.config.js b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/jest/e2e/jest.config.js new file mode 100644 index 0000000000..99cc594c97 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/jest/e2e/jest.config.js @@ -0,0 +1,11 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +/** @type {import('ts-jest').JestConfigWithTsJest} */ +module.exports = { + testMatch: ['<rootDir>/tests/**/?(*.)+(e2e).[tj]s?(x)'], + preset: 'ts-jest', + testEnvironment: 'node', +}; diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/mocha/e2e/.mocharc.js b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/mocha/e2e/.mocharc.js new file mode 100644 index 0000000000..63ca85e3eb --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/mocha/e2e/.mocharc.js @@ -0,0 +1,4 @@ +module.exports = { + file: ['e2e/babel.js'], + spec: './e2e/tests/**/*.e2e.ts', +}; diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/mocha/e2e/babel.js b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/mocha/e2e/babel.js new file mode 100644 index 0000000000..06259b39c8 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/mocha/e2e/babel.js @@ -0,0 +1,4 @@ +require('@babel/register')({ + extensions: ['.js', '.ts'], + presets: ['@babel/preset-env', '@babel/preset-typescript'], +}); diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/node/e2e/.gitignore.template b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/node/e2e/.gitignore.template new file mode 100644 index 0000000000..35250fac0e --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/files/node/e2e/.gitignore.template @@ -0,0 +1,3 @@ +# Compiled e2e tests output Node auto resolves files in folders named 'test' + +test/
\ No newline at end of file diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/index.ts b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/index.ts new file mode 100644 index 0000000000..49c56bfc2f --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/index.ts @@ -0,0 +1,127 @@ +/** + * Copyright 2022 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {chain, Rule, SchematicContext, Tree} from '@angular-devkit/schematics'; +import {NodePackageInstallTask} from '@angular-devkit/schematics/tasks'; +import {of} from 'rxjs'; +import {concatMap, map, scan} from 'rxjs/operators'; + +import { + addBaseFiles, + addFrameworkFiles, + getNgCommandName, +} from '../utils/files.js'; +import {getAngularConfig} from '../utils/json.js'; +import { + addPackageJsonDependencies, + addPackageJsonScripts, + getDependenciesFromOptions, + getPackageLatestNpmVersion, + DependencyType, + type NodePackage, + updateAngularJsonScripts, +} from '../utils/packages.js'; +import {type SchematicsOptions} from '../utils/types.js'; + +// You don't have to export the function as default. You can also have more than one rule +// factory per file. +export function ngAdd(options: SchematicsOptions): Rule { + return (tree: Tree, context: SchematicContext) => { + return chain([ + addDependencies(options), + addPuppeteerFiles(options), + addOtherFiles(options), + updateScripts(options), + updateAngularConfig(options), + ])(tree, context); + }; +} + +function addDependencies(options: SchematicsOptions): Rule { + return (tree: Tree, context: SchematicContext) => { + context.logger.debug('Adding dependencies to "package.json"'); + const dependencies = getDependenciesFromOptions(options); + + return of(...dependencies).pipe( + concatMap((packageName: string) => { + return getPackageLatestNpmVersion(packageName); + }), + scan((array, nodePackage) => { + array.push(nodePackage); + return array; + }, [] as NodePackage[]), + map(packages => { + context.logger.debug('Updating dependencies...'); + addPackageJsonDependencies(tree, packages, DependencyType.Dev); + context.addTask(new NodePackageInstallTask()); + + return tree; + }) + ); + }; +} + +function updateScripts(options: SchematicsOptions): Rule { + return (tree: Tree, context: SchematicContext): Tree => { + context.logger.debug('Updating "package.json" scripts'); + const angularJson = getAngularConfig(tree); + const projects = Object.keys(angularJson['projects']); + + if (projects.length === 1) { + const name = getNgCommandName(options); + const prefix = options.isDefaultTester ? '' : `run ${projects[0]}:`; + return addPackageJsonScripts(tree, [ + { + name, + script: `ng ${prefix}${name}`, + }, + ]); + } + return tree; + }; +} + +function addPuppeteerFiles(options: SchematicsOptions): Rule { + return (tree: Tree, context: SchematicContext) => { + context.logger.debug('Adding Puppeteer base files.'); + const {projects} = getAngularConfig(tree); + + return addBaseFiles(tree, context, { + projects, + options, + }); + }; +} + +function addOtherFiles(options: SchematicsOptions): Rule { + return (tree: Tree, context: SchematicContext) => { + context.logger.debug('Adding Puppeteer additional files.'); + const {projects} = getAngularConfig(tree); + + return addFrameworkFiles(tree, context, { + projects, + options, + }); + }; +} + +function updateAngularConfig(options: SchematicsOptions): Rule { + return (tree: Tree, context: SchematicContext): Tree => { + context.logger.debug('Updating "angular.json".'); + + return updateAngularJsonScripts(tree, options); + }; +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/schema.json b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/schema.json new file mode 100644 index 0000000000..77cb5bdd48 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/ng-add/schema.json @@ -0,0 +1,49 @@ +{ + "$schema": "http://json-schema.org/schema", + "$id": "Puppeteer", + "title": "Puppeteer Install Schema", + "type": "object", + "properties": { + "isDefaultTester": { + "description": "", + "type": "boolean", + "default": true, + "x-prompt": "Use Puppeteer as default `ng e2e` command?" + }, + "exportConfig": { + "description": "", + "type": "boolean", + "default": false, + "x-prompt": "Export default Puppeteer config file?" + }, + "testingFramework": { + "description": "", + "type": "string", + "enum": ["jasmine", "jest", "mocha", "node"], + "default": "jasmine", + "x-prompt": { + "message": "With what Testing Library do you wish to integrate?", + "type": "list", + "items": [ + { + "value": "jasmine", + "label": "Use Jasmine [https://jasmine.github.io/]" + }, + { + "value": "jest", + "label": "Use Jest [https://jestjs.io/]" + }, + { + "value": "mocha", + "label": "Use Mocha [https://mochajs.org/]" + }, + { + "value": "node", + "label": "Use Node Test Runner (Experimental: Node v18) [https://nodejs.org/api/test.html]" + } + ] + } + } + }, + "required": [] +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/files.ts b/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/files.ts new file mode 100644 index 0000000000..9518e1d996 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/files.ts @@ -0,0 +1,162 @@ +/** + * Copyright 2022 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {relative, resolve} from 'path'; + +import {getSystemPath, normalize, strings} from '@angular-devkit/core'; +import { + SchematicContext, + Tree, + apply, + applyTemplates, + chain, + filter, + mergeWith, + move, + url, +} from '@angular-devkit/schematics'; + +import {SchematicsOptions, TestingFramework} from './types.js'; + +export interface FilesOptions { + projects: any; + options: SchematicsOptions; + applyPath: string; + relativeToWorkspacePath: string; + movePath?: string; + filterPredicate?: (path: string) => boolean; +} + +const PUPPETEER_CONFIG_TEMPLATE = '.puppeteerrc.cjs.template'; + +export function addFiles( + tree: Tree, + context: SchematicContext, + { + projects, + options, + applyPath, + movePath, + relativeToWorkspacePath, + filterPredicate, + }: FilesOptions +): any { + return chain( + Object.keys(projects).map(name => { + const project = projects[name]; + const projectPath = resolve(getSystemPath(normalize(project.root))); + const workspacePath = resolve(getSystemPath(normalize(''))); + + const relativeToWorkspace = relative( + `${projectPath}${relativeToWorkspacePath}`, + workspacePath + ); + + const baseUrl = getProjectBaseUrl(project); + + return mergeWith( + apply(url(applyPath), [ + filter( + filterPredicate ?? + (() => { + return true; + }) + ), + move(movePath ? `${project.root}${movePath}` : project.root), + applyTemplates({ + ...options, + ...strings, + root: project.root ? `${project.root}/` : project.root, + baseUrl, + project: name, + relativeToWorkspace, + }), + ]) + ); + }) + )(tree, context); +} + +function getProjectBaseUrl(project: any): string { + let options = {protocol: 'http', port: 4200, host: 'localhost'}; + + if (project.architect?.serve?.options) { + const projectOptions = project.architect?.serve?.options; + + options = {...options, ...projectOptions}; + options.protocol = projectOptions.ssl ? 'https' : 'http'; + } + + return `${options.protocol}://${options.host}:${options.port}`; +} + +export function addBaseFiles( + tree: Tree, + context: SchematicContext, + filesOptions: Omit<FilesOptions, 'applyPath' | 'relativeToWorkspacePath'> +): any { + const options: FilesOptions = { + ...filesOptions, + applyPath: './files/base', + relativeToWorkspacePath: `/`, + filterPredicate: path => { + return path.includes(PUPPETEER_CONFIG_TEMPLATE) && + !filesOptions.options.exportConfig + ? false + : true; + }, + }; + + return addFiles(tree, context, options); +} + +export function addFrameworkFiles( + tree: Tree, + context: SchematicContext, + filesOptions: Omit<FilesOptions, 'applyPath' | 'relativeToWorkspacePath'> +): any { + const testingFramework = filesOptions.options.testingFramework; + const options: FilesOptions = { + ...filesOptions, + applyPath: `./files/${testingFramework}`, + relativeToWorkspacePath: `/`, + }; + + return addFiles(tree, context, options); +} + +export function getScriptFromOptions(options: SchematicsOptions): string[][] { + switch (options.testingFramework) { + case TestingFramework.Jasmine: + return [[`jasmine`, '--config=./e2e/support/jasmine.json']]; + case TestingFramework.Jest: + return [[`jest`, '-c', 'e2e/jest.config.js']]; + case TestingFramework.Mocha: + return [[`mocha`, '--config=./e2e/.mocharc.js']]; + case TestingFramework.Node: + return [ + [`tsc`, '-p', 'e2e/tsconfig.json'], + ['node', '--test', 'e2e/'], + ]; + } +} + +export function getNgCommandName(options: SchematicsOptions): string { + if (options.isDefaultTester) { + return 'e2e'; + } + return 'puppeteer'; +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/json.ts b/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/json.ts new file mode 100644 index 0000000000..f48eab399e --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/json.ts @@ -0,0 +1,38 @@ +/** + * Copyright 2022 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {SchematicsException, Tree} from '@angular-devkit/schematics'; + +export function getJsonFileAsObject( + tree: Tree, + path: string +): Record<string, any> { + try { + const buffer = tree.read(path) as Buffer; + const content = buffer.toString(); + return JSON.parse(content); + } catch { + throw new SchematicsException(`Unable to retrieve file at ${path}.`); + } +} + +export function getObjectAsJson(object: Record<string, any>): string { + return JSON.stringify(object, null, 2); +} + +export function getAngularConfig(tree: Tree): Record<string, any> { + return getJsonFileAsObject(tree, './angular.json'); +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/packages.ts b/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/packages.ts new file mode 100644 index 0000000000..b3724be90f --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/packages.ts @@ -0,0 +1,204 @@ +/** + * Copyright 2022 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import {get} from 'https'; + +import {Tree} from '@angular-devkit/schematics'; + +import {getNgCommandName, getScriptFromOptions} from './files.js'; +import { + getAngularConfig, + getJsonFileAsObject, + getObjectAsJson, +} from './json.js'; +import {SchematicsOptions, TestingFramework} from './types.js'; +export interface NodePackage { + name: string; + version: string; +} +export interface NodeScripts { + name: string; + script: string; +} + +export enum DependencyType { + Default = 'dependencies', + Dev = 'devDependencies', + Peer = 'peerDependencies', + Optional = 'optionalDependencies', +} + +export function getPackageLatestNpmVersion(name: string): Promise<NodePackage> { + return new Promise(resolve => { + let version = 'latest'; + + return get(`https://registry.npmjs.org/${name}`, res => { + let data = ''; + + res.on('data', (chunk: any) => { + data += chunk; + }); + res.on('end', () => { + try { + const response = JSON.parse(data); + version = response?.['dist-tags']?.latest ?? version; + } catch { + } finally { + resolve({ + name, + version, + }); + } + }); + }).on('error', () => { + resolve({ + name, + version, + }); + }); + }); +} + +function updateJsonValues( + json: Record<string, any>, + target: string, + updates: Array<{name: string; value: any}>, + overwrite = false +) { + updates.forEach(({name, value}) => { + if (!json[target][name] || overwrite) { + json[target] = { + ...json[target], + [name]: value, + }; + } + }); +} + +export function addPackageJsonDependencies( + tree: Tree, + packages: NodePackage[], + type: DependencyType, + overwrite?: boolean, + fileLocation = './package.json' +): Tree { + const packageJson = getJsonFileAsObject(tree, fileLocation); + + updateJsonValues( + packageJson, + type, + packages.map(({name, version}) => { + return {name, value: version}; + }), + overwrite + ); + + tree.overwrite(fileLocation, getObjectAsJson(packageJson)); + + return tree; +} + +export function getDependenciesFromOptions( + options: SchematicsOptions +): string[] { + const dependencies = ['puppeteer']; + const babelPackages = [ + '@babel/core', + '@babel/register', + '@babel/preset-env', + '@babel/preset-typescript', + ]; + + switch (options.testingFramework) { + case TestingFramework.Jasmine: + dependencies.push('jasmine', ...babelPackages); + break; + case TestingFramework.Jest: + dependencies.push('jest', '@types/jest', 'ts-jest'); + break; + case TestingFramework.Mocha: + dependencies.push('mocha', '@types/mocha', ...babelPackages); + break; + case TestingFramework.Node: + dependencies.push('@types/node'); + break; + } + + return dependencies; +} + +export function addPackageJsonScripts( + tree: Tree, + scripts: NodeScripts[], + overwrite?: boolean, + fileLocation = './package.json' +): Tree { + const packageJson = getJsonFileAsObject(tree, fileLocation); + + updateJsonValues( + packageJson, + 'scripts', + scripts.map(({name, script}) => { + return {name, value: script}; + }), + overwrite + ); + + tree.overwrite(fileLocation, getObjectAsJson(packageJson)); + + return tree; +} + +export function updateAngularJsonScripts( + tree: Tree, + options: SchematicsOptions, + overwrite = true +): Tree { + const angularJson = getAngularConfig(tree); + const commands = getScriptFromOptions(options); + const name = getNgCommandName(options); + + Object.keys(angularJson['projects']).forEach(project => { + const e2eScript = [ + { + name, + value: { + builder: '@puppeteer/ng-schematics:puppeteer', + options: { + commands, + devServerTarget: `${project}:serve`, + }, + configurations: { + production: { + devServerTarget: `${project}:serve:production`, + }, + }, + }, + }, + ]; + + updateJsonValues( + angularJson['projects'][project], + 'architect', + e2eScript, + overwrite + ); + }); + + tree.overwrite('./angular.json', getObjectAsJson(angularJson)); + + return tree; +} diff --git a/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/types.ts b/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/types.ts new file mode 100644 index 0000000000..c59f90e69d --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/src/schematics/utils/types.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2022 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum TestingFramework { + Jasmine = 'jasmine', + Jest = 'jest', + Mocha = 'mocha', + Node = 'node', +} + +export interface SchematicsOptions { + isDefaultTester: boolean; + exportConfig: boolean; + testingFramework: TestingFramework; +} diff --git a/remote/test/puppeteer/packages/ng-schematics/test/src/index.spec.ts b/remote/test/puppeteer/packages/ng-schematics/test/src/index.spec.ts new file mode 100644 index 0000000000..59636dbb38 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/test/src/index.spec.ts @@ -0,0 +1,212 @@ +import https from 'https'; +import {join} from 'path'; + +import {JsonObject} from '@angular-devkit/core'; +import { + SchematicTestRunner, + UnitTestTree, +} from '@angular-devkit/schematics/testing/schematic-test-runner'; +import expect from 'expect'; +import sinon from 'sinon'; + +const WORKSPACE_OPTIONS = { + name: 'workspace', + newProjectRoot: 'projects', + version: '14.0.0', +}; + +const APPLICATION_OPTIONS = { + name: 'sandbox', +}; + +function getProjectFile(file: string): string { + return `/${WORKSPACE_OPTIONS.newProjectRoot}/${APPLICATION_OPTIONS.name}/${file}`; +} + +function getAngularJsonScripts( + tree: UnitTestTree, + isDefault = true +): { + builder: string; + configurations: Record<string, any>; + options: Record<string, any>; +} { + const angularJson = tree.readJson('angular.json') as any; + const e2eScript = isDefault ? 'e2e' : 'puppeteer'; + return angularJson['projects']?.[APPLICATION_OPTIONS.name]?.['architect'][ + e2eScript + ]; +} + +function getPackageJson(tree: UnitTestTree): { + scripts: Record<string, string>; + devDependencies: string[]; +} { + const packageJson = tree.readJson('package.json') as JsonObject; + return { + scripts: packageJson['scripts'] as any, + devDependencies: Object.keys( + packageJson['devDependencies'] as Record<string, string> + ), + }; +} + +async function buildTestingTree(userOptions?: Record<string, any>) { + const runner = new SchematicTestRunner( + 'schematics', + join(__dirname, '../../lib/schematics/collection.json') + ); + const options = { + isDefaultTester: true, + exportConfig: false, + testingFramework: 'jasmine', + ...userOptions, + }; + let workingTree: UnitTestTree; + + // Build workspace + workingTree = await runner + .runExternalSchematicAsync( + '@schematics/angular', + 'workspace', + WORKSPACE_OPTIONS + ) + .toPromise(); + // Build dummy application + workingTree = await runner + .runExternalSchematicAsync( + '@schematics/angular', + 'application', + APPLICATION_OPTIONS, + workingTree + ) + .toPromise(); + + return await runner + .runSchematicAsync('ng-add', options, workingTree) + .toPromise(); +} + +describe('@puppeteer/ng-schematics: ng-add', () => { + // Stop outgoing Request for version fetching + before(() => { + const httpsGetStub = sinon.stub(https, 'get'); + httpsGetStub.returns({ + on: (_: any, callback: () => void) => { + callback(); + }, + } as any); + }); + + after(() => { + sinon.restore(); + }); + + it('should create base files and update to "package.json"', async () => { + const tree = await buildTestingTree(); + const {devDependencies, scripts} = getPackageJson(tree); + const {builder, configurations} = getAngularJsonScripts(tree); + + expect(tree.files).toContain(getProjectFile('e2e/tsconfig.json')); + expect(tree.files).toContain(getProjectFile('e2e/tests/app.e2e.ts')); + expect(devDependencies).toContain('puppeteer'); + expect(scripts['e2e']).toBe('ng e2e'); + expect(builder).toBe('@puppeteer/ng-schematics:puppeteer'); + expect(configurations).toEqual({ + production: { + devServerTarget: 'sandbox:serve:production', + }, + }); + }); + + it('should update create proper "ng" command for non default tester', async () => { + const tree = await buildTestingTree({ + isDefaultTester: false, + }); + const {scripts} = getPackageJson(tree); + const {builder} = getAngularJsonScripts(tree, false); + + expect(scripts['puppeteer']).toBe('ng run sandbox:puppeteer'); + expect(builder).toBe('@puppeteer/ng-schematics:puppeteer'); + }); + + it('should create Puppeteer config', async () => { + const {files} = await buildTestingTree({ + exportConfig: true, + }); + + expect(files).toContain(getProjectFile('.puppeteerrc.cjs')); + }); + + it('should not create Puppeteer config', async () => { + const {files} = await buildTestingTree({ + exportConfig: false, + }); + + expect(files).not.toContain(getProjectFile('.puppeteerrc.cjs')); + }); + + it('should create Jasmine files and update "package.json"', async () => { + const tree = await buildTestingTree({ + testingFramework: 'jasmine', + }); + const {devDependencies} = getPackageJson(tree); + const {options} = getAngularJsonScripts(tree); + + expect(tree.files).toContain(getProjectFile('e2e/support/jasmine.json')); + expect(tree.files).toContain(getProjectFile('e2e/helpers/babel.js')); + expect(devDependencies).toContain('jasmine'); + expect(devDependencies).toContain('@babel/core'); + expect(devDependencies).toContain('@babel/register'); + expect(devDependencies).toContain('@babel/preset-typescript'); + expect(options['commands']).toEqual([ + [`jasmine`, '--config=./e2e/support/jasmine.json'], + ]); + }); + + it('should create Jest files and update "package.json"', async () => { + const tree = await buildTestingTree({ + testingFramework: 'jest', + }); + const {devDependencies} = getPackageJson(tree); + const {options} = getAngularJsonScripts(tree); + + expect(tree.files).toContain(getProjectFile('e2e/jest.config.js')); + expect(devDependencies).toContain('jest'); + expect(devDependencies).toContain('@types/jest'); + expect(devDependencies).toContain('ts-jest'); + expect(options['commands']).toEqual([[`jest`, '-c', 'e2e/jest.config.js']]); + }); + + it('should create Mocha files and update "package.json"', async () => { + const tree = await buildTestingTree({ + testingFramework: 'mocha', + }); + const {devDependencies} = getPackageJson(tree); + const {options} = getAngularJsonScripts(tree); + + expect(tree.files).toContain(getProjectFile('e2e/.mocharc.js')); + expect(tree.files).toContain(getProjectFile('e2e/babel.js')); + expect(devDependencies).toContain('mocha'); + expect(devDependencies).toContain('@types/mocha'); + expect(devDependencies).toContain('@babel/core'); + expect(devDependencies).toContain('@babel/register'); + expect(devDependencies).toContain('@babel/preset-typescript'); + expect(options['commands']).toEqual([ + [`mocha`, '--config=./e2e/.mocharc.js'], + ]); + }); + + it('should create Node files"', async () => { + const tree = await buildTestingTree({ + testingFramework: 'node', + }); + const {options} = getAngularJsonScripts(tree); + + expect(tree.files).toContain(getProjectFile('e2e/.gitignore')); + expect(options['commands']).toEqual([ + [`tsc`, '-p', 'e2e/tsconfig.json'], + ['node', '--test', 'e2e/'], + ]); + }); +}); diff --git a/remote/test/puppeteer/packages/ng-schematics/tools/copySchemaFiles.js b/remote/test/puppeteer/packages/ng-schematics/tools/copySchemaFiles.js new file mode 100644 index 0000000000..bca61b8101 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/tools/copySchemaFiles.js @@ -0,0 +1,72 @@ +/** + * Copyright 2022 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const fs = require('fs/promises'); +const {join} = require('path'); +const path = require('path'); + +/** + * + * @param {String} directory + * @param {String[]} files + */ +async function findSchemaFiles(directory, files = []) { + const items = await fs.readdir(directory); + const promises = []; + // Match any listing that has no *.* format + // Ignore files folder + const regEx = /^.*\.[^\s]*$/; + + items.forEach(item => { + if (!item.match(regEx)) { + promises.push(findSchemaFiles(`${directory}/${item}`, files)); + } else if (item.endsWith('.json') || directory.includes('files')) { + files.push(`${directory}/${item}`); + } + }); + + await Promise.all(promises); + + return files; +} + +async function copySchemaFiles() { + const srcDir = join(__dirname, '..', 'src'); + const outputDir = join(__dirname, '..', 'lib'); + const files = await findSchemaFiles(srcDir); + + const moves = files.map(file => { + const to = file.replace(srcDir, outputDir); + + return {from: file, to}; + }); + + // Because fs.cp is Experimental (recursive support) + // We need to create directories first and copy the files + await Promise.all( + moves.map(({to}) => { + const dir = path.dirname(to); + return fs.mkdir(dir, {recursive: true}); + }) + ); + await Promise.all( + moves.map(({from, to}) => { + return fs.copyFile(from, to); + }) + ); +} + +copySchemaFiles(); diff --git a/remote/test/puppeteer/packages/ng-schematics/tools/sandbox.js b/remote/test/puppeteer/packages/ng-schematics/tools/sandbox.js new file mode 100644 index 0000000000..44f131bfac --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/tools/sandbox.js @@ -0,0 +1,104 @@ +/** + * Copyright 2023 Google Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const {spawn} = require('child_process'); +const {readFile, writeFile} = require('fs/promises'); +const {join} = require('path'); +const {cwd} = require('process'); + +const isInit = process.argv.indexOf('--init') !== -1; +const isBuild = process.argv.indexOf('--build') !== -1; +const commands = { + build: ['npm run build'], + createSandbox: ['npx ng new sandbox --defaults'], + runSchematics: [ + { + command: 'npm run schematics', + options: { + cwd: join(cwd(), '/sandbox/'), + }, + }, + ], +}; +const scripts = { + // Deletes all files created by Puppeteer Ng-Schematics to avoid errors + 'delete:file': + 'rm -f .puppeteerrc.cjs && rm -f tsconfig.e2e.json && rm -R -f e2e/', + // Runs the Puppeteer Ng-Schematics against the sandbox + schematics: 'npm run delete:file && schematics ../:ng-add --dry-run=false', +}; +/** + * + * @param {string | object} toExecute + * @returns {Promise<boolean>} + */ +async function executeCommand(commands) { + for (const toExecute of commands) { + let executable; + let args; + let options = {}; + if (typeof toExecute === 'string') { + [executable, ...args] = toExecute.split(' '); + } else { + [executable, ...args] = toExecute.command.split(' '); + options = toExecute.options ?? {}; + } + + await new Promise((resolve, reject) => { + const createProcess = spawn(executable, args, { + stdio: 'inherit', + shell: true, + ...options, + }); + + createProcess.on('error', message => { + console.error(message); + reject(message); + }); + + createProcess.on('exit', code => { + if (code === 0) { + resolve(true); + } else { + reject(); + } + }); + }); + } +} + +async function main() { + if (isInit) { + await executeCommand(commands.createSandbox); + + const packageJsonFile = join(cwd(), '/sandbox/package.json'); + const packageJson = JSON.parse(await readFile(packageJsonFile)); + packageJson['scripts'] = { + ...packageJson['scripts'], + ...scripts, + }; + await writeFile(packageJsonFile, JSON.stringify(packageJson, null, 2)); + } else { + if (isBuild) { + await executeCommand(commands.build); + } + await executeCommand(commands.runSchematics); + } +} + +main().catch(() => { + console.log('\n'); +}); diff --git a/remote/test/puppeteer/packages/ng-schematics/tsconfig.json b/remote/test/puppeteer/packages/ng-schematics/tsconfig.json new file mode 100644 index 0000000000..115c833159 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "tsconfig", + "lib": ["ES2018"], + "module": "CommonJS", + "noEmitOnError": true, + "rootDir": "src/", + "outDir": "lib/", + "skipDefaultLibCheck": true, + "skipLibCheck": true, + "sourceMap": true, + "types": ["node"], + "target": "ES6" + }, + "include": ["src/**/*"], + "exclude": ["src/**/files/**/*"], + "references": [{"path": "./tsconfig.spec.json"}] +} diff --git a/remote/test/puppeteer/packages/ng-schematics/tsconfig.spec.json b/remote/test/puppeteer/packages/ng-schematics/tsconfig.spec.json new file mode 100644 index 0000000000..8afa532cb1 --- /dev/null +++ b/remote/test/puppeteer/packages/ng-schematics/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": "test/src/", + "outDir": "test/build/", + "types": ["node", "mocha"] + }, + "include": ["test/src/**/*"], + "exclude": ["test/build/**/*"] +} |