diff --git a/.github/scripts/print_behat_tags_json.sh b/.github/scripts/print_behat_tags_json.sh new file mode 100755 index 000000000..068f48f7d --- /dev/null +++ b/.github/scripts/print_behat_tags_json.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +# Search tags. +declare -A tags + +for feature in `find ./src/ -iname *.feature` +do + tag=`head -n 1 $feature | sed -E s/\\\\s+.*//` + tags[$tag]=$tag +done + +# Serialize to JSON. +tags_json="[" + +for tag in "${tags[@]}" +do + tags_json+="\"$tag\"," +done + +tags_json="${tags_json%?}" +tags_json+="]" + +# Print to console. +echo $tags_json diff --git a/.github/workflows/acceptance.yml b/.github/workflows/acceptance.yml index ad6cb4845..58aba5ff0 100644 --- a/.github/workflows/acceptance.yml +++ b/.github/workflows/acceptance.yml @@ -20,7 +20,6 @@ concurrency: group: acceptance-${{ github.ref }} cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} - jobs: build: @@ -63,59 +62,10 @@ jobs: - name: Prepare Behat tags id: set-tags + working-directory: app run: | if [ -z $BEHAT_TAGS ]; then - tags=( - "@addon_block_timeline" - "@addon_calendar" - "@addon_competency" - "@addon_coursecompletion" - "@addon_messages" - "@addon_mod_assign" - "@addon_mod_bigbluebuttonbn" - "@addon_mod_book" - "@addon_mod_chat" - "@addon_mod_choice" - "@addon_mod_data" - "@addon_mod_feedback" - "@addon_mod_folder" - "@addon_mod_forum" - "@addon_mod_glossary" - "@addon_mod_imscp" - "@addon_mod_lesson" - "@addon_mod_page" - "@addon_mod_quiz" - "@addon_mod_scorm" - "@addon_mod_survey" - "@addon_mod_url" - "@addon_mod_workshop" - "@addon_notifications" - "@addon_blog" - "@core" - "@core_comments" - "@core_course" - "@core_courses" - "@core_dataprivacy" - "@core_grades" - "@core_login" - "@core_mainmenu" - "@core_policy" - "@core_reminders" - "@core_reportbuilder" - "@core_search" - "@core_settings" - "@core_siteplugins" - "@core_tag" - "@core_user" - "@core_usertour" - ) - - tags_json="[" - for tag in "${tags[@]}"; do - tags_json+="\"$tag\"," - done - tags_json="${tags_json%?}" - tags_json+="]" + tags_json=`.github/scripts/print_behat_tags_json.sh` echo "tags=$tags_json" >> $GITHUB_OUTPUT; else echo "tags=[\"$BEHAT_TAGS\"]" >> $GITHUB_OUTPUT; diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..b336c53a9 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,306 @@ +name: Coverage + +on: + workflow_dispatch: + inputs: + behat_tags: + description: 'Behat tags to execute' + moodle_branch: + description: 'Moodle branch' + required: true + default: 'main' + moodle_repository: + description: 'Moodle repository' + required: true + default: 'https://github.com/moodle/moodle.git' + +concurrency: + group: coverage-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +jobs: + + build: + runs-on: ubuntu-latest + outputs: + tags: ${{ steps.set-tags.outputs.tags }} + + steps: + + - uses: actions/checkout@v4 + with: + path: app + + - uses: actions/setup-node@v4 + with: + node-version-file: 'app/.nvmrc' + + - name: Install npm dependencies + working-directory: app + run: npm ci --no-audit + + - name: Build app + working-directory: app + run: npm run build:test + env: + MOODLE_APP_COVERAGE: true + + - name: Generate SSL certificates + working-directory: app + run: | + mkdir ./ssl + openssl req -x509 -nodes \ + -days 365 \ + -newkey rsa:2048 \ + -keyout ./ssl/certificate.key \ + -out ./ssl/certificate.crt \ + -subj="/O=Moodle" + + - name: Build Behat plugin + working-directory: app + run: ./scripts/build-behat-plugin.js ../plugin + + - name: Prepare Behat tags + id: set-tags + working-directory: app + run: | + if [ -z $BEHAT_TAGS ]; then + tags_json=`.github/scripts/print_behat_tags_json.sh` + echo "tags=$tags_json" >> $GITHUB_OUTPUT; + else + echo "tags=[\"$BEHAT_TAGS\"]" >> $GITHUB_OUTPUT; + fi + env: + BEHAT_TAGS: ${{ github.event.inputs.behat_tags }} + + # We need to upload an artifact so that the download-artifact action + # in the "complete" job does not fail if no other artifacts were uploaded. + - name: Create build logs + run: touch logs.txt + + - name: Upload build logs + uses: actions/upload-artifact@v4 + with: + name: build + path: logs.txt + + - uses: actions/cache/save@v4 + with: + key: build-${{ github.sha }} + path: | + app/ssl/**/* + app/node_modules/**/* + app/www/**/* + plugin/**/* + + jest: + runs-on: ubuntu-latest + needs: build + + steps: + + - uses: actions/checkout@v4 + with: + path: app + + - uses: actions/setup-node@v4 + with: + node-version-file: 'app/.nvmrc' + + - uses: actions/cache/restore@v4 + with: + key: build-${{ github.sha }} + path: | + app/ssl/**/* + app/node_modules/**/* + app/www/**/* + plugin/**/* + + - name: Run Jest tests + working-directory: app + run: | + NODE_ENV=testing npx gulp + npx jest --coverage --coverageReporters=json + mkdir ../coverage-jsons + mv coverage/coverage-final.json ../coverage-jsons/jest.json + + - uses: actions/upload-artifact@v4 + with: + name: coverage-jsons-jest + path: coverage-jsons + + behat: + runs-on: ubuntu-latest + needs: build + continue-on-error: true + + strategy: + matrix: + tags: ${{ fromJSON(needs.build.outputs.tags) }} + + services: + + postgres: + image: postgres:13 + env: + POSTGRES_USER: 'postgres' + POSTGRES_HOST_AUTH_METHOD: 'trust' + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 3 + + steps: + + - uses: actions/checkout@v4 + with: + path: app + + - uses: actions/setup-node@v4 + with: + node-version-file: 'app/.nvmrc' + + - uses: shivammathur/setup-php@v2 + with: + php-version: 8.1 + ini-values: max_input_vars=5000 + coverage: none + + - uses: actions/cache/restore@v4 + with: + key: build-${{ github.sha }} + path: | + app/ssl/**/* + app/node_modules/**/* + app/www/**/* + plugin/**/* + + - name: Launch Docker images + working-directory: app + run: | + docker run -d --rm \ + -p 8001:443 \ + --name moodleapp \ + -v ./www:/usr/share/nginx/html \ + -v ./nginx.conf:/etc/nginx/conf.d/default.conf \ + -v ./ssl/certificate.crt:/etc/ssl/certificate.crt \ + -v ./ssl/certificate.key:/etc/ssl/certificate.key \ + nginx:alpine + docker run -d --rm -p 8002:80 --name bigbluebutton moodlehq/bigbluebutton_mock:latest + + - name: Initialise moodle-plugin-ci + run: | + git clone https://github.com/NoelDeMartin/moodle-plugin-ci --branch selenium-env ci + composer install -d ./ci + echo $(cd ci/bin; pwd) >> $GITHUB_PATH + echo $(cd ci/vendor/bin; pwd) >> $GITHUB_PATH + sudo locale-gen en_AU.UTF-8 + echo "NVM_DIR=$HOME/.nvm" >> $GITHUB_ENV + sed -i "58i\$CFG->behat_profiles['chrome']['capabilities'] = ['extra_capabilities' => ['chromeOptions' => ['args' => ['--ignore-certificate-errors', '--allow-running-insecure-content']]]];" ci/res/template/config.php.txt + + - name: Install Behat Snapshots plugin + run: moodle-plugin-ci add-plugin NoelDeMartin/moodle-local_behatsnapshots + + - name: Install moodle-plugin-ci + run: moodle-plugin-ci install --plugin ./plugin --db-host=127.0.0.1 + env: + DB: pgsql + MOODLE_BRANCH: ${{ github.event.inputs.moodle_branch || 'main' }} + MOODLE_REPO: ${{ github.event.inputs.moodle_repository || 'https://github.com/moodle/moodle.git' }} + MOODLE_BEHAT_IONIC_WWWROOT: https://localhost:8001 + MOODLE_BEHAT_DEFAULT_BROWSER: chrome + + - name: Update config + run: | + moodle-plugin-ci add-config "\$CFG->behat_extraallowedsettings = ['forced_plugin_settings'];" + moodle-plugin-ci add-config "\$CFG->forced_plugin_settings = ['local_moodleappbehat' => ['coverage_path' => '$GITHUB_WORKSPACE/moodle/coverage/']];" + moodle-plugin-ci add-config 'define("TEST_MOD_BIGBLUEBUTTONBN_MOCK_SERVER", "http://localhost:8002/hash" . sha1($CFG->wwwroot));' + + - name: Run Behat tests + run: moodle-plugin-ci behat --auto-rerun 3 --profile chrome --tags="@app&&~@local&&$BEHAT_TAGS" + env: + BEHAT_TAGS: ${{ matrix.tags }} + MOODLE_BEHAT_SELENIUM_IMAGE: selenium/standalone-chrome:120.0 + + - name: Merge Coverage jsons + working-directory: app + run: | + mkdir ../coverage-jsons + mkdir -p ../moodle/coverage/ + echo "{}" > ../moodle/coverage/base.json + npx nyc merge ../moodle/coverage/ ../coverage-jsons/$BEHAT_TAGS.json + env: + BEHAT_TAGS: ${{ matrix.tags }} + + - name: Upload Coverage JSONs + uses: actions/upload-artifact@v4 + with: + name: coverage-jsons-${{ matrix.tags }} + path: coverage-jsons + + - name: Upload Snapshot failures + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: snapshot_failures-${{ matrix.tags }} + path: moodle/local/moodleappbehat/tests/behat/snapshots/failures/* + + - name: Upload Behat failures + uses: actions/upload-artifact@v4 + if: ${{ failure() }} + with: + name: behat_failures-${{ matrix.tags }} + path: moodledata/behat_dump/* + + complete: + runs-on: ubuntu-latest + needs: [behat, jest] + + steps: + + - uses: actions/checkout@v4 + with: + path: app + + - uses: actions/setup-node@v4 + with: + node-version-file: 'app/.nvmrc' + + - uses: actions/cache/restore@v4 + with: + key: build-${{ github.sha }} + path: | + app/ssl/**/* + app/node_modules/**/* + app/www/**/* + plugin/**/* + + - uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Prepare coverage jsons + run: | + mkdir ./app/coverage-jsons + mv ./artifacts/coverage-jsons-*/* ./app/coverage-jsons + rm ./artifacts/coverage-jsons* -r + + - name: Check failure artifacts + run: | + rm ./artifacts/build -rf + if [ -n "$(ls -A ./artifacts)" ]; then + echo "There were some failures in the previous jobs" + exit 1 + fi + + - name: Generate Coverage report + working-directory: app + run: | + npx nyc merge ./coverage-jsons/ .nyc_output/out.json + npx nyc report --reporter=html-spa + cp .nyc_output/out.json coverage/coverage-final.json + + - name: Upload Coverage report + uses: actions/upload-artifact@v4 + with: + name: coverage-html + path: app/coverage/* diff --git a/angular.json b/angular.json index 07faf01a0..ad7c8b33f 100644 --- a/angular.json +++ b/angular.json @@ -91,7 +91,7 @@ "defaultConfiguration": "production" }, "serve": { - "builder": "@angular-devkit/build-angular:dev-server", + "builder": "@angular-builders/custom-webpack:dev-server", "options": { "disableHostCheck": true, "port": 8100, diff --git a/jest.config.js b/jest.config.js index 7ae910a5f..94c01ee1a 100644 --- a/jest.config.js +++ b/jest.config.js @@ -6,8 +6,13 @@ module.exports = { setupFilesAfterEnv: ['/src/testing/setup.ts'], testMatch: ['**/?(*.)test.ts'], collectCoverageFrom: [ - 'src/**/*.{ts,html}', + 'src/**/*.ts', + '!src/**/*.{test,stories}.ts', + '!src/assets/**/*', '!src/testing/**/*', + '!src/storybook/**/*', + '!src/core/initializers/index.ts', + '!src/core/features/emulators/services/zip.ts', ], transformIgnorePatterns: ['node_modules/(?!@stencil|@angular|@ionic|@moodlehq|@ngx-translate|@awesome-cordova-plugins|swiper)'], moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { prefix: '/src/' }), diff --git a/local_moodleappbehat/tests/behat/behat_app.php b/local_moodleappbehat/tests/behat/behat_app.php index c5b4ccfa6..ca9a1557b 100644 --- a/local_moodleappbehat/tests/behat/behat_app.php +++ b/local_moodleappbehat/tests/behat/behat_app.php @@ -20,6 +20,7 @@ require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); require_once(__DIR__ . '/behat_app_helper.php'); use Behat\Behat\Hook\Scope\ScenarioScope; +use Behat\Behat\Hook\Scope\AfterStepScope; use Behat\Gherkin\Node\TableNode; use Behat\Mink\Exception\DriverException; use Behat\Mink\Exception\ExpectationException; @@ -44,7 +45,10 @@ class behat_app extends behat_app_helper { ], ]; - protected $featurepath = ''; + protected $featurepath; + protected $coveragepath; + protected $scenarioslug; + protected $scenariolaststep; /** * @BeforeScenario @@ -56,7 +60,35 @@ class behat_app extends behat_app_helper { return; } + $steps = $scope->getScenario()->getSteps(); + + $this->scenarioslug = $this->get_scenario_slug($scope); + $this->scenariolaststep = $steps[count($steps) - 1]; $this->featurepath = dirname($feature->getFile()); + $this->coveragepath = get_config('local_moodleappbehat', 'coverage_path') ?: ($this->featurepath . DIRECTORY_SEPARATOR . 'coverage' . DIRECTORY_SEPARATOR); + } + + /** + * @AfterStep + */ + public function after_step(AfterStepScope $scope) { + $step = $scope->getStep(); + + if ($step !== $this->scenariolaststep || empty($this->coveragepath)) { + return; + } + + if (!is_dir($this->coveragepath)) { + if (!@mkdir($this->coveragepath, 0777, true)) { + throw new Exception("Cannot create {$this->coveragepath} directory."); + } + } + + $coverage = $this->runtime_js('getCoverage()'); + + if (!is_null($coverage)) { + file_put_contents($this->coveragepath . $this->scenarioslug . '.json', $coverage); + } } /** diff --git a/local_moodleappbehat/tests/behat/behat_app_helper.php b/local_moodleappbehat/tests/behat/behat_app_helper.php index 50a905623..6cf42e881 100644 --- a/local_moodleappbehat/tests/behat/behat_app_helper.php +++ b/local_moodleappbehat/tests/behat/behat_app_helper.php @@ -18,6 +18,7 @@ require_once(__DIR__ . '/../../../../lib/behat/behat_base.php'); +use Behat\Behat\Hook\Scope\ScenarioScope; use Behat\Mink\Exception\DriverException; use Behat\Mink\Exception\ExpectationException; use Moodle\BehatExtension\Exception\SkippedException; @@ -357,7 +358,7 @@ class behat_app_helper extends behat_base { * @return mixed Result. */ protected function runtime_js(string $script) { - return $this->evaluate_script("window.behat.$script"); + return $this->evaluate_script("window.behat?.$script"); } /** @@ -472,6 +473,22 @@ class behat_app_helper extends behat_base { $this->i_wait_the_app_to_restart(); } + /** + * Get scenario slug. + * + * @param ScenarioScope $scope Scenario scope. + * @return string Slug. + */ + protected function get_scenario_slug(ScenarioScope $scope): string { + $text = $scope->getFeature()->getTitle() . ' ' . $scope->getScenario()->getTitle(); + $text = trim($text); + $text = strtolower($text); + $text = preg_replace('/\s+/', '-', $text); + $text = preg_replace('/[^a-z0-9-]/', '', $text); + + return $text; + } + /** * Returns the current mobile url scheme of the site. */ diff --git a/package-lock.json b/package-lock.json index 2c5b20b15..749693153 100644 --- a/package-lock.json +++ b/package-lock.json @@ -109,6 +109,7 @@ "@angular/language-service": "^17.3.0", "@ionic/angular-toolkit": "^10.0.0", "@ionic/cli": "^7.2.0", + "@jsdevtools/coverage-istanbul-loader": "^3.0.5", "@types/faker": "^5.1.3", "@types/jest": "^26.0.24", "@types/node": "^18.0.0", @@ -137,6 +138,7 @@ "jsonc-parser": "^2.3.1", "minimatch": "^9.0.3", "native-run": "^2.0.0", + "nyc": "^15.1.0", "patch-package": "^6.5.0", "terser-webpack-plugin": "^5.3.9", "ts-jest": "^29.1.1", @@ -5291,6 +5293,106 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@jsdevtools/coverage-istanbul-loader": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@jsdevtools/coverage-istanbul-loader/-/coverage-istanbul-loader-3.0.5.tgz", + "integrity": "sha512-EUCPEkaRPvmHjWAAZkWMT7JDzpw7FKB00WTISaiXsbNOd5hCHg77XLA8sLYLFDo1zepYLo2w7GstN8YBqRXZfA==", + "dev": true, + "dependencies": { + "convert-source-map": "^1.7.0", + "istanbul-lib-instrument": "^4.0.3", + "loader-utils": "^2.0.0", + "merge-source-map": "^1.1.0", + "schema-utils": "^2.7.0" + } + }, + "node_modules/@jsdevtools/coverage-istanbul-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@jsdevtools/coverage-istanbul-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/@jsdevtools/coverage-istanbul-loader/node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jsdevtools/coverage-istanbul-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/@jsdevtools/coverage-istanbul-loader/node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/@jsdevtools/coverage-istanbul-loader/node_modules/schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 8.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/@jsdevtools/coverage-istanbul-loader/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@leichtgewicht/ip-codec": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@leichtgewicht/ip-codec/-/ip-codec-2.0.5.tgz", @@ -8475,11 +8577,29 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/aproba": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, "node_modules/are-docs-informative": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", @@ -9389,6 +9509,21 @@ "node": ">=12" } }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -9988,6 +10123,12 @@ "resolved": "https://registry.npmjs.org/common-path-prefix/-/common-path-prefix-3.0.0.tgz", "integrity": "sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==" }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, "node_modules/component-emitter": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", @@ -11928,6 +12069,21 @@ "node": ">= 10" } }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -12485,6 +12641,12 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.2.tgz", "integrity": "sha512-l60ETUTmLqbVbVHv1J4/qj+M8nq7AwMzEcg3kmJDt9dCNrTk+yHcYFf/Kw75pMDwd9mPcIGCG5LcS20SxYRzFA==" }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, "node_modules/es6-promise-plugin": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/es6-promise-plugin/-/es6-promise-plugin-4.2.2.tgz", @@ -14276,6 +14438,26 @@ "node": ">= 0.6" } }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "dev": true, + "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/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -15012,6 +15194,31 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -16256,6 +16463,18 @@ "node": ">=8" } }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/istanbul-lib-instrument": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", @@ -16279,6 +16498,35 @@ "semver": "bin/semver.js" } }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", @@ -19020,6 +19268,12 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, "node_modules/lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -19480,6 +19734,24 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" }, + "node_modules/merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "dependencies": { + "source-map": "^0.6.1" + } + }, + "node_modules/merge-source-map/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -20241,6 +20513,18 @@ "integrity": "sha512-QNABxbrPa3qEIfrE6GOJ7BYIuignnJw7iQ2YPbc3Nla1HzRJjXzZOiikfF8m7eAMfichLt3M4VgLOetqgDmgGQ==", "dev": true }, + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/node-releases": { "version": "2.0.14", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", @@ -20831,6 +21115,218 @@ "node": ">=8" } }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" + } + }, + "node_modules/nyc/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==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/nyc/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==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/nyc/node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/nyc/node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/nyc/node_modules/istanbul-lib-instrument": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", + "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", + "dev": true, + "dependencies": { + "@babel/core": "^7.7.5", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-coverage": "^3.0.0", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/p-map": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/nyc/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -21245,6 +21741,21 @@ "node": ">= 14" } }, + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/pacote": { "version": "17.0.6", "resolved": "https://registry.npmjs.org/pacote/-/pacote-17.0.6.tgz", @@ -22539,6 +23050,18 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/promise-all-reject-late": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", @@ -23076,6 +23599,18 @@ "jsesc": "bin/jsesc" } }, + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", @@ -24108,6 +24643,51 @@ "integrity": "sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==", "dev": true }, + "node_modules/spawn-wrap": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/spawn-wrap/node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/spawn-wrap/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/spdx-correct": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", diff --git a/package.json b/package.json index 08e57eae8..d7be2c677 100644 --- a/package.json +++ b/package.json @@ -143,6 +143,7 @@ "@angular/language-service": "^17.3.0", "@ionic/angular-toolkit": "^10.0.0", "@ionic/cli": "^7.2.0", + "@jsdevtools/coverage-istanbul-loader": "^3.0.5", "@types/faker": "^5.1.3", "@types/jest": "^26.0.24", "@types/node": "^18.0.0", @@ -171,6 +172,7 @@ "jsonc-parser": "^2.3.1", "minimatch": "^9.0.3", "native-run": "^2.0.0", + "nyc": "^15.1.0", "patch-package": "^6.5.0", "terser-webpack-plugin": "^5.3.9", "ts-jest": "^29.1.1", diff --git a/src/testing/services/behat-runtime.ts b/src/testing/services/behat-runtime.ts index 9f98a5f3d..8a71e9a8f 100644 --- a/src/testing/services/behat-runtime.ts +++ b/src/testing/services/behat-runtime.ts @@ -110,6 +110,19 @@ export class TestingBehatRuntimeService { CoreIframeComponent.loadingTimeout = 1000; } + /** + * Get coverage data. + * + * @returns Coverage data. + */ + getCoverage(): string | null { + if (!('__coverage__' in window)) { + return null; + } + + return JSON.stringify(window.__coverage__); + } + /** * Check whether the service has been initialized or not. * diff --git a/webpack.config.js b/webpack.config.js index 1f9aad474..ccc4a6948 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -33,5 +33,18 @@ module.exports = config => { }), ); + if (process.env.MOODLE_APP_COVERAGE) { + const tsConfig = config.module.rules[2]; + + config.module.rules.splice(2, 0, { + ...tsConfig, + exclude: /node_modules/, + use: [ + '@jsdevtools/coverage-istanbul-loader', + ...tsConfig.use, + ], + }); + } + return config; };