├── .editorconfig ├── .gitattributes ├── .github ├── dependabot.yml ├── mergify.yml ├── renovate.json ├── setup │ └── action.yml └── workflows │ ├── ci.yml │ ├── mutation-testing.yml │ ├── release.yml │ └── update-screenshots.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .scala-steward.conf ├── .vscode ├── extensions.json ├── launch.json ├── settings.json └── tasks.json ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs ├── equivalent-mutants.md ├── img │ └── static-mutant.png ├── implementing-real-time-reporting.md ├── mutant-states-and-metrics.md ├── sonarqube-integration.md ├── static-mutants.md └── supported-mutators.md ├── integrations └── mutation-report-to-sonar.jq ├── lerna.json ├── libs └── eslint-plugin-mte │ ├── eslint.config.js │ ├── index.d.ts │ ├── index.js │ └── package.json ├── nx.json ├── package-lock.json ├── package.json ├── packages ├── elements │ ├── .browserslistrc │ ├── .bundlemonrc.json │ ├── .npmignore │ ├── .prettierrc │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── CHANGELOG.md │ ├── README.md │ ├── docs │ │ ├── directory-result-example.png │ │ ├── file-result-example-dark.png │ │ └── file-result-example.png │ ├── eslint.config.js │ ├── index.html │ ├── package.json │ ├── playwright.config.ts │ ├── src │ │ ├── components │ │ │ ├── README.md │ │ │ ├── app │ │ │ │ ├── app.component.ts │ │ │ │ └── theme.css │ │ │ ├── base-element.ts │ │ │ ├── breadcrumb.ts │ │ │ ├── drawer-mutant │ │ │ │ ├── drawer-mutant.component.ts │ │ │ │ └── util.ts │ │ │ ├── drawer-test │ │ │ │ └── drawer-test.component.ts │ │ │ ├── drawer │ │ │ │ ├── drawer.component.css │ │ │ │ ├── drawer.component.ts │ │ │ │ └── util.ts │ │ │ ├── file-icon │ │ │ │ ├── file-icon.component.ts │ │ │ │ └── file-icon.css │ │ │ ├── file-picker │ │ │ │ └── file-picker.component.ts │ │ │ ├── file │ │ │ │ ├── file.component.ts │ │ │ │ ├── file.css │ │ │ │ └── util.ts │ │ │ ├── metrics-table │ │ │ │ └── metrics-table.component.ts │ │ │ ├── mutant-view │ │ │ │ └── mutant-view.ts │ │ │ ├── real-time-element.ts │ │ │ ├── result-status-bar │ │ │ │ └── result-status-bar.ts │ │ │ ├── state-filter │ │ │ │ └── state-filter.component.ts │ │ │ ├── test-file │ │ │ │ ├── test-file.component.ts │ │ │ │ └── test-file.css │ │ │ ├── test-view │ │ │ │ └── test-view.ts │ │ │ ├── theme-switch │ │ │ │ ├── theme-switch.component.ts │ │ │ │ └── theme-switch.css │ │ │ └── tooltip │ │ │ │ └── tooltip.component.ts │ │ ├── declaration.d.ts │ │ ├── index.ts │ │ ├── lib │ │ │ ├── browser.ts │ │ │ ├── code-helpers.ts │ │ │ ├── custom-events.ts │ │ │ ├── html-helpers.ts │ │ │ ├── mutant-changes.ts │ │ │ ├── router.ts │ │ │ ├── svg-icons.ts │ │ │ └── theme.ts │ │ ├── style │ │ │ ├── code.css │ │ │ ├── index.ts │ │ │ ├── prism-plugins.ts │ │ │ ├── prismjs.css │ │ │ └── tailwind.css │ │ ├── tsconfig.json │ │ └── typings │ │ │ └── prism.d.ts │ ├── stryker.conf.js │ ├── test │ │ ├── integration │ │ │ ├── directoryReport.it.spec.ts │ │ │ ├── drawer-mutant.it.spec.ts │ │ │ ├── drawer-mutant.it.spec.ts-snapshots │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-chromium-linux.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-chromium-win32.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-firefox-linux.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-firefox-win32.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-chromium-linux.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-chromium-win32.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-firefox-linux.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-firefox-win32.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-chromium-linux.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-chromium-win32.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-firefox-linux.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-firefox-win32.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-chromium-linux.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-chromium-win32.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-firefox-linux.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-firefox-win32.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-chromium-linux.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-chromium-win32.png │ │ │ │ ├── Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-firefox-linux.png │ │ │ │ └── Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-firefox-win32.png │ │ │ ├── drawer-test.it.spec.ts │ │ │ ├── drawer-test.it.spec.ts-snapshots │ │ │ │ ├── Drawer-test-view-should-look-as-expected-1-chromium-linux.png │ │ │ │ ├── Drawer-test-view-should-look-as-expected-1-chromium-win32.png │ │ │ │ ├── Drawer-test-view-should-look-as-expected-1-firefox-linux.png │ │ │ │ ├── Drawer-test-view-should-look-as-expected-1-firefox-win32.png │ │ │ │ ├── Drawer-test-view-should-look-as-expected-in-dark-mode-1-chromium-linux.png │ │ │ │ ├── Drawer-test-view-should-look-as-expected-in-dark-mode-1-chromium-win32.png │ │ │ │ ├── Drawer-test-view-should-look-as-expected-in-dark-mode-1-firefox-linux.png │ │ │ │ ├── Drawer-test-view-should-look-as-expected-in-dark-mode-1-firefox-win32.png │ │ │ │ ├── Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-chromium-linux.png │ │ │ │ ├── Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-chromium-win32.png │ │ │ │ ├── Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-firefox-linux.png │ │ │ │ ├── Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-firefox-win32.png │ │ │ │ ├── Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-chromium-linux.png │ │ │ │ ├── Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-chromium-win32.png │ │ │ │ ├── Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-firefox-linux.png │ │ │ │ └── Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-firefox-win32.png │ │ │ ├── file-picker.it.spec.ts │ │ │ ├── fileReport.it.spec.ts │ │ │ ├── fileReport.it.spec.ts-snapshots │ │ │ │ ├── File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-chromium-linux.png │ │ │ │ ├── File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-chromium-win32.png │ │ │ │ ├── File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-firefox-linux.png │ │ │ │ └── File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-firefox-win32.png │ │ │ ├── lib │ │ │ │ ├── SseServer.ts │ │ │ │ ├── browser.ts │ │ │ │ ├── constants.ts │ │ │ │ └── helpers.ts │ │ │ ├── navigation.it.spec.ts │ │ │ ├── phpReport.it.spec.ts │ │ │ ├── po │ │ │ │ ├── Breadcrumb.po.ts │ │ │ │ ├── Drawer.po.ts │ │ │ │ ├── ElementSelector.po.ts │ │ │ │ ├── FilePicker.po.ts │ │ │ │ ├── MutantDot.po.ts │ │ │ │ ├── MutantElement.po.ts │ │ │ │ ├── MutantMarker.po.ts │ │ │ │ ├── MutantView.po.ts │ │ │ │ ├── NavTab.po.ts │ │ │ │ ├── PageObject.po.ts │ │ │ │ ├── ProgressBar.po.ts │ │ │ │ ├── RealTimeProgressBar.po.ts │ │ │ │ ├── ReportPage.ts │ │ │ │ ├── ResultTable.po.ts │ │ │ │ ├── ResultTableRow.po.ts │ │ │ │ ├── StateFilter.po.ts │ │ │ │ ├── StateFilterCheckbox.po.ts │ │ │ │ ├── TestDot.po.ts │ │ │ │ ├── TestListItem.po.ts │ │ │ │ ├── TestView.po.ts │ │ │ │ ├── ThemeSelector.po.ts │ │ │ │ └── View.po.ts │ │ │ ├── real-time-reporting.it.spec.ts │ │ │ ├── test-view.it.spec.ts │ │ │ ├── test-view.it.spec.ts-snapshots │ │ │ │ ├── Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-chromium-linux.png │ │ │ │ ├── Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-chromium-win32.png │ │ │ │ ├── Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-firefox-linux.png │ │ │ │ └── Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-firefox-win32.png │ │ │ ├── theming.it.spec.ts │ │ │ ├── theming.it.spec.ts-snapshots │ │ │ │ ├── Theming-dark-theme-should-match-the-dark-theme-1-chromium-linux.png │ │ │ │ ├── Theming-dark-theme-should-match-the-dark-theme-1-chromium-win32.png │ │ │ │ ├── Theming-dark-theme-should-match-the-dark-theme-1-firefox-linux.png │ │ │ │ ├── Theming-dark-theme-should-match-the-dark-theme-1-firefox-win32.png │ │ │ │ ├── Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-chromium-linux.png │ │ │ │ ├── Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-chromium-win32.png │ │ │ │ ├── Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-firefox-linux.png │ │ │ │ ├── Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-firefox-win32.png │ │ │ │ ├── Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-chromium-linux.png │ │ │ │ ├── Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-chromium-win32.png │ │ │ │ ├── Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-firefox-linux.png │ │ │ │ ├── Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-firefox-win32.png │ │ │ │ ├── Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-chromium-linux.png │ │ │ │ ├── Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-chromium-win32.png │ │ │ │ ├── Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-firefox-linux.png │ │ │ │ ├── Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-firefox-win32.png │ │ │ │ ├── Theming-light-theme-should-match-the-light-theme-1-chromium-linux.png │ │ │ │ ├── Theming-light-theme-should-match-the-light-theme-1-chromium-win32.png │ │ │ │ ├── Theming-light-theme-should-match-the-light-theme-1-firefox-linux.png │ │ │ │ ├── Theming-light-theme-should-match-the-light-theme-1-firefox-win32.png │ │ │ │ ├── Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-chromium-linux.png │ │ │ │ ├── Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-chromium-win32.png │ │ │ │ ├── Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-firefox-linux.png │ │ │ │ ├── Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-firefox-win32.png │ │ │ │ ├── Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-chromium-linux.png │ │ │ │ ├── Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-chromium-win32.png │ │ │ │ ├── Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-firefox-linux.png │ │ │ │ ├── Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-firefox-win32.png │ │ │ │ ├── Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-chromium-linux.png │ │ │ │ ├── Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-chromium-win32.png │ │ │ │ ├── Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-firefox-linux.png │ │ │ │ └── Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-firefox-win32.png │ │ │ ├── tsconfig.json │ │ │ └── unsanitized.it.spec.ts │ │ └── unit │ │ │ ├── components │ │ │ ├── app.component.spec.ts │ │ │ ├── breadcrumb.component.spec.ts │ │ │ ├── drawer-mutant.component.spec.ts │ │ │ ├── drawer-test.component.spec.ts │ │ │ ├── drawer.component.spec.ts │ │ │ ├── file-icon.component.spec.ts │ │ │ ├── file-picker.component.spec.ts │ │ │ ├── file.component.spec.ts │ │ │ ├── mutant-view.component.spec.ts │ │ │ ├── result-status-bar.component.spec.ts │ │ │ ├── state-filter.component.spec.ts │ │ │ ├── test-file.component.spec.ts │ │ │ ├── test-view.component.spec.ts │ │ │ ├── theme-switch.component.spec.ts │ │ │ └── totals.component.spec.ts │ │ │ ├── helpers │ │ │ ├── CustomElementFixture.ts │ │ │ ├── factory.ts │ │ │ ├── helperFunctions.ts │ │ │ └── tick.ts │ │ │ ├── lib │ │ │ ├── browser.spec.ts │ │ │ ├── code-helpers.spec.ts │ │ │ ├── html-helpers.spec.ts │ │ │ └── router.spec.ts │ │ │ ├── setup.ts │ │ │ ├── tsconfig.json │ │ │ └── typings │ │ │ └── prism.d.ts │ ├── testResources │ │ ├── csharp-example │ │ │ ├── index.html │ │ │ ├── mutation-report-old.json │ │ │ └── mutation-report.json │ │ ├── cucumber-example │ │ │ ├── index.html │ │ │ └── mutation.json │ │ ├── deep-dir-example │ │ │ ├── index.html │ │ │ └── mutation-report.json │ │ ├── index.html │ │ ├── infection-php-example │ │ │ ├── index.html │ │ │ └── report.json │ │ ├── install-local-example │ │ │ └── index.html │ │ ├── large-example │ │ │ ├── index.html │ │ │ └── report.json │ │ ├── lighthouse-example │ │ │ ├── index.html │ │ │ └── mutation.json │ │ ├── pitest-example │ │ │ ├── index.html │ │ │ └── report.js │ │ ├── realtime-reporting-example │ │ │ ├── index.html │ │ │ └── mutation-report.json │ │ ├── rust-example │ │ │ ├── index.html │ │ │ └── report.json │ │ ├── scala-example │ │ │ ├── index.html │ │ │ └── mutation-report.json │ │ ├── simple-example │ │ │ ├── index.html │ │ │ └── mutation-report.json │ │ ├── stryker-example │ │ │ ├── index.html │ │ │ └── mutation-report.json │ │ ├── svelte-example │ │ │ ├── index.html │ │ │ ├── mutation-report.json │ │ │ └── svelte-logo.svg │ │ ├── test-files-example │ │ │ ├── index.html │ │ │ └── mutation-report.json │ │ ├── tests-example │ │ │ ├── index.html │ │ │ └── mutation-report.json │ │ └── unsanitized-example │ │ │ ├── index.html │ │ │ └── mutation-report.json │ ├── tsconfig.elements.settings.json │ ├── tsconfig.json │ └── vite.config.ts ├── metrics-scala │ ├── .gitignore │ ├── .npmignore │ ├── .scalafix.conf │ ├── .scalafmt.conf │ ├── .vscode │ │ └── settings.json │ ├── CHANGELOG.md │ ├── NPM_PROJECTS_PUBLISHING.md │ ├── README.md │ ├── build.sbt │ ├── circe │ │ └── src │ │ │ ├── main │ │ │ └── scala │ │ │ │ └── mutationtesting │ │ │ │ ├── CodecOps.scala │ │ │ │ └── circe.scala │ │ │ └── test │ │ │ ├── scala │ │ │ └── mutationtesting │ │ │ │ └── EncoderTest.scala │ │ │ └── scalajvm │ │ │ └── mutationtesting │ │ │ └── SchemaTest.scala │ ├── docs │ │ └── README.md │ ├── metrics │ │ └── src │ │ │ ├── main │ │ │ └── scala │ │ │ │ └── mutationtesting │ │ │ │ ├── Metrics.scala │ │ │ │ ├── MetricsResult.scala │ │ │ │ ├── MutantStatus.scala │ │ │ │ ├── Thresholds.scala │ │ │ │ ├── mutationTestResult.scala │ │ │ │ └── package.scala │ │ │ └── test │ │ │ └── scala │ │ │ └── mutationtesting │ │ │ ├── MetricsResultTest.scala │ │ │ ├── MetricsTest.scala │ │ │ └── MutationTestResultTest.scala │ ├── npmProjPublish.sh │ ├── package.json │ ├── project │ │ ├── build.properties │ │ └── plugins.sbt │ └── stryker4s.conf ├── metrics │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── eslint.config.js │ ├── package.json │ ├── src │ │ ├── aggregate.ts │ │ ├── calculateMetrics.ts │ │ ├── helpers │ │ │ ├── file.ts │ │ │ ├── group-by.ts │ │ │ ├── index.ts │ │ │ ├── is-not-nullish.ts │ │ │ └── text.ts │ │ ├── index.ts │ │ ├── model │ │ │ ├── file-under-test-model.ts │ │ │ ├── index.ts │ │ │ ├── metrics-result.ts │ │ │ ├── metrics.ts │ │ │ ├── mutant-model.ts │ │ │ ├── mutation-test-metrics-result.ts │ │ │ ├── source-file.ts │ │ │ ├── test-file-model.ts │ │ │ ├── test-metrics.ts │ │ │ └── test-model.ts │ │ └── tsconfig.json │ ├── stryker.conf.js │ ├── test │ │ ├── helpers │ │ │ └── factories.ts │ │ ├── tsconfig.json │ │ └── unit │ │ │ ├── aggregate.spec.ts │ │ │ ├── calculateMetrics.spec.ts │ │ │ ├── helpers.spec.ts │ │ │ └── model │ │ │ ├── file-under-test-model.spec.ts │ │ │ ├── mutant-model.spec.ts │ │ │ ├── test-file-model.spec.ts │ │ │ └── test-model.spec.ts │ └── tsconfig.json ├── real-time │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── eslint.config.js │ ├── package.json │ ├── src │ │ ├── index.ts │ │ ├── mutation-event-sender.ts │ │ ├── real-time-options.ts │ │ ├── real-time-reporter.ts │ │ ├── tsconfig.json │ │ └── types.d.ts │ ├── stryker.conf.js │ ├── test │ │ ├── integration │ │ │ ├── mutant-event-source.ts │ │ │ └── real-time-reporter.it.spec.ts │ │ ├── tsconfig.json │ │ └── unit │ │ │ ├── mutation-event-sender.spec.ts │ │ │ └── real-time-reporter.spec.ts │ └── tsconfig.json └── report-schema │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── eslint.config.js │ ├── package.json │ ├── src │ ├── index.ts │ ├── mutation-testing-report-schema.json │ └── tsconfig.json │ ├── test │ ├── tsconfig.json │ └── unit │ │ ├── JsonValidator.spec.ts │ │ └── TypeScript.spec.ts │ ├── testResources │ ├── additional-properties-report.json │ ├── data-url.json │ ├── missing-end-location.json │ ├── missing-framework-name.json │ ├── missing-mutant-location-report.json │ ├── missing-performance-fields.json │ ├── missing-replacement-report.json │ ├── missing-system-ci.json │ ├── missing-system-cpu-logical-cores.json │ ├── missing-system-os-platform.json │ ├── missing-system-ram-total.json │ ├── missing-test-files.json │ ├── missing-test-name.json │ ├── missing-tests.json │ ├── missing-version-report.json │ ├── strict-report-v1.json │ ├── strict-report-v2.json │ └── thresholds │ │ ├── threshold-too-high-report.json │ │ └── threshold-too-low-report.json │ └── tsconfig.json ├── stryker.parent.json ├── tasks ├── download-incremental-reports.sh └── schema2ts.mjs ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.settings.json └── workspace.code-workspace /.editorconfig: -------------------------------------------------------------------------------- 1 | # 2 space indentation 2 | [{*.ts,*.js,*.json,*.jsonc,*.code-workspace,*.scss,*.css,*.html}] 3 | indent_style = space 4 | indent_size = 2 5 | end_of_line = lf 6 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.db binary 3 | *.gif binary 4 | *.ico binary 5 | *.jpg binary 6 | *.png binary 7 | *.zip binary -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | commit-message: 8 | prefix: 'build' 9 | prefix-development: 'build' 10 | include: 'scope' 11 | -------------------------------------------------------------------------------- /.github/mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: automatic merge for Dependabot devDependencies pull requests 3 | conditions: 4 | - author~=^(dependabot\[bot\]|scala-steward)$ 5 | - check-success=build_and_test (18.x, ubuntu-latest) 6 | - check-success=build_and_test (18.x, windows-latest) 7 | - check-success=build_and_test (20.x, ubuntu-latest) 8 | - check-success=build_and_test (20.x, windows-latest) 9 | - title~=^build\(deps-dev\) 10 | actions: 11 | merge: 12 | method: squash 13 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:recommended", "config:js-lib", "group:linters", "group:test"], 4 | "timezone": "Europe/Amsterdam", 5 | "schedule": ["every weekend"], 6 | "configMigration": true, 7 | "lockFileMaintenance": { 8 | "enabled": true 9 | }, 10 | "github-actions": { 11 | "extends": [":preserveSemverRanges"] 12 | }, 13 | "packageRules": [ 14 | { 15 | "matchDepTypes": ["devDependencies"], 16 | "automerge": true 17 | }, 18 | { 19 | "matchUpdateTypes": ["patch", "pin", "digest", "lockFileMaintenance"], 20 | "automerge": true 21 | }, 22 | { 23 | "groupName": "Types", 24 | "matchPackageNames": ["@types/{/,}**"] 25 | }, 26 | { 27 | "groupName": "linters", 28 | "matchPackageNames": ["/eslint/", "/prettier/"] 29 | }, 30 | { 31 | "groupName": "Tailwind packages", 32 | "matchPackageNames": ["/tailwindcss/"] 33 | } 34 | ], 35 | "automergeStrategy": "squash", 36 | "platformAutomerge": true, 37 | "ignorePaths": ["**/*.sbt"] 38 | } 39 | -------------------------------------------------------------------------------- /.github/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Setup CI' 2 | description: 'Set up npm, Java, and cache for CI' 3 | 4 | inputs: 5 | node-version: 6 | description: 'The Node.js version to use' 7 | default: '22.x' 8 | required: false 9 | setup-java: 10 | description: 'Whether to set up Java' 11 | default: 'false' 12 | required: false 13 | powerpack-license: 14 | description: 'The license for the Nx Powerpack' 15 | required: true 16 | runs: 17 | using: 'composite' 18 | steps: 19 | - uses: nrwl/nx-set-shas@v4 20 | with: 21 | main-branch-name: master 22 | - name: Use Node.js ${{ inputs.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ inputs.node-version }} 26 | cache: 'npm' 27 | - uses: actions/setup-java@v4 28 | if: ${{ inputs.setup-java == 'true' }} 29 | with: 30 | distribution: 'temurin' 31 | java-version: 21 32 | cache: 'sbt' 33 | - uses: sbt/setup-sbt@v1 34 | - uses: actions/cache@v4 35 | if: ${{ github.actor == 'stryker-mutator' }} 36 | with: 37 | path: .nx/cache 38 | key: ${{ runner.os }}-nx-${{ inputs.node-version }}-${{ hashFiles('**/package-lock.json') }} 39 | restore-keys: | 40 | ${{ runner.os }}-nx-${{ inputs.node-version }}- 41 | ${{ runner.os }}-nx- 42 | - run: npm ci 43 | shell: bash 44 | - run: | 45 | echo "NX_POWERPACK_LICENSE=${{ inputs.powerpack-license }}" >> $GITHUB_ENV 46 | npx nx activate-powerpack "${{ inputs.powerpack-license }}" 47 | if: ${{ github.actor == 'stryker-mutator' }} 48 | shell: bash 49 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | types: [synchronize, opened, reopened] 8 | 9 | jobs: 10 | build_and_test: 11 | runs-on: ${{ matrix.os }} 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | node-version: [20.x, 22.x] 16 | os: [ubuntu-latest, windows-latest] 17 | 18 | steps: 19 | - uses: actions/checkout@v4 20 | with: 21 | fetch-depth: 0 22 | - uses: ./.github/setup 23 | with: 24 | node-version: ${{ matrix.node-version }} 25 | setup-java: true 26 | powerpack-license: ${{ secrets.NX_POWERPACK_LICENSE }} 27 | # Only install playwright if elements project is affected 28 | - name: affected 29 | id: affected 30 | shell: bash 31 | run: echo "affected=$(npx nx show projects --affected --json)" >> "$GITHUB_OUTPUT" 32 | - name: Install Playwright Browsers 33 | if: ${{ contains(fromJson(steps.affected.outputs.affected), 'elements') }} 34 | run: npx playwright install chromium firefox --with-deps 35 | - name: Lint & Build & Test 36 | run: npm run all 37 | - name: Bundlemon 38 | if: ${{ contains(fromJson(steps.affected.outputs.affected), 'elements') && matrix.os == 'ubuntu-latest' && matrix.node-version == '22.x' }} 39 | uses: lironer/bundlemon-action@v1 40 | with: 41 | working-directory: packages/elements 42 | - uses: actions/upload-artifact@v4 43 | if: failure() 44 | with: 45 | name: image-diffs 46 | path: packages/elements/playwright-report/ 47 | incremental_mutation_testing: 48 | runs-on: ubuntu-latest 49 | steps: 50 | - uses: actions/checkout@v4 51 | - uses: ./.github/setup 52 | with: 53 | powerpack-license: ${{ secrets.NX_POWERPACK_LICENSE }} 54 | - name: Install Playwright Browsers 55 | run: npx playwright install chromium --with-deps 56 | - run: npm run download-incremental-reports 57 | - name: Run Stryker incrementally 58 | run: | 59 | npx nx run-many --target=stryker --projects=metrics,elements,real-time -- --incremental 60 | env: 61 | STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} 62 | -------------------------------------------------------------------------------- /.github/workflows/mutation-testing.yml: -------------------------------------------------------------------------------- 1 | name: Mutation testing 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | workflow_dispatch: 8 | 9 | jobs: 10 | nodejs: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: ./.github/setup 15 | with: 16 | powerpack-license: ${{ secrets.NX_POWERPACK_LICENSE }} 17 | - run: npx nx run metrics:stryker 18 | env: 19 | STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} 20 | - run: npx nx run real-time:stryker 21 | env: 22 | STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} 23 | elements: 24 | runs-on: ubuntu-latest 25 | steps: 26 | - uses: actions/checkout@v4 27 | - uses: ./.github/setup 28 | with: 29 | powerpack-license: ${{ secrets.NX_POWERPACK_LICENSE }} 30 | - name: Install Playwright Browsers 31 | run: npx playwright install chromium --with-deps 32 | - name: Run Stryker 33 | run: npx nx run elements:stryker 34 | env: 35 | STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} 36 | 37 | metrics-scala: 38 | runs-on: ubuntu-latest 39 | steps: 40 | - uses: actions/checkout@v4 41 | - uses: ./.github/setup 42 | with: 43 | setup-java: true 44 | powerpack-license: ${{ secrets.NX_POWERPACK_LICENSE }} 45 | - name: Run Stryker 46 | run: npx nx run metrics-scala:stryker 47 | env: 48 | STRYKER_DASHBOARD_API_KEY: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} 49 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: ['*'] 6 | 7 | jobs: 8 | release: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v4 15 | with: 16 | fetch-depth: 0 17 | - uses: ./.github/setup 18 | with: 19 | setup-java: true 20 | powerpack-license: ${{ secrets.NX_POWERPACK_LICENSE }} 21 | - name: Set Release Env 22 | run: | 23 | echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > ~/.npmrc 24 | echo ${{ secrets.PGP_SECRET }} | base64 --decode | gpg --batch --import 25 | - run: npx nx run-many --target=build 26 | - name: Release 27 | run: npm run lerna:publish 28 | env: 29 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 30 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 31 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 32 | NPM_CONFIG_PROVENANCE: true 33 | -------------------------------------------------------------------------------- /.github/workflows/update-screenshots.yml: -------------------------------------------------------------------------------- 1 | name: Update screenshots 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | update: 6 | runs-on: ${{ matrix.os }} 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | os: [ubuntu-latest, windows-latest] 11 | steps: 12 | - name: Generate token 13 | id: generate-token 14 | uses: tibdex/github-app-token@v2 15 | with: 16 | app_id: ${{ vars.STRYKER_MUTATOR_APP_ID }} 17 | private_key: ${{ secrets.STRYKER_MUTATOR_NPA_KEY }} 18 | - uses: actions/checkout@v4 19 | with: 20 | token: ${{ steps.generate-token.outputs.token }} 21 | - uses: ./.github/setup 22 | with: 23 | powerpack-license: ${{ secrets.NX_POWERPACK_LICENSE }} 24 | - run: npx playwright install chromium firefox --with-deps 25 | - run: npx nx run-many --target=build 26 | - run: npx nx run elements:test:integration:update 27 | - name: Commit 28 | run: | 29 | git config --global user.name "stryker-mutator[bot]" 30 | git config --global user.email 158062761+stryker-mutator[bot]@users.noreply.github.com 31 | git pull 32 | git add . 33 | git commit -m "test(screenshots): update screenshots for ${{ runner.os }}" 34 | git push 35 | env: 36 | GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }} 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | dist-tsc 4 | .stryker-tmp 5 | reports 6 | src-generated 7 | *.tsbuildinfo 8 | 9 | target/ 10 | _site/ 11 | .metals/ 12 | .bloop/ 13 | .bsp/ 14 | .idea/ 15 | pom.xml.versionsBackup 16 | packages/*/mvn/resources/ 17 | .scala-build/ 18 | 19 | .nx/ 20 | # pack files 21 | mutation-*.tgz 22 | 23 | # Playwright 24 | test-results/ 25 | playwright-report/ 26 | blob-report/ 27 | playwright/.cache/ 28 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Handled via npm 2 | package-lock.json 3 | # Handled by lerna 4 | CHANGELOG.md 5 | 6 | # Other generated files 7 | dist 8 | dist-tsc 9 | src-generated 10 | .stryker-tmp 11 | reports 12 | target/ 13 | packages/metrics-scala/README.md 14 | .bsp/ 15 | 16 | # Large files that don't need prettier formatting 17 | report.json 18 | 19 | testResources 20 | 21 | /.nx/ 22 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 150, 3 | "singleQuote": true, 4 | "htmlWhitespaceSensitivity": "strict", 5 | "plugins": ["prettier-plugin-tailwindcss"], 6 | "tailwindStylesheet": "./packages/elements/src/style/tailwind.css" 7 | } 8 | -------------------------------------------------------------------------------- /.scala-steward.conf: -------------------------------------------------------------------------------- 1 | buildRoots = [ "packages/metrics-scala" ] 2 | # If set, Scala Steward will use this message template for the commit messages and PR titles. 3 | # Supported variables: ${artifactName}, ${currentVersion}, ${nextVersion} and ${default} 4 | # Default: "${default}" which is equivalent to "Update ${artifactName} to ${nextVersion}" 5 | commits.message = "build(deps-dev): update ${artifactName} from ${currentVersion} to ${nextVersion}" 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "streetsidesoftware.code-spell-checker", 8 | "esbenp.prettier-vscode", 9 | "dbaeumer.vscode-eslint", 10 | "bradlc.vscode-tailwindcss", 11 | "ms-playwright.playwright" 12 | ], 13 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 14 | "unwantedRecommendations": [] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Mocha unit tests", 11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 12 | "args": [ 13 | "--timeout", 14 | "999999", 15 | "--colors", 16 | "${workspaceFolder}/packages/report-schema/dist/test/**/*.js", 17 | "${workspaceFolder}/packages/metrics/dist/test/**/*.js", 18 | "${workspaceFolder}/packages/real-time/dist/test/**/*.js" 19 | ], 20 | "runtimeArgs": ["--enable-source-maps"], 21 | "internalConsoleOptions": "openOnSessionStart" 22 | } 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.tslint": "explicit" 4 | }, 5 | "files.watcherExclude": { 6 | "**/target": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "start:tsc", 9 | "label": "Run typescript compiler in watch mode", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | }, 14 | "dependsOn": "generate", 15 | "problemMatcher": "$tsc-watch" 16 | }, 17 | { 18 | "label": "generate", 19 | "type": "npm", 20 | "script": "generate", 21 | "isBackground": true, 22 | "group": "build" 23 | } 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /docs/equivalent-mutants.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Equivalent mutants 3 | custom_edit_url: https://github.com/stryker-mutator/mutation-testing-elements/edit/master/docs/equivalent-mutants.md 4 | --- 5 | 6 | Suppose you are writing a big chunk of code. You have decided to run both unit and mutation tests. 7 | Your score of unit tests is 100% and mutation 99%. You think "I'll make that 100%". After some work, you notice this part of your code: 8 | 9 | ```js 10 | var max = Math.max(a.comma, b.comma); 11 | var min = Math.min(a.comma, b.comma); 12 | if (a.comma >= b.comma) { 13 | a.number *= 10 ** (max - min); 14 | } else { 15 | b.number *= 10 ** (max - min); 16 | } 17 | ``` 18 | 19 | Let's analyse this example. 20 | 21 | Assume `a.comma` is greater than `b.comma`. For example 4 and 2 22 | 23 | We get that 24 | 25 | ```js 26 | max = 4; 27 | min = 2; 28 | if (4 >= 2) { 29 | a.number *= 10 ** 2; 30 | } else { 31 | b.number *= 10 ** 2; 32 | } 33 | ``` 34 | 35 | if we change `>=` sign in our source code to `<=` it won't work, so our mutant is killed. 36 | 37 | If we change 4 and 2 to 3 and 5, we will get the same output: mutant will be killed. 38 | But what if we have both values the same? Let's say: 3, 3 39 | 40 | ```js 41 | max = 3; 42 | min = 3; 43 | if (3 >= 3) { 44 | a.number *= 10 ** 0; 45 | } else { 46 | b.number *= 10 ** 0; 47 | } 48 | ``` 49 | 50 | Notice that 10 \*\* 0 = 1, so even if we change `>=` to `<=` and even `<` or `>` we will get the same output each time! 51 | 52 | It is called _equivalent mutant_. There is no definitive way for Stryker to find and ignore them. There is currently also no way yet to mark them to be ignored. 53 | 54 | For now, the only solution is by finding these by hand, which is time consuming and try to rewrite the code so it won't occur, or accept that you won't make 100%. 55 | 56 | First one has been shown above. The easiest schema for this mutant is: 57 | 58 | ```js 59 | if(whatever condition) { 60 | number1 += 0 // can be `-= 0` or `*= 1` `/= 1` 61 | } else { 62 | number2 += 0 // can be `-= 0` or `*= 1` `/= 1` 63 | } 64 | ``` 65 | 66 | The second one we have found is about `BigInt` 67 | since `-0n` is transformed to `0n`, doing 68 | 69 | ```js 70 | a = 0n; 71 | a = a <= 0n ? -a : a; 72 | ``` 73 | 74 | will produce another equivalent mutant. 75 | 76 | So knowing that for now, help us find more of them and don't be scared of not 100% mutation score! 77 | -------------------------------------------------------------------------------- /docs/img/static-mutant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/docs/img/static-mutant.png -------------------------------------------------------------------------------- /docs/sonarqube-integration.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: SonarQube integration 3 | custom_edit_url: https://github.com/stryker-mutator/mutation-testing-elements/edit/master/docs/sonarqube-integration.md 4 | --- 5 | 6 | A [jq](https://stedolan.github.io/jq/) filter can be downloaded from [mutation-report-to-sonar.jq](https://github.com/stryker-mutator/mutation-testing-elements/blob/master/integrations/mutation-report-to-sonar.jq) to convert a [JSON mutation testing report](https://github.com/stryker-mutator/mutation-testing-elements/tree/master/packages/report-schema) to the [SonarQube generic issue import format](https://docs.sonarqube.org/latest/analysis/generic-issue/). 7 | 8 | Usage: 9 | 10 | ``` 11 | jq -f mutation-report-to-sonar.jq mutation.json > mutation-sonar.json 12 | ``` 13 | 14 | After `mutation-sonar.json` is generated, you can import the file into SonarQube using the analysis parameter `sonar.externalIssuesReportPaths`. The mutation testing results will be imported as [external issues](https://docs.sonarqube.org/latest/analysis/generic-issue/). There are a couple of limitations with importing external issues: 15 | 16 | - You can't manage them within SonarQube; for instance, there is no ability to mark them False Positive. 17 | - You can't manage the activation of the rules that raise these issues within SonarQube. External rules aren't visible on the Rules page or reflected in Quality Profiles. 18 | 19 | External issues and the rules that raise them must be managed in the configuration of Stryker. 20 | 21 | When importing mutation testing results using this method: 22 | 23 | - Only mutants with a state of **Survived** or **No coverage** are imported. 24 | - Mutants are imported as [issues](https://docs.sonarqube.org/latest/user-guide/issues/) of type **Code Smell** with a severity of **MAJOR** and an effort of 10 minutes. 25 | - If the mutation testing report includes the `projectRoot` property, absolute file paths are converted to relative file paths to avoid issues with the [SonarScanner](https://docs.sonarqube.org/latest/analysis/scan/sonarscanner/) locating the source files. 26 | -------------------------------------------------------------------------------- /integrations/mutation-report-to-sonar.jq: -------------------------------------------------------------------------------- 1 | # This jq filter transforms a mutation testing report to the SonarQube generic issue import format. 2 | 3 | .framework.name as $frameworkName 4 | | .projectRoot as $projectRoot 5 | | .files 6 | | to_entries 7 | | { 8 | issues: map( 9 | .value.mutants[] as $mutants 10 | | del(.value) as $file 11 | | $mutants 12 | | select(.status == ("Survived", "NoCoverage")) 13 | | ( 14 | if .replacement then 15 | "The " + .mutatorName + " was mutated to " + .replacement + " without any tests failing." 16 | else 17 | "The " + .mutatorName + " was mutated without any tests failing." 18 | end 19 | ) as $mutation 20 | | { 21 | engineId: ($frameworkName // "Mutation Testing"), 22 | ruleId: ("Mutant" + .status), 23 | primaryLocation: { 24 | message: ( 25 | if .status == "NoCoverage" then 26 | "A mutant was not covered by any of the tests. " + $mutation 27 | else 28 | "A mutant survived after running the tests. " + $mutation 29 | end 30 | ), 31 | filePath: ( 32 | if $projectRoot then 33 | $file.key | sub("^" + $projectRoot + "/"; "") 34 | else 35 | $file.key 36 | end 37 | ), 38 | textRange: { 39 | startLine: .location.start.line, 40 | endLine: .location.end.line, 41 | startColumn: (.location.start.column - 1), 42 | endColumn: (.location.end.column - 1) 43 | } 44 | }, 45 | type: "CODE_SMELL", 46 | severity: "MAJOR", 47 | effortMinutes: 10 48 | } 49 | ) 50 | } 51 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "node_modules/lerna/schemas/lerna-schema.json", 3 | "packages": ["packages/*"], 4 | "version": "3.5.4", 5 | "command": { 6 | "version": { 7 | "allowBranch": "master", 8 | "conventionalCommits": true, 9 | "createRelease": "github", 10 | "exact": true 11 | }, 12 | "publish": { 13 | "preDistTag": "beta", 14 | "concurrency": 1 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /libs/eslint-plugin-mte/eslint.config.js: -------------------------------------------------------------------------------- 1 | import eslint from '@eslint/js'; 2 | 3 | export default [eslint.configs.recommended]; 4 | -------------------------------------------------------------------------------- /libs/eslint-plugin-mte/index.d.ts: -------------------------------------------------------------------------------- 1 | import type { config } from 'typescript-eslint'; 2 | 3 | declare const configs: ReturnType; 4 | export default configs; 5 | -------------------------------------------------------------------------------- /libs/eslint-plugin-mte/index.js: -------------------------------------------------------------------------------- 1 | import eslint from '@eslint/js'; 2 | import eslintConfigPrettier from 'eslint-config-prettier'; 3 | import importX from 'eslint-plugin-import-x'; 4 | import simpleImportSort from 'eslint-plugin-simple-import-sort'; 5 | import globals from 'globals'; 6 | import tseslint from 'typescript-eslint'; 7 | 8 | export default tseslint.config( 9 | eslint.configs.recommended, 10 | tseslint.configs.eslintRecommended, 11 | ...tseslint.configs.recommendedTypeChecked, 12 | ...tseslint.configs.stylisticTypeChecked, 13 | eslintConfigPrettier, 14 | importX.flatConfigs.recommended, 15 | importX.flatConfigs.typescript, 16 | { 17 | languageOptions: { 18 | parserOptions: { 19 | project: ['./tsconfig.json', './{src,test}/tsconfig.json', './test/{unit,integration}/tsconfig.json', '../../tsconfig.node.json'], 20 | }, 21 | }, 22 | plugins: { 23 | 'simple-import-sort': simpleImportSort, 24 | }, 25 | linterOptions: { 26 | reportUnusedDisableDirectives: 'error', 27 | }, 28 | rules: { 29 | '@typescript-eslint/consistent-type-imports': 'error', 30 | '@typescript-eslint/explicit-function-return-type': 'off', 31 | '@typescript-eslint/explicit-module-boundary-types': 'off', 32 | '@typescript-eslint/no-unsafe-assignment': 'off', 33 | 34 | 'import-x/newline-after-import': 'error', 35 | 'import-x/no-deprecated': 'error', 36 | 37 | 'simple-import-sort/exports': 'error', 38 | 'simple-import-sort/imports': 'error', 39 | 40 | eqeqeq: 'error', 41 | }, 42 | }, 43 | { 44 | // Test-specific rules 45 | files: ['test/**/*.ts'], 46 | rules: { 47 | '@typescript-eslint/no-unused-expressions': 'off', 48 | }, 49 | }, 50 | { 51 | files: ['*.js', '*.config.{js,ts}'], 52 | ...tseslint.configs.disableTypeChecked, 53 | }, 54 | { 55 | files: ['*.js', '*.config.{js,ts}'], 56 | languageOptions: { 57 | globals: { 58 | ...globals.node, 59 | }, 60 | }, 61 | }, 62 | { 63 | ignores: [ 64 | 'trace/', 65 | 'tsconfig-transpiler.js', 66 | 'node_modules', 67 | 'dist', 68 | 'dist-tsc', 69 | 'src-generated', 70 | '.stryker-tmp', 71 | 'reports', 72 | 'testResources', 73 | 'target', 74 | 'playwright-report', 75 | ], 76 | }, 77 | ); 78 | -------------------------------------------------------------------------------- /libs/eslint-plugin-mte/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-mte", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Shared ESLint config", 6 | "main": "index.js", 7 | "type": "module", 8 | "repository": { 9 | "type": "git", 10 | "url": "git+https://github.com/stryker-mutator/mutation-testing-elements.git" 11 | }, 12 | "license": "Apache-2.0", 13 | "bugs": { 14 | "url": "https://github.com/stryker-mutator/mutation-testing-elements/issues" 15 | }, 16 | "homepage": "https://github.com/stryker-mutator/mutation-testing-elements#readme", 17 | "scripts": { 18 | "lint": "eslint ." 19 | }, 20 | "author": "", 21 | "dependencies": { 22 | "@eslint/js": "^9.0.0", 23 | "eslint": "^9.0.0", 24 | "eslint-config-prettier": "~10.1.0", 25 | "eslint-import-resolver-typescript": "^4.0.0", 26 | "eslint-plugin-import-x": "^4.4.3", 27 | "eslint-plugin-lit": "^2.0.0", 28 | "eslint-plugin-simple-import-sort": "^12.1.1", 29 | "eslint-plugin-wc": "^3.0.0", 30 | "typescript-eslint": "^8.23.0" 31 | }, 32 | "devDependencies": {} 33 | } 34 | -------------------------------------------------------------------------------- /nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://raw.githubusercontent.com/nrwl/nx/master/packages/nx/schemas/nx-schema.json", 3 | "extends": "nx/presets/npm.json", 4 | "namedInputs": { 5 | "default": ["{projectRoot}/**/*", "{workspaceRoot}/package-lock.json", { "runtime": "node -v" }], 6 | "production": ["!{projectRoot}/test/**/*", "!{projectRoot}/.eslintrc", "!{projectRoot}/stryker.conf.js"] 7 | }, 8 | "targetDefaults": { 9 | "build:tsc": { 10 | "inputs": ["default", "{projectRoot}/**/*.ts", "{workspaceRoot}/**/*.ts"], 11 | "dependsOn": ["report-schema:generate"], 12 | "outputs": ["{projectRoot}/packages/*/dist", "{projectRoot}/packages/*/dist-tsc"], 13 | "cache": true 14 | }, 15 | "build": { 16 | "inputs": ["production", "^production"], 17 | "dependsOn": ["^build", "root:build:tsc"], 18 | "outputs": ["{projectRoot}/dist", "{projectRoot}/target"], 19 | "cache": true 20 | }, 21 | "generate": { 22 | "inputs": ["{projectRoot}/src/*.json", "{workspaceRoot}/tasks/schema2ts.mjs", { "externalDependencies": ["json-schema-to-typescript"] }], 23 | "outputs": ["{projectRoot}/src-generated"], 24 | "cache": true 25 | }, 26 | "test": { 27 | "inputs": ["default", "^production"], 28 | "dependsOn": ["root:build:tsc"], 29 | "cache": true 30 | }, 31 | "stryker": { 32 | "inputs": ["default", "^production", { "env": "GITHUB_REF" }], 33 | "dependsOn": ["generate", "root:build:tsc", "^build"], 34 | "outputs": ["{projectRoot}/reports", "{projectRoot}/target/stryker4s-report-*/"], 35 | "cache": true 36 | }, 37 | "lint": { 38 | "dependsOn": ["root:build:tsc"], 39 | "cache": true 40 | }, 41 | "lint:prettier": { 42 | "cache": true 43 | }, 44 | "start": { 45 | "dependsOn": ["root:build:tsc"] 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "private": true, 4 | "version": "0.0.6", 5 | "packageManager": "npm@11.4.1", 6 | "description": "A suite of web components for a mutation testing report.", 7 | "workspaces": [ 8 | "packages/*", 9 | "libs/*" 10 | ], 11 | "nx": {}, 12 | "scripts": { 13 | "clean": "rimraf --glob packages/*/{dist,dist-tsc,src-generated}", 14 | "all": "nx affected --target=lint:prettier,lint,build,test", 15 | "build": "nx run-many --target=build --exclude=root", 16 | "lint:prettier": "prettier --check .", 17 | "lint:fix": "prettier --write . && nx run-many --target=lint --fix", 18 | "build:tsc": "tsc --build", 19 | "start": "nx start elements", 20 | "start:tsc": "tsc --build --watch", 21 | "download-incremental-reports": "bash tasks/download-incremental-reports.sh", 22 | "lerna:version:patch": "lerna version patch", 23 | "lerna:version:minor": "lerna version minor", 24 | "lerna:version:major": "lerna version major", 25 | "lerna:publish": "lerna publish from-git --yes --no-verify-access", 26 | "generate": "nx run report-schema:generate" 27 | }, 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/stryker-mutator/mutation-testing-elements.git" 31 | }, 32 | "license": "Apache-2.0", 33 | "bugs": { 34 | "url": "https://github.com/stryker-mutator/mutation-testing-elements/issues" 35 | }, 36 | "homepage": "https://github.com/stryker-mutator/mutation-testing-elements#readme", 37 | "devDependencies": { 38 | "@nx/powerpack-license": "2.1.0", 39 | "@nx/powerpack-shared-fs-cache": "2.1.0", 40 | "@stryker-mutator/core": "9.0.1", 41 | "@stryker-mutator/mocha-runner": "9.0.1", 42 | "@stryker-mutator/typescript-checker": "9.0.1", 43 | "@stryker-mutator/vitest-runner": "9.0.1", 44 | "@types/chai": "5.2.2", 45 | "@types/mocha": "10.0.10", 46 | "@types/node": "22.15.30", 47 | "@types/sinon": "17.0.4", 48 | "chai": "5.2.0", 49 | "json-schema-to-typescript": "15.0.4", 50 | "lerna": "8.2.2", 51 | "mocha": "11.5.0", 52 | "nx": "21.1.3", 53 | "prettier": "3.5.3", 54 | "prettier-plugin-tailwindcss": "0.6.12", 55 | "rimraf": "6.0.1", 56 | "sinon": "20.0.0", 57 | "typescript": "5.8.3" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/elements/.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults and > 0.2% 2 | -------------------------------------------------------------------------------- /packages/elements/.bundlemonrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultCompression": "none", 3 | "subProject": "elements", 4 | "baseDir": "./dist", 5 | "files": [{ "path": "*.js" }, { "path": "*.cjs" }], 6 | "reportOutput": ["github"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/elements/.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !README.md 3 | !dist/**/* 4 | !dist-tsc/src/**/* 5 | tsconfig.tsbuildinfo 6 | -------------------------------------------------------------------------------- /packages/elements/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 150, 3 | "singleQuote": true, 4 | "htmlWhitespaceSensitivity": "strict", 5 | "plugins": ["prettier-plugin-tailwindcss"], 6 | "tailwindStylesheet": "./src/style/tailwind.css" 7 | } 8 | -------------------------------------------------------------------------------- /packages/elements/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Attach to Chrome", 9 | "port": 9222, 10 | "request": "attach", 11 | "type": "chrome", 12 | "webRoot": "${workspaceFolder}" 13 | }, 14 | { 15 | "type": "chrome", 16 | "request": "launch", 17 | "name": "Launch Chrome", 18 | "url": "http://localhost:5173", 19 | "webRoot": "${workspaceFolder}", 20 | "sourceMaps": true 21 | }, 22 | { 23 | "type": "node", 24 | "request": "launch", 25 | "name": "Run unit tests", 26 | "program": "${workspaceFolder:parent}/node_modules/vitest/vitest.mjs", 27 | "console": "integratedTerminal", 28 | "skipFiles": ["/**"], 29 | "args": ["--inspect-brk", "--browser", "--no-file-parallelism"] 30 | }, 31 | { 32 | "type": "chrome", 33 | "request": "attach", 34 | "name": "Attach to unit test browser", 35 | "skipFiles": ["/**"], 36 | "port": 9229 37 | } 38 | ], 39 | "compounds": [ 40 | { 41 | "name": "Debug unit tests", 42 | "configurations": ["Attach to unit test browser", "Run unit tests"], 43 | "stopAll": true 44 | } 45 | ] 46 | } 47 | -------------------------------------------------------------------------------- /packages/elements/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "type": "npm", 8 | "script": "start", 9 | "label": "Run vite in watch mode", 10 | "group": { 11 | "kind": "build", 12 | "isDefault": true 13 | }, 14 | "problemMatcher": [] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /packages/elements/docs/directory-result-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/docs/directory-result-example.png -------------------------------------------------------------------------------- /packages/elements/docs/file-result-example-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/docs/file-result-example-dark.png -------------------------------------------------------------------------------- /packages/elements/docs/file-result-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/docs/file-result-example.png -------------------------------------------------------------------------------- /packages/elements/eslint.config.js: -------------------------------------------------------------------------------- 1 | import mte from 'eslint-config-mte'; 2 | import { configs as litConfigs } from 'eslint-plugin-lit'; 3 | import { configs as wcConfigs } from 'eslint-plugin-wc'; 4 | import { config } from 'typescript-eslint'; 5 | 6 | export default config( 7 | wcConfigs['flat/best-practice'], 8 | litConfigs['flat/recommended'], 9 | { 10 | settings: { 11 | elementBaseClasses: ['LitElement', 'RealTimeElement', 'BaseElement'], 12 | }, 13 | rules: { 14 | 'wc/guard-super-call': 'off', 15 | 'wc/no-method-prefixed-with-on': 'error', 16 | 'wc/require-listener-teardown': 'off', 17 | 18 | 'lit/attribute-names': ['error', { convention: 'kebab' }], 19 | 'lit/lifecycle-super': 'error', 20 | 'lit/no-legacy-imports': 'error', 21 | 'lit/no-this-assign-in-render': 'error', 22 | 'lit/no-useless-template-literals': 'error', 23 | 'lit/no-value-attribute': 'error', 24 | 'lit/prefer-nothing': 'error', 25 | }, 26 | }, 27 | ...mte, 28 | ); 29 | -------------------------------------------------------------------------------- /packages/elements/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packages/elements/src/components/base-element.ts: -------------------------------------------------------------------------------- 1 | import { LitElement } from 'lit'; 2 | 3 | import { tailwind } from '../style/index.js'; 4 | 5 | export abstract class BaseElement extends LitElement { 6 | public static styles = [tailwind]; 7 | } 8 | -------------------------------------------------------------------------------- /packages/elements/src/components/drawer-mutant/util.ts: -------------------------------------------------------------------------------- 1 | import type { TemplateResult } from 'lit'; 2 | import { html, nothing } from 'lit'; 3 | 4 | export const renderDetailLine = (title: string, content: string | TemplateResult) => 5 | html`
  • ${content}
  • `; 6 | 7 | // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- we want to coalesce on empty string 8 | export const renderSummaryLine = (content: string | TemplateResult, title?: string) => html`

    ${content}

    `; 9 | 10 | export const renderSummaryContainer = (content: TemplateResult) => html`
    ${content}
    `; 11 | 12 | /** 13 | * Wrap the given emoji in an accessible-friendly span 14 | */ 15 | export const renderEmoji = (emoji: string, label: string) => html`${emoji}`; 16 | -------------------------------------------------------------------------------- /packages/elements/src/components/drawer/drawer.component.css: -------------------------------------------------------------------------------- 1 | :host([mode='closed']) { 2 | height: 0; 3 | } 4 | :host([mode='half']) { 5 | height: var(--spacing-drawer-half-open); 6 | } 7 | :host([mode='open']) { 8 | height: 50%; 9 | } 10 | -------------------------------------------------------------------------------- /packages/elements/src/components/drawer/util.ts: -------------------------------------------------------------------------------- 1 | import type { nothing, TemplateResult } from 'lit'; 2 | import { html } from 'lit'; 3 | 4 | import type { DrawerMode } from './drawer.component.js'; 5 | 6 | export const renderDrawer = ({ hasDetail, mode }: { hasDetail: boolean; mode: DrawerMode }, content: TemplateResult | typeof nothing) => 7 | html` 12 | ${content} 13 | `; 14 | -------------------------------------------------------------------------------- /packages/elements/src/components/file-icon/file-icon.css: -------------------------------------------------------------------------------- 1 | svg.cs { 2 | fill: var(--mut-file-csharp-color); 3 | } 4 | svg.html { 5 | fill: var(--mut-file-html-color); 6 | } 7 | svg.java { 8 | fill: var(--mut-file-java-color); 9 | } 10 | svg.javascript { 11 | fill: var(--mut-file-js-color); 12 | } 13 | svg.scala { 14 | fill: var(--mut-file-scala-color); 15 | } 16 | svg.typescript { 17 | fill: var(--mut-file-ts-color); 18 | } 19 | svg.php { 20 | fill: var(--mut-file-php-color); 21 | } 22 | svg.vue { 23 | fill: var(--mut-file-vue-color); 24 | } 25 | svg.octicon { 26 | fill: var(--mut-octicon-icon-color); 27 | } 28 | 29 | svg.javascript.test { 30 | fill: var(--mut-file-js-test-color); 31 | } 32 | svg.typescript.test { 33 | fill: var(--mut-file-js-test-color); 34 | } 35 | 36 | svg.gherkin { 37 | fill: var(--mut-file-gherkin-color); 38 | } 39 | 40 | svg.svelte { 41 | fill: var(--mut-file-svelte-color); 42 | } 43 | 44 | svg.rust { 45 | fill: var(--mut-file-rust-color); 46 | } 47 | 48 | svg { 49 | width: 20px; 50 | vertical-align: middle; 51 | } 52 | -------------------------------------------------------------------------------- /packages/elements/src/components/file/util.ts: -------------------------------------------------------------------------------- 1 | import type { TemplateResult } from 'lit'; 2 | import { html, nothing, svg } from 'lit'; 3 | import { unsafeHTML } from 'lit/directives/unsafe-html.js'; 4 | 5 | export function renderDots(dots: typeof nothing | TemplateResult[], finalDots: typeof nothing | TemplateResult[]) { 6 | if (dots === nothing && finalDots === nothing) { 7 | return nothing; 8 | } else { 9 | return html`${dots}${finalDots}`; 10 | } 11 | } 12 | 13 | export function renderLine(line: string, dots: TemplateResult | typeof nothing) { 14 | return html`${unsafeHTML(line)}${dots}`; 17 | } 18 | 19 | // To edit, I recommend opening the SVG in a tool like Inkscape 20 | const circleSvgPath = 'M 0,5 C 0,-1.66 10,-1.66 10,5 10,7.76 7.76,10 5,10 2.24,10 0,7.76 0,5 Z'; 21 | // Triangle with curve paths of 0, so it has the same number of path elements as the circle, which is needed for animation 22 | const triangleSvgPath = 'M 0,0 C 0,0 10,0 10,0 10,0 5,10 5,10 5,10 0,0 0,0 Z'; 23 | // Fancy cubic bezier curve for the animation. Same as `transition-*` in tailwind 24 | const animationCurve = '0.4 0 0.2 1'; 25 | 26 | const pathF = (from: string, to: string, strokeOpacity: number) => 27 | svg` 28 | 29 | `; 30 | 31 | export const triangle = pathF(circleSvgPath, triangleSvgPath, 1); 32 | export const circle = pathF(triangleSvgPath, circleSvgPath, 0); 33 | 34 | /** 35 | * Animate a svg element that has a path.animate child 36 | */ 37 | export function beginElementAnimation(root: ParentNode | undefined, prop: string, value: string) { 38 | const el = root?.querySelector(`[${prop}="${encodeURIComponent(value)}"] path animate`); 39 | el?.beginElement(); 40 | } 41 | -------------------------------------------------------------------------------- /packages/elements/src/components/real-time-element.ts: -------------------------------------------------------------------------------- 1 | import { Subscription } from 'rxjs'; 2 | 3 | import { mutantChanges } from '../lib/mutant-changes.js'; 4 | import { BaseElement } from './base-element.js'; 5 | 6 | export abstract class RealTimeElement extends BaseElement { 7 | public shouldReactivate(): boolean { 8 | return true; 9 | } 10 | 11 | public reactivate() { 12 | this.requestUpdate(); 13 | } 14 | 15 | #subscription = new Subscription(); 16 | override connectedCallback(): void { 17 | super.connectedCallback(); 18 | this.#subscription.add(mutantChanges.subscribe(() => this.shouldReactivate() && this.reactivate())); 19 | } 20 | 21 | override disconnectedCallback(): void { 22 | super.disconnectedCallback(); 23 | this.#subscription.unsubscribe(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/elements/src/components/test-file/test-file.css: -------------------------------------------------------------------------------- 1 | @import '../../style/code.css'; 2 | 3 | .Killing { 4 | --mut-test-dot-color: var(--color-green-700); 5 | } 6 | .Covering { 7 | --mut-test-dot-color: var(--color-amber-400); 8 | } 9 | .NotCovering { 10 | --mut-test-dot-color: var(--color-orange-500); 11 | } 12 | svg.test-dot { 13 | fill: var(--mut-test-dot-color); 14 | } 15 | -------------------------------------------------------------------------------- /packages/elements/src/components/theme-switch/theme-switch.component.ts: -------------------------------------------------------------------------------- 1 | import { html, unsafeCSS } from 'lit'; 2 | import { customElement, property } from 'lit/decorators.js'; 3 | 4 | import { createCustomEvent } from '../../lib/custom-events.js'; 5 | import { tailwind } from '../../style/index.js'; 6 | import { BaseElement } from '../base-element.js'; 7 | import style from './theme-switch.css?inline'; 8 | 9 | @customElement('mte-theme-switch') 10 | export class MutationTestReportThemeSwitchComponent extends BaseElement { 11 | @property() 12 | declare public theme: string | undefined; 13 | 14 | private readonly dispatchThemeChangedEvent = (e: MouseEvent) => { 15 | const checked = (e.target as HTMLInputElement).checked; 16 | this.dispatchEvent(createCustomEvent('theme-switch', checked ? 'dark' : 'light')); 17 | }; 18 | 19 | public static override styles = [tailwind, unsafeCSS(style)]; 20 | 21 | public render() { 22 | return html` 23 |
    24 | 25 | 26 |
    27 | `; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/elements/src/components/tooltip/tooltip.component.ts: -------------------------------------------------------------------------------- 1 | import { html } from 'lit'; 2 | import { customElement, property } from 'lit/decorators.js'; 3 | 4 | import { BaseElement } from '../base-element.js'; 5 | 6 | @customElement('mte-tooltip') 7 | export class MutationTestReportThemeSwitchComponent extends BaseElement { 8 | @property({ attribute: true }) 9 | declare title: string; 10 | 11 | render() { 12 | return html``; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/elements/src/declaration.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.scss?inline' { 2 | declare const style: string; 3 | export default style; 4 | } 5 | 6 | declare module '*.css?inline' { 7 | declare const style: string; 8 | export default style; 9 | } 10 | -------------------------------------------------------------------------------- /packages/elements/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components/app/app.component.js'; 2 | import './components/file/file.component.js'; 3 | import './components/file-picker/file-picker.component.js'; 4 | import './components/breadcrumb.js'; 5 | import './components/state-filter/state-filter.component.js'; 6 | import './components/theme-switch/theme-switch.component.js'; 7 | import './components/drawer/drawer.component.js'; 8 | import './components/drawer-mutant/drawer-mutant.component.js'; 9 | import './components/mutant-view/mutant-view.js'; 10 | import './components/test-view/test-view.js'; 11 | import './components/metrics-table/metrics-table.component.js'; 12 | import './components/test-file/test-file.component.js'; 13 | import './components/drawer-test/drawer-test.component.js'; 14 | import './components/file-icon/file-icon.component.js'; 15 | import './components/tooltip/tooltip.component.js'; 16 | import './components/result-status-bar/result-status-bar.js'; 17 | 18 | import type { MteCustomEvent } from './lib/custom-events.js'; 19 | 20 | export type ThemeChangedEvent = MteCustomEvent<'theme-changed'>; 21 | -------------------------------------------------------------------------------- /packages/elements/src/lib/browser.ts: -------------------------------------------------------------------------------- 1 | import { isServer } from 'lit'; 2 | 3 | /** 4 | * Test if localStorage exists and is enabled 5 | */ 6 | export function isLocalStorageAvailable() { 7 | if (isServer) return false; 8 | const test = 'test'; 9 | try { 10 | localStorage.setItem(test, test); 11 | localStorage.removeItem(test); 12 | return true; 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | } catch (e) { 15 | return false; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/elements/src/lib/custom-events.ts: -------------------------------------------------------------------------------- 1 | import type { MutantModel, TestModel } from 'mutation-testing-metrics'; 2 | 3 | import type { Theme } from './theme.js'; 4 | 5 | export interface CustomEventMap { 6 | 'mte-file-picker-open': void; 7 | 'mutant-selected': { selected: boolean; mutant: MutantModel | undefined }; 8 | 'test-selected': { selected: boolean; test: TestModel | undefined }; 9 | 'theme-changed': { theme: Theme; themeBackgroundColor: string }; 10 | 'theme-switch': Theme; 11 | 'filters-changed': string[]; 12 | next: void; 13 | previous: void; 14 | } 15 | 16 | export function createCustomEvent(eventName: T, detail: CustomEventMap[T], opts?: Omit) { 17 | return new CustomEvent(eventName, { detail, ...opts }); 18 | } 19 | 20 | export type MteCustomEvent = CustomEvent; 21 | -------------------------------------------------------------------------------- /packages/elements/src/lib/mutant-changes.ts: -------------------------------------------------------------------------------- 1 | import { Subject } from 'rxjs'; 2 | 3 | export const mutantChanges = new Subject(); 4 | -------------------------------------------------------------------------------- /packages/elements/src/lib/router.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import-x/no-deprecated */ 2 | import { isServer } from 'lit'; 3 | import { EMPTY, fromEvent, merge, of } from 'rxjs'; 4 | import { map, tap } from 'rxjs/operators'; 5 | 6 | /** 7 | * Observable for location changes on the hash part of the url 8 | * As soon as you subscribe you'll get a first event for the current location 9 | * @example 10 | * window.location.url === 'http://localhost:8080#foo/bar/baz.js' => ['foo', 'bar', 'baz.js '] 11 | */ 12 | export const locationChange$ = isServer 13 | ? EMPTY 14 | : merge(of(1), fromEvent(window, 'hashchange').pipe(tap((event) => event.preventDefault()))).pipe( 15 | map(() => window.location.hash.substr(1).split('/').filter(Boolean).map(decodeURIComponent)), 16 | ); 17 | 18 | export enum View { 19 | mutant = 'mutant', 20 | test = 'test', 21 | } 22 | -------------------------------------------------------------------------------- /packages/elements/src/lib/svg-icons.ts: -------------------------------------------------------------------------------- 1 | import { svg } from 'lit'; 2 | 3 | export const mutantFileIcon = svg` 4 | 5 | 6 | 7 | `; 8 | 9 | export const searchIcon = svg` 10 | 11 | 16 | 17 | `; 18 | 19 | export const testFileIcon = svg` 20 | 21 | 22 | 23 | `; 24 | 25 | const arrow = ( 26 | classes?: string, 27 | ) => svg``; 34 | 35 | export const arrowLeft = arrow('rotate-180'); 36 | export const arrowRight = arrow(); 37 | -------------------------------------------------------------------------------- /packages/elements/src/lib/theme.ts: -------------------------------------------------------------------------------- 1 | export type Theme = 'light' | 'dark'; 2 | -------------------------------------------------------------------------------- /packages/elements/src/style/code.css: -------------------------------------------------------------------------------- 1 | #report-code-block { 2 | /* Don't show scrollbar in code block, the page already scrolls */ 3 | /* Also let the popup appear outside the code block boundaries */ 4 | overflow-y: visible; 5 | overflow-x: auto; 6 | background: var(--prism-background); 7 | border: 1px solid var(--prism-border); 8 | } 9 | 10 | .line-numbers { 11 | counter-reset: mte-line-number; 12 | } 13 | 14 | .line .line-number { 15 | text-align: right; 16 | padding: 0px 10px 0px 15px; 17 | color: var(--mut-line-number); 18 | counter-increment: mte-line-number; 19 | 20 | &:before { 21 | content: counter(mte-line-number); 22 | } 23 | } 24 | 25 | .line-marker::before { 26 | content: ' '; 27 | padding: 0px 5px; 28 | } 29 | -------------------------------------------------------------------------------- /packages/elements/src/style/index.ts: -------------------------------------------------------------------------------- 1 | import './prism-plugins'; 2 | 3 | import { unsafeCSS } from 'lit'; 4 | 5 | import prismjsCss from './prismjs.css?inline'; 6 | import tailwindCss from './tailwind.css?inline'; 7 | 8 | export const tailwind = unsafeCSS(tailwindCss); 9 | export const prismjs = unsafeCSS(prismjsCss); 10 | 11 | // https://github.com/tailwindlabs/tailwindcss/issues/15005 12 | // Set all @property values from tailwind on the document 13 | // And only do this once (check if the there is already a stylesheet with the same content) 14 | if ( 15 | tailwind.styleSheet && 16 | document?.adoptedStyleSheets && 17 | !document.adoptedStyleSheets.some((sheet) => sheet.cssRules[0]?.cssText === tailwind.styleSheet!.cssRules[0].cssText) 18 | ) { 19 | const propertiesSheet = new CSSStyleSheet(); 20 | let code = tailwind.cssText; 21 | 22 | code = code 23 | // Change custom properties to inherit 24 | .replaceAll('inherits: false', 'inherits: true') 25 | // Remove everything before the property declarations 26 | .substring(code.indexOf('@property')); 27 | 28 | propertiesSheet.replaceSync(code); 29 | 30 | document.adoptedStyleSheets.push(propertiesSheet); 31 | } 32 | -------------------------------------------------------------------------------- /packages/elements/src/style/prism-plugins.ts: -------------------------------------------------------------------------------- 1 | import 'prismjs/components/prism-core'; 2 | // Order is important here! Scala depends on java, which depends on clike 3 | import 'prismjs/components/prism-clike'; 4 | import 'prismjs/components/prism-javascript'; 5 | import 'prismjs/components/prism-typescript'; 6 | import 'prismjs/components/prism-csharp'; 7 | import 'prismjs/components/prism-java'; 8 | import 'prismjs/components/prism-scala'; 9 | import 'prismjs/components/prism-gherkin'; 10 | import 'prismjs/components/prism-rust'; 11 | // Markup and markup-templating are needed for php 12 | import 'prismjs/components/prism-markup'; 13 | import 'prismjs/components/prism-markup-templating'; 14 | import 'prismjs/components/prism-php'; 15 | // Svelte 16 | import 'prism-svelte'; 17 | // Don't strip pre-existing HTML to keep the popups and badges working 18 | import 'prismjs/plugins/keep-markup/prism-keep-markup'; 19 | // Removed auto-loader plugin because of https://github.com/stryker-mutator/mutation-testing-elements/issues/393 20 | -------------------------------------------------------------------------------- /packages/elements/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.elements.settings.json", 3 | "compilerOptions": { 4 | "types": ["vite/client"], 5 | "outDir": "../dist-tsc/src", 6 | "emitDeclarationOnly": true 7 | }, 8 | "references": [ 9 | { 10 | "path": "../../report-schema/src" 11 | }, 12 | { 13 | "path": "../../metrics/src" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/elements/src/typings/prism.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'prismjs/components/prism-core' { 2 | // Core has same exports as root prismjs export 3 | export * from 'prismjs'; 4 | } 5 | -------------------------------------------------------------------------------- /packages/elements/stryker.conf.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | 3 | /** 4 | * @type {import('@stryker-mutator/api/core').PartialStrykerOptions & typeof import('../../stryker.parent.json')} 5 | */ 6 | const config = JSON.parse(await fs.readFile('../../stryker.parent.json', 'utf-8')); 7 | 8 | // @ts-expect-error removing gives error from parent.json type 9 | delete config.buildCommand; 10 | // @ts-expect-error removing gives error from parent.json type 11 | delete config.mochaOptions; 12 | 13 | config.dashboard = { module: 'elements' }; 14 | config.testRunner = 'vitest'; 15 | 16 | export default config; 17 | -------------------------------------------------------------------------------- /packages/elements/test/integration/directoryReport.it.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | import { ReportPage } from './po/ReportPage.js'; 4 | 5 | test.describe('Directory report page', () => { 6 | let page: ReportPage; 7 | test.beforeEach(async ({ page: p }) => { 8 | page = new ReportPage(p); 9 | await page.navigateTo('install-local-example/'); 10 | }); 11 | 12 | test.describe('the results table', () => { 13 | test('should show 11 rows in the result table', async () => { 14 | expect(await page.mutantView.resultTable().rows()).toHaveLength(11); 15 | }); 16 | 17 | test('should show "all files" with "78.57" mutation score', async () => { 18 | const row = page.mutantView.resultTable().row('All files'); 19 | await row.progressBar().expectPercentage('78.57'); 20 | await expect(row.mutationScore()).toHaveText('78.57'); 21 | }); 22 | 23 | test('should show expected totals for cli.ts', async () => { 24 | const row = page.mutantView.resultTable().row('cli.ts'); 25 | await Promise.all([ 26 | row.progressBar().expectPercentage('8.70'), 27 | expect(row.mutationScore()).toHaveText('8.70'), 28 | row.testStrengthProgressBar().expectPercentage('66.67'), 29 | expect(row.mutationScoreBasedOnCoveredCode()).toHaveText('66.67'), 30 | expect(row.killed()).toHaveText('2'), 31 | expect(row.survived()).toHaveText('1'), 32 | expect(row.timeout()).toHaveText('0'), 33 | expect(row.noCoverage()).toHaveText('20'), 34 | expect(row.ignored()).toHaveText('0'), 35 | expect(row.runtimeErrors()).toHaveText('0'), 36 | expect(row.compileErrors()).toHaveText('3'), 37 | expect(row.totalDetected()).toHaveText('2'), 38 | expect(row.totalUndetected()).toHaveText('21'), 39 | expect(row.totalMutants()).toHaveText('26'), 40 | ]); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-look-as-expected-in-dark-mode-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-should-show-the-statusReason-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-whe-a1d5a--toggled-should-look-as-expected-in-dark-mode-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-mutant.it.spec.ts-snapshots/Drawer-mutant-view-when-a-mutant-is-opened-when-read-more-is-toggled-should-look-as-expected-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-in-dark-mode-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-in-dark-mode-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-in-dark-mode-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-in-dark-mode-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-in-dark-mode-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-in-dark-mode-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-in-dark-mode-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-should-look-as-expected-in-dark-mode-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/drawer-test.it.spec.ts-snapshots/Drawer-test-view-when-read-more-is-toggled-should-look-as-expected-in-dark-mode-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/file-picker.it.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | import { ReportPage } from './po/ReportPage.js'; 4 | 5 | test.describe('FilePicker', () => { 6 | let page: ReportPage; 7 | 8 | test.beforeEach(async ({ page: p }) => { 9 | page = new ReportPage(p); 10 | await page.navigateTo('test-files-example/#mutant/deep-merge.ts'); 11 | }); 12 | 13 | test('clicking search should open the file picker', async () => { 14 | const filePicker = await page.breadcrumb().openFilePicker(); 15 | await expect(filePicker.picker()).toBeVisible(); 16 | }); 17 | 18 | test('pressing enter should open the file and close the file picker', async ({ page: p }) => { 19 | const filePicker = await page.breadcrumb().openFilePicker(); 20 | 21 | await p.keyboard.press('Enter'); 22 | await page.mutantView.waitForVisible(); 23 | await expect(filePicker.picker()).not.toBeVisible(); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/elements/test/integration/fileReport.it.spec.ts-snapshots/File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/fileReport.it.spec.ts-snapshots/File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/fileReport.it.spec.ts-snapshots/File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/fileReport.it.spec.ts-snapshots/File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/fileReport.it.spec.ts-snapshots/File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/fileReport.it.spec.ts-snapshots/File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/fileReport.it.spec.ts-snapshots/File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/fileReport.it.spec.ts-snapshots/File-report-install-local-example-Options-ts--6b2d4-ant-when-scrolled-up-should-look-as-expected-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/lib/SseServer.ts: -------------------------------------------------------------------------------- 1 | import type { Server } from 'http'; 2 | import { createServer } from 'http'; 3 | import { RealTimeReporter } from 'mutation-testing-real-time'; 4 | import type { AddressInfo } from 'net'; 5 | import { promisify } from 'util'; 6 | 7 | export class SseTestServer { 8 | #server: Server; 9 | sse: RealTimeReporter; 10 | 11 | constructor() { 12 | this.sse = new RealTimeReporter({ accessControlAllowOrigin: '*' }); 13 | this.#server = createServer((_, res) => this.sse.add(res)); 14 | } 15 | 16 | public start(): number { 17 | this.#server.listen(0); 18 | return (this.#server.address() as AddressInfo).port; 19 | } 20 | 21 | public async close() { 22 | if (this.#server) { 23 | this.sse.sendFinished(); 24 | await promisify(this.#server.close.bind(this.#server))(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/elements/test/integration/lib/browser.ts: -------------------------------------------------------------------------------- 1 | export function isHeadless(): boolean { 2 | return !!(process.env.HEADLESS ?? process.env.CI); 3 | } 4 | -------------------------------------------------------------------------------- /packages/elements/test/integration/lib/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Sleep time before taking a screenshot when the page is scrolling with scroll-behaviour "smooth" 3 | * The exact timing function used for "smooth" scroll-behavior is browser dependent, so better be safe than sorry 🤷‍♂️ 4 | * https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-behavior#values 5 | */ 6 | export const SLEEP_FOR_SCROLL = 2000; 7 | -------------------------------------------------------------------------------- /packages/elements/test/integration/lib/helpers.ts: -------------------------------------------------------------------------------- 1 | import type { Page, TestInfo } from '@playwright/test'; 2 | import { expect, test } from '@playwright/test'; 3 | 4 | import { isHeadless } from './browser.js'; 5 | 6 | export function itShouldMatchScreenshot(title: string) { 7 | test(title, async function ({ page }, context) { 8 | await actScreenshotMatch(page, context); 9 | }); 10 | } 11 | 12 | export async function actScreenshotMatch(page: Page, context: TestInfo) { 13 | if (isHeadless()) { 14 | await page.locator('mutation-test-report-app >> :is(mte-test-view, mte-mutant-view)').waitFor(); 15 | await expect(page).toHaveScreenshot(); 16 | } else { 17 | console.log('[SKIP] skipping screenshot comparison, because not running in headless mode'); 18 | context.skip(); 19 | } 20 | } 21 | 22 | /** 23 | * Waits until the given predicate returns a truthy value. Calls and awaits the predicate 24 | * function at the given interval time. Can be used to poll until a certain condition is true. 25 | * 26 | * @example 27 | * ```js 28 | * import { waitUntil } from './lib/helpers.js'; 29 | * 30 | * await waitUntil(async () => expect(await drawer.isHalfOpen()).true); 31 | * ``` 32 | * 33 | * @param predicate - predicate function which is called each poll interval. 34 | * The predicate is awaited, so it can return a promise. 35 | * @param message an optional message to display when the condition timed out 36 | * @param options timeout and polling interval 37 | */ 38 | export function waitUntil( 39 | predicate: () => Promise, 40 | message?: string, 41 | { interval, timeout }: { interval?: number; timeout?: number } = {}, 42 | ): Promise { 43 | return expect(predicate, message).toPass({ intervals: interval ? [interval] : undefined, timeout }); 44 | } 45 | -------------------------------------------------------------------------------- /packages/elements/test/integration/phpReport.it.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | import { ReportPage } from './po/ReportPage.js'; 4 | 5 | test.describe('File report "infection-php-example/TextFileLogger.php"', () => { 6 | let page: ReportPage; 7 | 8 | test.beforeEach(async ({ page: p }) => { 9 | page = new ReportPage(p); 10 | await page.navigateTo('infection-php-example/#mutant/TextFileLogger.php'); 11 | }); 12 | 13 | test('should highlight the code', async () => { 14 | await page.mutantView.whenCodeIsHighlighted(); 15 | }); 16 | 17 | test('should show mutants', async () => { 18 | await page.mutantView.whenCodeIsHighlighted(); 19 | await expect(page.mutantView.mutantDot('ebf143eb565188ddd7959bfbe70f631f').dot).toBeVisible(); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/Breadcrumb.po.ts: -------------------------------------------------------------------------------- 1 | import { FilePicker } from './FilePicker.po.js'; 2 | import { PageObject } from './PageObject.po.js'; 3 | 4 | export default class Breadcrumb extends PageObject { 5 | public items() { 6 | return this.$('li'); 7 | } 8 | 9 | public async navigate(to: string): Promise { 10 | const anchor = this.host.getByText(to); 11 | await anchor.click(); 12 | } 13 | 14 | public async openFilePicker(): Promise { 15 | const button = this.$('button'); 16 | await button.click(); 17 | 18 | return new FilePicker(this.browser.locator('mutation-test-report-app >> mte-file-picker'), this.browser); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/Drawer.po.ts: -------------------------------------------------------------------------------- 1 | import { expect } from '@playwright/test'; 2 | 3 | import { PageObject } from './PageObject.po.js'; 4 | 5 | export const HALF_OPEN_SIZE = 120; 6 | const CLOSED_SIZE = 0; 7 | 8 | export class Drawer extends PageObject { 9 | public get header() { 10 | return this.$('[slot="header"]'); 11 | } 12 | 13 | private get readMoreToggle() { 14 | return this.$('mte-drawer >> [data-testId="btnReadMoreToggle"]'); 15 | } 16 | 17 | public toggleReadMore() { 18 | return this.readMoreToggle.click(); 19 | } 20 | 21 | public details() { 22 | return this.$('mte-drawer >> [slot="detail"]'); 23 | } 24 | 25 | public async clickOnHeader() { 26 | return this.header.click(); 27 | } 28 | 29 | public async height() { 30 | return (await this.$('mte-drawer').boundingBox())?.height ?? 0; 31 | } 32 | 33 | public summary() { 34 | return this.$('[slot="summary"]'); 35 | } 36 | 37 | public async whenOpen() { 38 | await expect.poll(async () => this.height()).toBeGreaterThan(HALF_OPEN_SIZE); 39 | } 40 | 41 | public whenHalfOpen() { 42 | return expect.poll(async () => this.height()).toBe(HALF_OPEN_SIZE); 43 | } 44 | 45 | public whenClosed() { 46 | return expect.poll(async () => this.height()).toBe(CLOSED_SIZE); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/ElementSelector.po.ts: -------------------------------------------------------------------------------- 1 | import type { Locator } from '@playwright/test'; 2 | 3 | export class ElementSelector { 4 | constructor(private readonly context: Locator) {} 5 | 6 | public $$(cssSelector: string): Promise { 7 | return this.context.locator(cssSelector).all(); 8 | } 9 | 10 | public $(cssSelector: string): Locator { 11 | return this.context.locator(cssSelector); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/FilePicker.po.ts: -------------------------------------------------------------------------------- 1 | import { PageObject } from './PageObject.po.js'; 2 | 3 | export class FilePicker extends PageObject { 4 | public async search(query: string) { 5 | return this.$('input').fill(query); 6 | } 7 | 8 | public async results() { 9 | return this.$$('#files li'); 10 | } 11 | 12 | public picker() { 13 | return this.$('dialog'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/MutantDot.po.ts: -------------------------------------------------------------------------------- 1 | import { MutantElement } from './MutantElement.po.js'; 2 | 3 | export class MutantDot extends MutantElement { 4 | public async isActive(): Promise { 5 | const classes = await this.classes(); 6 | return classes.includes('selected'); 7 | } 8 | 9 | get dot() { 10 | return this.host; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/MutantElement.po.ts: -------------------------------------------------------------------------------- 1 | import type { MutantStatus } from 'mutation-testing-report-schema/api'; 2 | 3 | import { PageObject } from './PageObject.po.js'; 4 | 5 | const allMutantStates = Object.keys({ 6 | Killed: null, 7 | Survived: null, 8 | NoCoverage: null, 9 | CompileError: null, 10 | RuntimeError: null, 11 | Timeout: null, 12 | Ignored: null, 13 | Pending: null, 14 | } satisfies Record) as MutantStatus[]; 15 | 16 | /** 17 | * Represents a dom element with a mutant status class mutant-id 18 | */ 19 | export abstract class MutantElement extends PageObject { 20 | public toggle() { 21 | return this.host.click(); 22 | } 23 | 24 | protected async classes() { 25 | return (await this.host.getAttribute('class'))?.split(' ') ?? []; 26 | } 27 | 28 | public async getStatus(): Promise { 29 | return (await this.classes()).find((clazz) => { 30 | const testState = allMutantStates.find((state) => state === clazz); 31 | if (testState) { 32 | return testState; 33 | } 34 | return; 35 | }) as MutantStatus | undefined; 36 | } 37 | public async mutantId() { 38 | return this.host.getAttribute('mutant-id'); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/MutantMarker.po.ts: -------------------------------------------------------------------------------- 1 | import { MutantElement } from './MutantElement.po.js'; 2 | 3 | export class MutantMarker extends MutantElement { 4 | async underlineIsVisible() { 5 | // First try text-decoration, then border-bottom 6 | return (await this.#styleMatches('textDecorationLine', 'underline')) || (await this.#styleMatches('borderBottomStyle', 'solid')); 7 | } 8 | 9 | async #styleMatches(property: keyof CSSStyleDeclaration & string, trueCase: string, falseCase = 'none') { 10 | const style = (await this.host.evaluate((el, property) => window.getComputedStyle(el)[property], property)) as string; 11 | switch (style) { 12 | case trueCase: 13 | return true; 14 | case falseCase: 15 | return false; 16 | default: 17 | throw new Error(`${property} style "${style}" is not supported`); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/MutantView.po.ts: -------------------------------------------------------------------------------- 1 | import type { Locator } from '@playwright/test'; 2 | 3 | import { Drawer } from './Drawer.po.js'; 4 | import { MutantDot } from './MutantDot.po.js'; 5 | import { MutantMarker } from './MutantMarker.po.js'; 6 | import { StateFilter } from './StateFilter.po.js'; 7 | import { View } from './View.po.js'; 8 | 9 | export class MutantView extends View { 10 | protected codeElement(): Locator { 11 | return this.$('mte-file >> pre'); 12 | } 13 | 14 | public async mutantDots(): Promise { 15 | const dots = await this.$$('mte-file >> svg.mutant-dot'); 16 | return dots.map((host) => new MutantDot(host, this.browser)); 17 | } 18 | 19 | public mutantDot(mutantId: number | string) { 20 | return new MutantDot(this.$(`mte-file >> svg.mutant-dot[mutant-id="${mutantId}"]`).first(), this.browser); 21 | } 22 | 23 | public mutantMarker(mutantId: number | string) { 24 | return new MutantMarker(this.$(`mte-file >> span.mutant[mutant-id="${mutantId}"]`).first(), this.browser); 25 | } 26 | public async mutantMarkers() { 27 | const spans = await this.$$(`mte-file >> span.mutant[mutant-id]`); 28 | return spans.map((span) => new MutantMarker(span, this.browser)); 29 | } 30 | 31 | public stateFilter() { 32 | const context = this.$('mte-file >> mte-state-filter'); 33 | return new StateFilter(context, this.browser); 34 | } 35 | 36 | public mutantDrawer() { 37 | const context = this.$('mte-drawer-mutant'); 38 | return new Drawer(context, this.browser); 39 | } 40 | public async currentDiff(): Promise { 41 | const [mutatedLineElements, originalLineElements] = ( 42 | await Promise.all([this.$('mte-file >> .diff-new .code').allInnerTexts(), this.$('mte-file >> .diff-old .code').allInnerTexts()]) 43 | ).map((items) => items.map((item) => item.trim())); 44 | 45 | if (mutatedLineElements.length || originalLineElements.length) { 46 | return { 47 | mutated: mutatedLineElements.join('\n').trim(), 48 | original: originalLineElements.join('\n').trim(), 49 | }; 50 | } 51 | return null; 52 | } 53 | } 54 | 55 | interface Diff { 56 | mutated: string; 57 | original: string; 58 | } 59 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/NavTab.po.ts: -------------------------------------------------------------------------------- 1 | import { PageObject } from './PageObject.po.js'; 2 | 3 | export class NavTab extends PageObject { 4 | async isActive() { 5 | const link = this.anchorLink(); 6 | const cssClasses = await link.getAttribute('aria-selected'); 7 | return cssClasses === 'true'; 8 | } 9 | 10 | async text() { 11 | return this.host.innerText(); 12 | } 13 | 14 | async navigate() { 15 | const link = this.anchorLink(); 16 | return link.click(); 17 | } 18 | 19 | private anchorLink() { 20 | return this.$('a'); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/PageObject.po.ts: -------------------------------------------------------------------------------- 1 | import type { Locator, Page } from '@playwright/test'; 2 | import { expect } from '@playwright/test'; 3 | 4 | import { ElementSelector } from './ElementSelector.po.js'; 5 | 6 | export class PageObject extends ElementSelector { 7 | constructor( 8 | protected readonly host: Locator, 9 | protected readonly browser: Page, 10 | ) { 11 | super(host); 12 | } 13 | 14 | public waitForVisible() { 15 | return expect(this.host).toBeVisible(); 16 | } 17 | 18 | public waitForHidden() { 19 | return expect(this.host).toBeHidden(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/ProgressBar.po.ts: -------------------------------------------------------------------------------- 1 | import { expect } from '@playwright/test'; 2 | 3 | import { PageObject } from './PageObject.po.js'; 4 | 5 | export class ProgressBar extends PageObject { 6 | private readonly progressBar = this.$('[role=progressbar]'); 7 | 8 | public expectPercentage = async (percentage: string | number) => expect(this.progressBar).toHaveAttribute('aria-valuenow', `${percentage}`); 9 | public barSize = async () => (await this.progressBar.boundingBox()) ?? { width: 0, height: 0 }; 10 | public totalSize = async () => (await this.host.boundingBox()) ?? { width: 0, height: 0 }; 11 | public relativeBarWidth = async () => { 12 | const [totalSize, barSize] = await Promise.all([this.totalSize(), this.barSize()]); 13 | return Math.floor((barSize.width / totalSize.width) * 100); 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/RealTimeProgressBar.po.ts: -------------------------------------------------------------------------------- 1 | import { PageObject } from './PageObject.po.js'; 2 | 3 | export class RealTimeProgressBar extends PageObject { 4 | public async smallProgressBarVisible() { 5 | const smallProgressBar = this.$('div.pointer-events-none'); 6 | return (await smallProgressBar.evaluate((el) => getComputedStyle(el).opacity)) === '1'; 7 | } 8 | 9 | get progressBar() { 10 | return this.$('div.my-4'); 11 | } 12 | 13 | public async progressBarWidth() { 14 | return await this.$('.parts > div') 15 | .first() 16 | .boundingBox() 17 | .then((el) => el?.width); 18 | } 19 | 20 | public killedCount() { 21 | return this.host.getByTitle('killed + timeout').first(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/ResultTable.po.ts: -------------------------------------------------------------------------------- 1 | import { PageObject } from './PageObject.po.js'; 2 | import { ResultTableRow } from './ResultTableRow.po.js'; 3 | 4 | export class ResultTable extends PageObject { 5 | public head() { 6 | return this.$$('thead th'); 7 | } 8 | 9 | public async rows() { 10 | const rows = await this.$$('tbody tr'); 11 | return rows.map((row) => new ResultTableRow(row, this.browser)); 12 | } 13 | 14 | public row(name: string) { 15 | return new ResultTableRow(this.$(`tbody tr[title="${name}"]`), this.browser); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/ResultTableRow.po.ts: -------------------------------------------------------------------------------- 1 | import { PageObject } from './PageObject.po.js'; 2 | import { ProgressBar } from './ProgressBar.po.js'; 3 | 4 | export class ResultTableRow extends PageObject { 5 | private readonly nameTableElement = this.$.bind(this, 'td:nth-child(1)'); 6 | public navigate = () => this.nameTableElement().locator('a').click(); 7 | public name = () => this.nameTableElement().innerText(); 8 | public progressBar = () => new ProgressBar(this.$('td:nth-child(2)>div.rounded-full'), this.browser); 9 | public mutationScore = () => this.$('td:nth-child(3)'); 10 | public testStrengthProgressBar = () => new ProgressBar(this.$('td:nth-child(4)>div.rounded-full'), this.browser); 11 | public mutationScoreBasedOnCoveredCode = () => this.$('td:nth-child(5)'); 12 | public killed = () => this.$('td:nth-child(6)'); 13 | public survived = () => this.$('td:nth-child(7)'); 14 | public timeout = () => this.$('td:nth-child(8)'); 15 | public noCoverage = () => this.$('td:nth-child(9)'); 16 | public ignored = () => this.$('td:nth-child(10)'); 17 | public runtimeErrors = () => this.$('td:nth-child(11)'); 18 | public compileErrors = () => this.$('td:nth-child(12)'); 19 | public totalDetected = () => this.$('td:nth-child(13)'); 20 | public totalUndetected = () => this.$('td:nth-child(14)'); 21 | public totalMutants = () => this.$('td:nth-child(15)'); 22 | } 23 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/StateFilter.po.ts: -------------------------------------------------------------------------------- 1 | import type { TestStatus } from 'mutation-testing-metrics'; 2 | import type { MutantStatus } from 'mutation-testing-report-schema/api'; 3 | 4 | import { PageObject } from './PageObject.po.js'; 5 | import { StateFilterCheckbox } from './StateFilterCheckbox.po.js'; 6 | 7 | export class StateFilter extends PageObject { 8 | public state(state: MutantStatus | TestStatus): StateFilterCheckbox { 9 | return new StateFilterCheckbox(this.$(`[data-status="${state}"]`), this.browser); 10 | } 11 | 12 | public previous(): Promise { 13 | return this.$('button[title=Previous]').click(); 14 | } 15 | 16 | public next(): Promise { 17 | return this.$('button[title=Next]').click(); 18 | } 19 | 20 | public async states() { 21 | const labels = await this.$$('[data-status]'); 22 | return labels.map((label) => new StateFilterCheckbox(label, this.browser)); 23 | } 24 | 25 | public get statesLocator() { 26 | return this.$('[data-status]'); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/StateFilterCheckbox.po.ts: -------------------------------------------------------------------------------- 1 | import { PageObject } from './PageObject.po.js'; 2 | 3 | export class StateFilterCheckbox extends PageObject { 4 | public click() { 5 | return this.host.click(); 6 | } 7 | 8 | public text() { 9 | return this.host.innerText(); 10 | } 11 | 12 | get input() { 13 | return this.$('input'); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/TestDot.po.ts: -------------------------------------------------------------------------------- 1 | import { TestStatus } from 'mutation-testing-metrics'; 2 | 3 | import { PageObject } from './PageObject.po.js'; 4 | 5 | const allTestStates = Object.values(TestStatus) as TestStatus[]; 6 | 7 | export class TestDot extends PageObject { 8 | public toggle() { 9 | return this.host.click(); 10 | } 11 | 12 | private async classes(): Promise { 13 | return (await this.host.getAttribute('class'))?.split(' ') ?? []; 14 | } 15 | 16 | public async getStatus(): Promise { 17 | return (await this.classes()).find((clazz) => { 18 | // eslint-disable-next-line @typescript-eslint/no-unsafe-enum-comparison 19 | const testState = allTestStates.find((state) => state === clazz); 20 | if (testState) { 21 | return testState; 22 | } 23 | return; 24 | }) as TestStatus | undefined; 25 | } 26 | 27 | public async isActive(): Promise { 28 | const classes = await this.classes(); 29 | return classes.includes('selected'); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/TestListItem.po.ts: -------------------------------------------------------------------------------- 1 | import { PageObject } from './PageObject.po.js'; 2 | 3 | export class TestListItem extends PageObject { 4 | public async isSelected() { 5 | return (await this.host.getAttribute('data-active')) === 'true'; 6 | } 7 | 8 | public toggle() { 9 | return this.host.click(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/TestView.po.ts: -------------------------------------------------------------------------------- 1 | import type { Locator } from '@playwright/test'; 2 | 3 | import { Drawer } from './Drawer.po.js'; 4 | import { StateFilter } from './StateFilter.po.js'; 5 | import { TestDot } from './TestDot.po.js'; 6 | import { TestListItem } from './TestListItem.po.js'; 7 | import { View } from './View.po.js'; 8 | 9 | export class TestView extends View { 10 | protected codeElement(): Locator { 11 | return this.$('mte-test-file >> pre'); 12 | } 13 | 14 | public async testDots(): Promise { 15 | return (await this.$$('mte-test-file >> svg.test-dot')).map((el) => new TestDot(el, this.browser)); 16 | } 17 | 18 | public async testListItems(): Promise { 19 | return (await this.$$('mte-test-file >> button[test-id]')).map((host) => new TestListItem(host, this.browser)); 20 | } 21 | 22 | public testDot(testId: number | string) { 23 | const el = this.$(`mte-test-file >> svg.test-dot[test-id="${testId}"]`); 24 | return new TestDot(el, this.browser); 25 | } 26 | 27 | public get stateFilter() { 28 | const context = this.$('mte-test-file >> mte-state-filter'); 29 | return new StateFilter(context, this.browser); 30 | } 31 | 32 | public get testDrawer() { 33 | const context = this.$('mte-drawer-test'); 34 | return new Drawer(context, this.browser); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/ThemeSelector.po.ts: -------------------------------------------------------------------------------- 1 | import { PageObject } from './PageObject.po.js'; 2 | 3 | export type Theme = 'dark' | 'light'; 4 | 5 | export class ThemeSelector extends PageObject { 6 | public async select(mode: Theme) { 7 | const needsToggle = (await this.selectedTheme()) !== mode; 8 | if (needsToggle) { 9 | return this.$('.check-box-container label').click(); 10 | } 11 | } 12 | 13 | async selectedTheme(): Promise { 14 | const darkModeSelected = await this.darkModeCheckbox.isChecked(); 15 | return darkModeSelected ? 'dark' : 'light'; 16 | } 17 | 18 | private get darkModeCheckbox() { 19 | return this.$('input[type="checkbox"]'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/elements/test/integration/po/View.po.ts: -------------------------------------------------------------------------------- 1 | import type { Locator } from '@playwright/test'; 2 | 3 | import { PageObject } from './PageObject.po.js'; 4 | import { ResultTable } from './ResultTable.po.js'; 5 | 6 | export abstract class View extends PageObject { 7 | public clickOnCode() { 8 | return this.codeElement().click(); 9 | } 10 | 11 | public async whenCodeIsHighlighted(): Promise { 12 | return this.codeElement().locator('span.token').first().waitFor(); 13 | } 14 | 15 | public async codeBackgroundColor(): Promise { 16 | return this.codeElement().evaluate((el) => getComputedStyle(el).backgroundColor); 17 | } 18 | 19 | protected abstract codeElement(): Locator; 20 | 21 | public async scrollToCode() { 22 | const codeElement = this.codeElement(); 23 | await this.browser.evaluate(`window.scrollTo(0, ${(await codeElement.boundingBox())!.y})`); 24 | } 25 | 26 | public resultTable() { 27 | return new ResultTable(this.$('mte-metrics-table'), this.browser); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/elements/test/integration/test-view.it.spec.ts-snapshots/Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/test-view.it.spec.ts-snapshots/Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/test-view.it.spec.ts-snapshots/Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/test-view.it.spec.ts-snapshots/Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/test-view.it.spec.ts-snapshots/Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/test-view.it.spec.ts-snapshots/Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/test-view.it.spec.ts-snapshots/Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/test-view.it.spec.ts-snapshots/Test-view-test-file-with-code-and-test-locatio-3c4f6-ating-previous-test-should-look-as-expected-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-should-match-the-dark-theme-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-should-match-the-dark-theme-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-should-match-the-dark-theme-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-should-match-the-dark-theme-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-should-match-the-dark-theme-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-should-match-the-dark-theme-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-should-match-the-dark-theme-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-should-match-the-dark-theme-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-a-code-file-should-match-the-dark-theme-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picke-6b805-in-the-search-box-should-match-the-dark-theme-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-dark-theme-when-opening-the-file-picker-should-match-the-dark-theme-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-should-match-the-light-theme-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-should-match-the-light-theme-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-should-match-the-light-theme-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-should-match-the-light-theme-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-should-match-the-light-theme-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-should-match-the-light-theme-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-should-match-the-light-theme-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-should-match-the-light-theme-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-a-code-file-should-match-the-light-theme-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-pick-d5814-n-the-search-box-should-match-the-light-theme-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-chromium-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-chromium-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-chromium-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-firefox-linux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-firefox-linux.png -------------------------------------------------------------------------------- /packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-firefox-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stryker-mutator/mutation-testing-elements/4549563e42519add209f1b3ed7a301c10c68aebe/packages/elements/test/integration/theming.it.spec.ts-snapshots/Theming-light-theme-when-opening-the-file-picker-should-match-the-light-theme-1-firefox-win32.png -------------------------------------------------------------------------------- /packages/elements/test/integration/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.elements.settings.json", 3 | "compilerOptions": { 4 | "types": ["playwright", "node"], 5 | "emitDeclarationOnly": true, 6 | "outDir": "../../dist-tsc/test/integration" 7 | }, 8 | "references": [ 9 | { 10 | "path": "../../src" 11 | }, 12 | { 13 | "path": "../../../real-time/src" 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /packages/elements/test/integration/unsanitized.it.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from '@playwright/test'; 2 | 3 | import type { MutantMarker } from './po/MutantMarker.po.js'; 4 | import { ReportPage } from './po/ReportPage.js'; 5 | 6 | test.describe('Unsanitized example', () => { 7 | let page: ReportPage; 8 | 9 | test.beforeEach(({ page: p }) => { 10 | page = new ReportPage(p); 11 | }); 12 | 13 | test.describe('mutant view', () => { 14 | test.beforeEach(async () => { 15 | await page.navigateTo('unsanitized-example/#mutant/platform.ts'); 16 | }); 17 | 18 | test('should escape quotes in mutant ids', async () => { 19 | const mutantMarkers = await page.mutantView.mutantMarkers(); 20 | let m: MutantMarker | undefined; 21 | for (const mutantMarker of mutantMarkers) { 22 | const mutantId = await mutantMarker.mutantId(); 23 | if (mutantId === 'src/platform.ts@7:31-7:38\nStringLiteral: ""') { 24 | m = mutantMarker; 25 | break; 26 | } 27 | } 28 | expect(m).toBeTruthy(); 29 | await m!.toggle(); 30 | await page.mutantView.mutantDrawer().whenHalfOpen(); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/elements/test/unit/helpers/helperFunctions.ts: -------------------------------------------------------------------------------- 1 | import type { MutantStatus } from 'mutation-testing-report-schema/api'; 2 | import colors from 'tailwindcss/colors.js'; 3 | 4 | export function normalizeWhitespace(pseudoHtml: string) { 5 | return pseudoHtml.replace(/\s+/g, ' ').trim(); 6 | } 7 | 8 | export const expectedMutantColors = Object.freeze({ 9 | Killed: fixCssVarPercentage(colors.green['100']), 10 | Survived: fixCssVarPercentage(colors.red['100']), 11 | NoCoverage: fixCssVarPercentage(colors.orange['100']), 12 | Timeout: fixCssVarPercentage(colors.yellow['100']), 13 | CompileError: fixCssVarPercentage(colors.zinc['100']), 14 | RuntimeError: fixCssVarPercentage(colors.zinc['100']), 15 | Ignored: fixCssVarPercentage(colors.zinc['100']), 16 | Pending: fixCssVarPercentage(colors.zinc['100']), 17 | } satisfies Record); 18 | 19 | /** 20 | * Replaces `oklch(97.3%` with `oklch(0.973`. The variable is defined as a %, but the browser reads it as a percentile number 21 | */ 22 | export function fixCssVarPercentage(value: string) { 23 | const match = /oklch\(((\d+)(\.\d)?%)/.exec(value); 24 | 25 | if (match) { 26 | const updatedCssVar = value.replace(match[1], `0.${match[2]}${match[3]?.replace('.', '') ?? ''}`); 27 | return updatedCssVar; 28 | } 29 | return value; 30 | } 31 | -------------------------------------------------------------------------------- /packages/elements/test/unit/helpers/tick.ts: -------------------------------------------------------------------------------- 1 | export function tick() { 2 | return new Promise((res) => setTimeout(res, 0)); 3 | } 4 | -------------------------------------------------------------------------------- /packages/elements/test/unit/lib/browser.spec.ts: -------------------------------------------------------------------------------- 1 | import type { MockInstance } from 'vitest'; 2 | 3 | import { isLocalStorageAvailable } from '../../../src/lib/browser.js'; 4 | 5 | describe(isLocalStorageAvailable.name, () => { 6 | let setItemStub: MockInstance; 7 | let removeItemStub: MockInstance; 8 | 9 | beforeEach(() => { 10 | setItemStub = vi.spyOn(Storage.prototype, 'setItem'); 11 | removeItemStub = vi.spyOn(Storage.prototype, 'removeItem'); 12 | }); 13 | 14 | it(`should be false if setItem throws`, () => { 15 | setItemStub.mockImplementation(() => { 16 | throw new Error('Quota exceeded'); 17 | }); 18 | 19 | expect(isLocalStorageAvailable()).to.be.false; 20 | }); 21 | 22 | it(`should be false if removeItem throws`, () => { 23 | removeItemStub.mockImplementation(() => { 24 | throw new Error('Quota exceeded'); 25 | }); 26 | 27 | expect(isLocalStorageAvailable()).to.be.false; 28 | }); 29 | 30 | it(`should be false if localStorage is undefined`, () => { 31 | vi.spyOn(window, 'localStorage', 'get').mockImplementation(() => undefined as unknown as Storage); 32 | 33 | expect(isLocalStorageAvailable()).to.be.false; 34 | }); 35 | 36 | it('should be true if localStorage works', () => { 37 | expect(isLocalStorageAvailable()).to.be.true; 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/elements/test/unit/lib/html-helpers.spec.ts: -------------------------------------------------------------------------------- 1 | import type { MutantStatus } from 'mutation-testing-report-schema/api'; 2 | 3 | import { escapeHtml, getContextClassForStatus, toAbsoluteUrl } from '../../../src/lib/html-helpers.js'; 4 | 5 | describe(getContextClassForStatus.name, () => { 6 | function actArrangeAssert(expected: string, input: MutantStatus) { 7 | it(`should should show "${expected}" for "${input}"`, () => { 8 | expect(getContextClassForStatus(input)).eq(expected); 9 | }); 10 | } 11 | actArrangeAssert('success', 'Killed'); 12 | actArrangeAssert('danger', 'Survived'); 13 | actArrangeAssert('caution', 'NoCoverage'); 14 | actArrangeAssert('warning', 'Timeout'); 15 | actArrangeAssert('secondary', 'CompileError'); 16 | actArrangeAssert('secondary', 'RuntimeError'); 17 | actArrangeAssert('secondary', 'Ignored'); 18 | }); 19 | 20 | describe(escapeHtml.name, () => { 21 | function actArrangeAssert(input: string, expectedOutput: string) { 22 | it(`should translate ${input} to ${expectedOutput}`, () => { 23 | expect(escapeHtml(input)).eq(expectedOutput); 24 | }); 25 | } 26 | 27 | actArrangeAssert('foo&bar', 'foo&bar'); 28 | actArrangeAssert('foobar', 'foo>bar'); 30 | actArrangeAssert('foo"bar', 'foo"bar'); 31 | actArrangeAssert("foo'bar", 'foo'bar'); 32 | }); 33 | 34 | describe(toAbsoluteUrl.name, () => { 35 | it('should make a fragment absolute', () => { 36 | const actual = toAbsoluteUrl('foo'); 37 | expect(actual).eq(expectedUrl()); 38 | 39 | function expectedUrl() { 40 | const currentUrl = window.location.href; 41 | if (currentUrl.endsWith('#')) { 42 | return `${currentUrl}foo`; 43 | } else { 44 | return `${currentUrl}#foo`; 45 | } 46 | } 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/elements/test/unit/lib/router.spec.ts: -------------------------------------------------------------------------------- 1 | import { take } from 'rxjs/operators'; 2 | 3 | import { locationChange$ } from '../../../src/lib/router.js'; 4 | 5 | describe('locationChange$', () => { 6 | afterEach(() => { 7 | window.location.hash = ''; 8 | }); 9 | 10 | it('should emit a value as soon as it is subscribed', async () => { 11 | const actual = await locationChange$.pipe(take(1)).toPromise(); 12 | expect(actual).deep.eq([]); 13 | }); 14 | 15 | it('should respond to the "hashchange" event', async () => { 16 | const sut = locationChange$.pipe(take(2)).toPromise(); 17 | window.location.hash = 'foo'; 18 | const actualValue = await sut; 19 | expect(actualValue).deep.eq(['foo']); 20 | }); 21 | 22 | it('should split on "/", and filter out empty parts', async () => { 23 | const sut = locationChange$.pipe(take(2)).toPromise(); 24 | window.location.hash = 'foo//bar'; 25 | const actualValue = await sut; 26 | expect(actualValue).deep.eq(['foo', 'bar']); 27 | }); 28 | 29 | it('should url decode the path components before emitting a value', async () => { 30 | const sut = locationChange$.pipe(take(2)).toPromise(); 31 | window.location.hash = 'foo/%60bar%60.js'; 32 | const actualValue = await sut; 33 | expect(actualValue).deep.eq(['foo', '`bar`.js']); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/elements/test/unit/setup.ts: -------------------------------------------------------------------------------- 1 | import '../../src'; 2 | -------------------------------------------------------------------------------- /packages/elements/test/unit/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.elements.settings.json", 3 | "compilerOptions": { 4 | "types": ["vite/client", "vitest/globals", "vitest/importMeta", "vitest", "@vitest/browser/providers/playwright"], 5 | "emitDeclarationOnly": true, 6 | "outDir": "../../dist-tsc/test/unit" 7 | }, 8 | "references": [ 9 | { 10 | "path": "../../src/tsconfig.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/elements/test/unit/typings/prism.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'prismjs/components/prism-core' { 2 | // Core has same exports as root prismjs export 3 | export * from 'prismjs'; 4 | } 5 | -------------------------------------------------------------------------------- /packages/elements/testResources/csharp-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Install local example - Mutation test elements 5 | 6 | 7 | 8 | 9 | Back 10 | 11 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /packages/elements/testResources/cucumber-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 🥒 Cucumber example - Mutation test elements 5 | 6 | 7 | 8 | 9 | 10 | Back 11 | 12 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/elements/testResources/deep-dir-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Back 10 | 11 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /packages/elements/testResources/deep-dir-example/mutation-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "mutationScore": 50, 3 | "thresholds": { 4 | "high": 80, 5 | "low": 60 6 | }, 7 | "projectRoot": "/src/project", 8 | "files": { 9 | "a/b/test.js": { 10 | "language": "javascript", 11 | "source": "function add(a, b) {\n return a + b;\n}", 12 | "mutants": [ 13 | { 14 | "id": "1", 15 | "mutatorName": "Arithmetic Operator", 16 | "replacement": "-", 17 | "location": { 18 | "start": { 19 | "line": 2, 20 | "column": 12 21 | }, 22 | "end": { 23 | "line": 2, 24 | "column": 13 25 | } 26 | }, 27 | "status": "Survived" 28 | }, 29 | { 30 | "id": "2", 31 | "mutatorName": "Block Statement", 32 | "replacement": "{}", 33 | "location": { 34 | "start": { 35 | "line": 1, 36 | "column": 20 37 | }, 38 | "end": { 39 | "line": 3, 40 | "column": 1 41 | } 42 | }, 43 | "status": "Killed" 44 | } 45 | ] 46 | }, 47 | "a/b/test2.js": { 48 | "language": "javascript", 49 | "source": "function add(a, b) {\n return a + b;\n}", 50 | "mutants": [] 51 | }, 52 | "foo/bar/baz.js": { 53 | "language": "javascript", 54 | "source": "function add(a, b) {\n return a + b;\n}", 55 | "mutants": [] 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/elements/testResources/infection-php-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Back 6 | 7 | 8 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packages/elements/testResources/large-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Back 12 | 13 | 14 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /packages/elements/testResources/lighthouse-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple example - Mutation test elements 6 | 7 | 8 | 9 | 10 | Back 11 | 12 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/elements/testResources/pitest-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Back 6 | 7 | 8 | 9 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /packages/elements/testResources/realtime-reporting-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Install local example - Mutation test elements 6 | 7 | 8 | 9 | 10 | Back 11 | 12 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /packages/elements/testResources/rust-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Back 12 | 13 | 14 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/elements/testResources/simple-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple example - Mutation test elements 6 | 7 | 8 | 9 | 10 | Back 11 | 12 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /packages/elements/testResources/simple-example/mutation-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "mutationScore": 50, 3 | "thresholds": { 4 | "high": 80, 5 | "low": 60 6 | }, 7 | "projectRoot": "/src/project", 8 | "files": { 9 | "test.js": { 10 | "language": "javascript", 11 | "source": "\"use strict\";\nfunction add(a, b) {\n return a + b;\n}", 12 | "mutants": [ 13 | { 14 | "id": "3", 15 | "location": { 16 | "start": { 17 | "column": 1, 18 | "line": 1 19 | }, 20 | "end": { 21 | "column": 13, 22 | "line": 1 23 | } 24 | }, 25 | "replacement": "\"\"", 26 | "mutatorName": "String Literal", 27 | "status": "Survived" 28 | }, 29 | { 30 | "id": "1", 31 | "mutatorName": "Arithmetic Operator", 32 | "replacement": "-", 33 | "location": { 34 | "start": { 35 | "line": 3, 36 | "column": 12 37 | }, 38 | "end": { 39 | "line": 3, 40 | "column": 13 41 | } 42 | }, 43 | "status": "Survived" 44 | }, 45 | { 46 | "id": "2", 47 | "mutatorName": "Block Statement", 48 | "replacement": "{}", 49 | "location": { 50 | "start": { 51 | "line": 2, 52 | "column": 20 53 | }, 54 | "end": { 55 | "line": 4, 56 | "column": 1 57 | } 58 | }, 59 | "status": "Killed" 60 | } 61 | ] 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /packages/elements/testResources/stryker-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test files example - Mutation test elements 6 | 7 | 8 | 9 | 10 | 11 | Back 12 | 13 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/elements/testResources/svelte-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Svelte example - Mutation test elements 6 | 7 | 8 | 9 | 10 | 11 | Back 12 | 13 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/elements/testResources/svelte-example/svelte-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /packages/elements/testResources/test-files-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test files example - Mutation test elements 6 | 7 | 8 | 9 | 10 | 11 | Back 12 | 13 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/elements/testResources/tests-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test files example - Mutation test elements 6 | 7 | 8 | 9 | 10 | 11 | Back 12 | 13 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/elements/testResources/unsanitized-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test files example - Mutation test elements 6 | 7 | 8 | 9 | 10 | 11 | Back 12 | 13 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /packages/elements/tsconfig.elements.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "lib": ["es2022", "DOM", "DOM.Iterable"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/elements/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./src" 6 | }, 7 | { 8 | "path": "./test/unit" 9 | }, 10 | { 11 | "path": "./test/integration" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /packages/metrics-scala/.gitignore: -------------------------------------------------------------------------------- 1 | elements/ 2 | report-schema/ 3 | 4 | project/metals.sbt 5 | project/project/ 6 | -------------------------------------------------------------------------------- /packages/metrics-scala/.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !README.md 3 | !dist/**/* 4 | !dist/**/*. 5 | tsconfig.tsbuildinfo 6 | target/ 7 | -------------------------------------------------------------------------------- /packages/metrics-scala/.scalafix.conf: -------------------------------------------------------------------------------- 1 | OrganizeImports { 2 | coalesceToWildcardImportThreshold = 5 3 | groupedImports = Merge 4 | } 5 | -------------------------------------------------------------------------------- /packages/metrics-scala/.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = "3.9.7" 2 | project.git = true 3 | lineEndings = unix 4 | maxColumn = 120 5 | align.preset = more 6 | runner.dialect = scala213 7 | 8 | assumeStandardLibraryStripMargin = true 9 | 10 | rewrite.rules = [SortImports, SortModifiers, RedundantParens, RedundantBraces, PreferCurlyFors] 11 | rewrite.redundantBraces.stringInterpolation = true 12 | -------------------------------------------------------------------------------- /packages/metrics-scala/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.watcherExclude": { 3 | "**/target": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /packages/metrics-scala/NPM_PROJECTS_PUBLISHING.md: -------------------------------------------------------------------------------- 1 | # NPM projects & publishing 2 | 3 | There are two npm projects in this repository that are useful to be published to Sonatype for use in Java/Scala/etc projects: 4 | 5 | - `mutation-testing-report-schema` 6 | - `mutation-testing-elements` 7 | 8 | To add the projects to your depedencies (replace `mutation-testing-elements` with `mutation-testing-report-schema` if you need the JSON schema): 9 | 10 | ## Maven: 11 | 12 | ```xml 13 | 14 | io.stryker-mutator 15 | mutation-testing-elements 16 | ${mutation-testing-elements.version} 17 | 18 | ``` 19 | 20 | ## Sbt: 21 | 22 | ```scala 23 | libraryDependencies += "io.stryker-mutator" % "mutation-testing-elements" % elementsVersion // Version defined elsewhere 24 | ``` 25 | 26 | ## Usage 27 | 28 | The projects are empty, save for resources from the dist folder of the npm projects. The resources are under the project name folder. For example, to get the html report js file: 29 | 30 | ```java 31 | this.getClass().getClassLoader().getResourceAsStream("mutation-testing-elements/mutation-test-elements.js") 32 | ``` 33 | 34 | ## Publishing 35 | 36 | To work with the npm projects, two empty sbt projects are added. When nothing is configured, they'll have `skip in publish := true` set by sbt. This way they won't be published along with the rest of the projects. 37 | 38 | When, for example, `mutation-testing-elements` is published there is a postPublish step that calls a [script](./npmProjPublish.sh) with the environment variable `PUBLISH_ELEMENTS=true`. The script copies over the `dist` files from the npm project to the resources folder of the sbt project and calls `sbt publish`. When the `PUBLISH_ELEMENTS` variable is true, only the elements project will be published (`skip in publish := false` for that project), and all the other projects will be skipped (`skip in publish := true`). The schema project is published the same way, but the the `PUBLISH_SCHEMA` environment variable. 39 | -------------------------------------------------------------------------------- /packages/metrics-scala/circe/src/main/scala/mutationtesting/CodecOps.scala: -------------------------------------------------------------------------------- 1 | package mutationtesting 2 | 3 | import io.circe.{Codec, Decoder, Encoder} 4 | 5 | protected[mutationtesting] object CodecOps { 6 | implicit class CodecMapOps[A](codec: Codec[A]) { 7 | 8 | /** Create a new Codec by mapping both the Decoder and Encoder to a new type with the given functions and combining 9 | * them 10 | */ 11 | def mapCodec[B](f: Decoder[A] => Decoder[B])(g: Encoder[A] => Encoder[B]) = 12 | Codec.from(f(codec), g(codec)) 13 | 14 | /** Map the Decoder inside this Codec and return a new Codec with the given function applied 15 | */ 16 | def mapDecoder(f: Decoder[A] => Decoder[A]): Codec[A] = 17 | mapCodec(f)(identity) 18 | 19 | /** Map the Encoder inside this Codec and return a new Codec with the given function applied 20 | */ 21 | def mapEncoder(f: Encoder[A] => Encoder[A]): Codec[A] = 22 | mapCodec(identity)(f) 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/metrics-scala/metrics/src/main/scala/mutationtesting/Metrics.scala: -------------------------------------------------------------------------------- 1 | package mutationtesting 2 | 3 | object Metrics { 4 | 5 | def calculateMetrics( 6 | mutationTestReport: MutationTestResult[Any] 7 | ): MetricsResult = 8 | calculateMetrics(mutationTestReport.files) 9 | 10 | def calculateMetrics( 11 | mutationTestResults: FileResultDictionary 12 | ): MetricsResult = 13 | MetricsResultRoot( 14 | parseMutationTestResults( 15 | mutationTestResults 16 | .map { case (name, result) => (name.split("/").toSeq, result) } 17 | ) 18 | ) 19 | 20 | private def parseMutationTestResults( 21 | results: Map[Seq[String], FileResult] 22 | ): Seq[MetricsResult] = { 23 | val (rootFiles, directories) = results.partition(_._1.size == 1) 24 | 25 | directories 26 | .groupBy(_._1.head) 27 | .map(a => (a._1, a._2.map(b => (b._1.tail, b._2)))) 28 | .map { case (name, result) => 29 | MetricsDirectory(name, parseMutationTestResults(result)) 30 | } 31 | .toSeq 32 | .sortBy(_.dirName) ++ 33 | rootFiles.map { case (name, result) => 34 | MetricsFile(name.head, result.mutants.map(m => MetricMutant(m.status))) 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /packages/metrics-scala/metrics/src/main/scala/mutationtesting/MutantStatus.scala: -------------------------------------------------------------------------------- 1 | package mutationtesting 2 | 3 | /** Result of the mutation. */ 4 | sealed trait MutantStatus extends Product with Serializable 5 | 6 | object MutantStatus { 7 | 8 | case object Killed extends MutantStatus 9 | case object Survived extends MutantStatus 10 | case object NoCoverage extends MutantStatus 11 | case object Timeout extends MutantStatus 12 | case object CompileError extends MutantStatus 13 | case object RuntimeError extends MutantStatus 14 | case object Ignored extends MutantStatus 15 | case object Pending extends MutantStatus 16 | 17 | } 18 | -------------------------------------------------------------------------------- /packages/metrics-scala/metrics/src/main/scala/mutationtesting/Thresholds.scala: -------------------------------------------------------------------------------- 1 | package mutationtesting 2 | 3 | /** Thresholds for the status of the reported application. 4 | * 5 | * Suggested method for creating a Thresholds object is by the 'smart' `create` constructor in the companion object 6 | * 7 | * @param high 8 | * Higher bound threshold. 9 | * @param low 10 | * Lower bound threshold. 11 | */ 12 | final case class Thresholds(high: Int, low: Int) 13 | 14 | object Thresholds { 15 | 16 | /** Smart constructor to create a [[mutationtesting.Thresholds]]. Returns an Either of an error message if the values 17 | * are out of bounds, or the Thresholds object 18 | */ 19 | def create(high: Int, low: Int): Either[String, Thresholds] = 20 | (high, low) match { 21 | case (high, _) if high > 100 => Left(s"thresholds.high should be <= 100") 22 | case (high, _) if high < 0 => Left(s"thresholds.high should be > 0") 23 | case (_, low) if low > 100 => Left(s"thresholds.low should be <= 100") 24 | case (_, low) if low < 0 => Left(s"thresholds.low should be > 0") 25 | case (high, low) => Right(Thresholds(high, low)) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/metrics-scala/metrics/src/main/scala/mutationtesting/package.scala: -------------------------------------------------------------------------------- 1 | package object mutationtesting { 2 | 3 | /** All mutated files, with the relative path of the file as the key 4 | */ 5 | type FileResultDictionary = Map[String, FileResult] 6 | 7 | /** Test file definitions by file path OR class name. 8 | */ 9 | type TestFileDefinitionDictionary = Map[String, TestFile] 10 | 11 | /** Dependencies used by the framework. Key-value pair of dependencies and their versions. 12 | */ 13 | type Dependencies = Map[String, String] 14 | } 15 | -------------------------------------------------------------------------------- /packages/metrics-scala/metrics/src/test/scala/mutationtesting/MutationTestResultTest.scala: -------------------------------------------------------------------------------- 1 | package mutationtesting 2 | 3 | class MutationTestResultTest extends munit.FunSuite { 4 | test( 5 | "MutationTestResult should have correct default $schema and schemaVersion" 6 | ) { 7 | val sut = 8 | MutationTestResult(thresholds = Thresholds(80, 60), files = Map.empty) 9 | 10 | assertEquals(sut.`$schema`.get, "https://git.io/mutation-testing-schema") 11 | assertEquals(sut.schemaVersion, "2") 12 | } 13 | 14 | test("FileResult should have default language Scala") { 15 | val sut = FileResult("", Seq.empty) 16 | 17 | assertEquals(sut.language, "scala") 18 | } 19 | 20 | val validThresholds = List( 21 | (0, 0), 22 | (1, 0), 23 | (0, 1), 24 | (99, 0), 25 | (0, 99), 26 | (100, 0), 27 | (0, 100), 28 | (100, 100) 29 | ) 30 | 31 | validThresholds.foreach { case (high, low) => 32 | test(s"Threshold should be valid for high $high low $low") { 33 | Thresholds.create(high, low) match { 34 | case Left(value) => fail(s"Expected valid threshold, got error $value") 35 | case Right(value) => 36 | assertEquals(value, Thresholds(high = high, low = low)) 37 | } 38 | } 39 | } 40 | 41 | val invalidThresholds = List( 42 | (101, 0, "thresholds.high should be <= 100"), 43 | (-1, 0, "thresholds.high should be > 0"), 44 | (0, 101, "thresholds.low should be <= 100"), 45 | (0, -1, "thresholds.low should be > 0") 46 | ) 47 | 48 | invalidThresholds.foreach { case (high, low, expectedErrorMessage) => 49 | test(s"Threshold should be invalid for high $high low $low") { 50 | Thresholds.create(high, low) match { 51 | case Left(value) => assertEquals(value, expectedErrorMessage) 52 | case Right(value) => fail(s"Expected Left error, got Right $value") 53 | } 54 | } 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /packages/metrics-scala/npmProjPublish.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # 1st argument, or dist folder 5 | DIST_BASE=${1:-dist} 6 | # Full directory name 7 | DIST_DIR=$PWD/$DIST_BASE 8 | # Current folder name 9 | PROJ_BASE=$(basename `pwd`) 10 | 11 | echo "Deploying $PROJ_BASE $PACKAGE_VERSION..." 12 | 13 | cd ../metrics-scala 14 | 15 | SBT_RESOURCES_DIR=$PROJ_BASE/src/main/resources 16 | mkdir -p $SBT_RESOURCES_DIR 17 | 18 | echo "Copying $DIST_BASE files to resources" 19 | cp -r $DIST_DIR $SBT_RESOURCES_DIR/$PROJ_BASE 20 | 21 | sbt "publishSigned; sonaRelease" 22 | -------------------------------------------------------------------------------- /packages/metrics-scala/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mutation-testing-metrics-scala", 3 | "version": "3.5.4", 4 | "description": "Zero-dependency library to calculate mutation testing metrics in Scala.", 5 | "nx": { 6 | "name": "metrics-scala", 7 | "namedInputs": { 8 | "default": [ 9 | "{projectRoot}/**/*", 10 | { 11 | "runtime": "java -version" 12 | } 13 | ] 14 | }, 15 | "targets": { 16 | "test": { 17 | "dependsOn": [ 18 | "report-schema:generate" 19 | ] 20 | } 21 | } 22 | }, 23 | "scripts": { 24 | "test": "sbt \"compile; metrics/test; circe/test; docs/mdoc --check\"", 25 | "publish": "sbt \"publishSigned; sonaRelease;\"", 26 | "get-version": "echo $npm_package_version", 27 | "stryker": "sbt 'project metrics; stryker'" 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/stryker-mutator/mutation-testing-elements.git", 32 | "directory": "packages/metrics-scala" 33 | }, 34 | "license": "Apache-2.0", 35 | "bugs": { 36 | "url": "https://github.com/stryker-mutator/mutation-testing-elements/issues" 37 | }, 38 | "homepage": "https://github.com/stryker-mutator/mutation-testing-elements/tree/master/packages/metrics-scala#readme" 39 | } 40 | -------------------------------------------------------------------------------- /packages/metrics-scala/project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.2 2 | -------------------------------------------------------------------------------- /packages/metrics-scala/project/plugins.sbt: -------------------------------------------------------------------------------- 1 | addSbtPlugin("com.github.sbt" % "sbt-pgp" % "2.3.1") 2 | 3 | addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.7.1") 4 | 5 | addSbtPlugin("org.typelevel" % "sbt-tpolecat" % "0.5.2") 6 | 7 | addSbtPlugin("io.stryker-mutator" % "sbt-stryker4s" % "0.18.0") 8 | 9 | addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.11.0") 10 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.19.0") 11 | -------------------------------------------------------------------------------- /packages/metrics-scala/stryker4s.conf: -------------------------------------------------------------------------------- 1 | stryker4s { 2 | reporters = ["console", "dashboard", "html"], 3 | base-dir = "metrics", 4 | dashboard.module = "metrics-scala" 5 | } 6 | -------------------------------------------------------------------------------- /packages/metrics/.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !README.md 3 | !dist/src/**/* 4 | !src/**/*.ts 5 | tsconfig.tsbuildinfo 6 | -------------------------------------------------------------------------------- /packages/metrics/eslint.config.js: -------------------------------------------------------------------------------- 1 | import mte from 'eslint-config-mte'; 2 | 3 | export default [...mte]; 4 | -------------------------------------------------------------------------------- /packages/metrics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mutation-testing-metrics", 3 | "version": "3.5.1", 4 | "description": "Utility functions to calculate mutation testing metrics.", 5 | "type": "module", 6 | "main": "dist/src/index.js", 7 | "exports": { 8 | ".": "./dist/src/index.js" 9 | }, 10 | "nx": { 11 | "name": "metrics" 12 | }, 13 | "scripts": { 14 | "test": "mocha --node-option enable-source-maps --forbid-only --forbid-pending dist/test/**/*.js", 15 | "stryker": "stryker run", 16 | "lint": "eslint ." 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/stryker-mutator/mutation-testing-elements.git", 21 | "directory": "packages/metrics" 22 | }, 23 | "license": "Apache-2.0", 24 | "bugs": { 25 | "url": "https://github.com/stryker-mutator/mutation-testing-elements/issues" 26 | }, 27 | "homepage": "https://github.com/stryker-mutator/mutation-testing-elements/tree/master/packages/metrics#readme", 28 | "dependencies": { 29 | "mutation-testing-report-schema": "3.5.1" 30 | }, 31 | "devDependencies": { 32 | "eslint-config-mte": "*" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/metrics/src/helpers/file.ts: -------------------------------------------------------------------------------- 1 | import type { FileUnderTestModel } from '../model/file-under-test-model.js'; 2 | import type { Metrics } from '../model/metrics.js'; 3 | import type { MetricsResult } from '../model/metrics-result.js'; 4 | 5 | const SEPARATOR = '/'; 6 | 7 | export function normalizeFileNames(input: Record, projectRoot = ''): Record { 8 | return normalize(input, projectRoot, (input) => input); 9 | } 10 | 11 | export function normalize( 12 | input: Record, 13 | projectRoot: string, 14 | factory: (input: TIn, relativeFileName: string) => TOut, 15 | ): Record { 16 | const fileNames = Object.keys(input); 17 | const commonBasePath = determineCommonBasePath(fileNames); 18 | const output: Record = Object.create(null); 19 | fileNames.forEach((fileName) => { 20 | const relativeFileName = normalizeName(fileName.startsWith(projectRoot) ? fileName.substr(projectRoot.length) : fileName); 21 | output[normalizeName(fileName.substr(commonBasePath.length))] = factory(input[fileName], relativeFileName); 22 | }); 23 | return output; 24 | } 25 | 26 | function normalizeName(fileName: string) { 27 | return fileName.split(/\/|\\/).filter(Boolean).join('/'); 28 | } 29 | 30 | export function determineCommonBasePath(fileNames: readonly string[]): string { 31 | const directories = fileNames.map((fileName) => fileName.split(/\/|\\/).slice(0, -1)); 32 | if (fileNames.length) { 33 | return directories.reduce(filterDirectories).join(SEPARATOR); 34 | } else { 35 | return ''; 36 | } 37 | 38 | function filterDirectories(previousDirectories: string[], currentDirectories: string[]) { 39 | for (let i = 0; i < previousDirectories.length; i++) { 40 | if (previousDirectories[i] !== currentDirectories[i]) { 41 | return previousDirectories.splice(0, i); 42 | } 43 | } 44 | 45 | return previousDirectories; 46 | } 47 | } 48 | 49 | export function compareNames(a: MetricsResult, b: MetricsResult) { 50 | const sortValue = (metricsResult: MetricsResult) => { 51 | // Directories first 52 | if (metricsResult.file) { 53 | return `1${metricsResult.name}`; 54 | } else { 55 | return `0${metricsResult.name}`; 56 | } 57 | }; 58 | return sortValue(a).localeCompare(sortValue(b)); 59 | } 60 | -------------------------------------------------------------------------------- /packages/metrics/src/helpers/group-by.ts: -------------------------------------------------------------------------------- 1 | export function groupBy(arr: T[], criteria: (element: T) => string): Record { 2 | return arr.reduce((acc: Record, item) => { 3 | const key = criteria(item); 4 | if (!Object.prototype.hasOwnProperty.call(acc, key)) { 5 | acc[key] = []; 6 | } 7 | acc[key].push(item); 8 | return acc; 9 | }, {}); 10 | } 11 | -------------------------------------------------------------------------------- /packages/metrics/src/helpers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './file.js'; 2 | export * from './group-by.js'; 3 | export * from './is-not-nullish.js'; 4 | export * from './text.js'; 5 | -------------------------------------------------------------------------------- /packages/metrics/src/helpers/is-not-nullish.ts: -------------------------------------------------------------------------------- 1 | export function isNotNullish(val: T | undefined | null): val is T { 2 | return val !== null && val !== undefined; 3 | } 4 | -------------------------------------------------------------------------------- /packages/metrics/src/helpers/text.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-unsafe-enum-comparison */ 2 | // The implementation of this file is grabbed and modified from TypeScript source code 3 | 4 | const enum CharacterCodes { 5 | maxAsciiCharacter = 0x7f, 6 | lineFeed = 0x0a, // \n 7 | carriageReturn = 0x0d, // \r 8 | lineSeparator = 0x2028, 9 | paragraphSeparator = 0x2029, 10 | } 11 | 12 | function isLineBreak(ch: number): boolean { 13 | // ES5 7.3: 14 | // The ECMAScript line terminator characters are listed in Table 3. 15 | // Table 3: Line Terminator Characters 16 | // Code Unit Value Name Formal Name 17 | // \u000A Line Feed 18 | // \u000D Carriage Return 19 | // \u2028 Line separator 20 | // \u2029 Paragraph separator 21 | // Only the characters in Table 3 are treated as line terminators. Other new line or line 22 | // breaking characters are treated as white space but not as line terminators. 23 | 24 | return ( 25 | ch === CharacterCodes.lineFeed || 26 | ch === CharacterCodes.carriageReturn || 27 | ch === CharacterCodes.lineSeparator || 28 | ch === CharacterCodes.paragraphSeparator 29 | ); 30 | } 31 | 32 | export function computeLineStarts(text: string): number[] { 33 | const result: number[] = []; 34 | let pos = 0; 35 | let lineStart = 0; 36 | function progressLineStart(pos: number) { 37 | result.push(lineStart); 38 | lineStart = pos; 39 | } 40 | // Mutation testing elements works with 1-based lines 41 | progressLineStart(0); 42 | while (pos < text.length) { 43 | const ch = text.charCodeAt(pos); 44 | pos++; 45 | switch (ch) { 46 | case CharacterCodes.carriageReturn: 47 | if (text.charCodeAt(pos) === CharacterCodes.lineFeed) { 48 | pos++; 49 | } 50 | progressLineStart(pos); 51 | break; 52 | case CharacterCodes.lineFeed: 53 | progressLineStart(pos); 54 | break; 55 | default: 56 | if (ch > CharacterCodes.maxAsciiCharacter && isLineBreak(ch)) { 57 | progressLineStart(pos); 58 | } 59 | break; 60 | } 61 | } 62 | result.push(lineStart); 63 | return result; 64 | } 65 | -------------------------------------------------------------------------------- /packages/metrics/src/index.ts: -------------------------------------------------------------------------------- 1 | export { aggregateResultsByModule } from './aggregate.js'; 2 | export { calculateMetrics, calculateMutationTestMetrics } from './calculateMetrics.js'; 3 | export { normalizeFileNames } from './helpers/index.js'; 4 | export type { Metrics, MutationTestMetricsResult, TestMetrics } from './model/index.js'; 5 | export { FileUnderTestModel, MetricsResult, MutantModel, TestFileModel, TestModel, TestStatus } from './model/index.js'; 6 | -------------------------------------------------------------------------------- /packages/metrics/src/model/file-under-test-model.ts: -------------------------------------------------------------------------------- 1 | import type { FileResult, MutantResult } from 'mutation-testing-report-schema'; 2 | 3 | import type { MetricsResult } from './metrics-result.js'; 4 | import { MutantModel } from './mutant-model.js'; 5 | import { SourceFile } from './source-file.js'; 6 | 7 | /** 8 | * Represents a file which was mutated (your production code). 9 | */ 10 | export class FileUnderTestModel extends SourceFile implements FileResult { 11 | /** 12 | * Programming language that is used. Used for code highlighting, see https://prismjs.com/#examples. 13 | */ 14 | language: string; 15 | /** 16 | * Full source code of the mutated file, this is used for highlighting. 17 | */ 18 | source: string; 19 | /** 20 | * The mutants inside this file. 21 | */ 22 | mutants: MutantModel[]; 23 | /** 24 | * The associated MetricsResult of this file. 25 | */ 26 | result?: MetricsResult; 27 | 28 | /** 29 | * @param input The file result content 30 | * @param name The file name 31 | */ 32 | constructor( 33 | input: FileResult, 34 | public name: string, 35 | ) { 36 | super(); 37 | this.language = input.language; 38 | this.source = input.source; 39 | this.mutants = input.mutants.map((mutantResult) => { 40 | const mutant = new MutantModel(mutantResult); 41 | mutant.sourceFile = this; 42 | return mutant; 43 | }); 44 | } 45 | 46 | /** 47 | * Retrieves the lines of code with the mutant applied to it, to be shown in a diff view. 48 | */ 49 | public getMutationLines(mutant: MutantResult): string { 50 | const lineMap = this.getLineMap(); 51 | const start = lineMap[mutant.location.start.line]; 52 | const startOfEndLine = lineMap[mutant.location.end.line]; 53 | const end = lineMap[mutant.location.end.line + 1]; 54 | return `${this.source.substr(start, mutant.location.start.column - 1)}${ 55 | mutant.replacement ?? mutant.description ?? mutant.mutatorName 56 | }${this.source.substring(startOfEndLine + mutant.location.end.column - 1, end)}`; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/metrics/src/model/index.ts: -------------------------------------------------------------------------------- 1 | export { FileUnderTestModel } from './file-under-test-model.js'; 2 | export type { Metrics } from './metrics.js'; 3 | export { MetricsResult } from './metrics-result.js'; 4 | export { MutantModel } from './mutant-model.js'; 5 | export type { MutationTestMetricsResult } from './mutation-test-metrics-result.js'; 6 | export { TestFileModel } from './test-file-model.js'; 7 | export type { TestMetrics } from './test-metrics.js'; 8 | export { TestModel, TestStatus } from './test-model.js'; 9 | -------------------------------------------------------------------------------- /packages/metrics/src/model/mutation-test-metrics-result.ts: -------------------------------------------------------------------------------- 1 | import type { FileUnderTestModel } from './file-under-test-model.js'; 2 | import type { Metrics } from './metrics.js'; 3 | import type { MetricsResult } from './metrics-result.js'; 4 | import type { TestFileModel } from './test-file-model.js'; 5 | import type { TestMetrics } from './test-metrics.js'; 6 | 7 | export interface MutationTestMetricsResult { 8 | systemUnderTestMetrics: MetricsResult; 9 | testMetrics: MetricsResult | undefined; 10 | } 11 | -------------------------------------------------------------------------------- /packages/metrics/src/model/source-file.ts: -------------------------------------------------------------------------------- 1 | import type { OpenEndLocation } from 'mutation-testing-report-schema'; 2 | 3 | import { computeLineStarts } from '../helpers/index.js'; 4 | 5 | export function assertSourceDefined(source: string | undefined): asserts source { 6 | if (source === undefined) { 7 | throw new Error('sourceFile.source is undefined'); 8 | } 9 | } 10 | 11 | export abstract class SourceFile { 12 | public abstract source: string | undefined; 13 | private lineMap?: number[]; 14 | 15 | public getLineMap(): number[] { 16 | assertSourceDefined(this.source); 17 | return this.lineMap ?? (this.lineMap = computeLineStarts(this.source)); 18 | } 19 | 20 | /** 21 | * Retrieves the source lines based on the `start.line` and `end.line` property. 22 | */ 23 | public getLines(location: OpenEndLocation): string { 24 | assertSourceDefined(this.source); 25 | const lineMap = this.getLineMap(); 26 | return this.source.substring(lineMap[location.start.line], lineMap[(location.end ?? location.start).line + 1]); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /packages/metrics/src/model/test-file-model.ts: -------------------------------------------------------------------------------- 1 | import type { TestFile as TestFile } from 'mutation-testing-report-schema'; 2 | 3 | import type { MetricsResult } from './metrics-result.js'; 4 | import { SourceFile } from './source-file.js'; 5 | import type { TestMetrics } from './test-metrics.js'; 6 | import { TestModel } from './test-model.js'; 7 | 8 | /** 9 | * Represents a file that contains tests 10 | */ 11 | export class TestFileModel extends SourceFile implements TestFile { 12 | tests: TestModel[]; 13 | source: string | undefined; 14 | /** 15 | * The associated MetricsResult of this file. 16 | */ 17 | result?: MetricsResult; 18 | 19 | /** 20 | * @param input the test file content 21 | * @param name the file name 22 | */ 23 | constructor( 24 | input: TestFile, 25 | public name: string, 26 | ) { 27 | super(); 28 | this.source = input.source; 29 | this.tests = input.tests.map((testDefinition) => { 30 | const test = new TestModel(testDefinition); 31 | test.sourceFile = this; 32 | return test; 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/metrics/src/model/test-metrics.ts: -------------------------------------------------------------------------------- 1 | export interface TestMetrics { 2 | /** 3 | * The total number of tests 4 | */ 5 | total: number; 6 | /** 7 | * The total number of tests that did end up killing a mutant. 8 | */ 9 | killing: number; 10 | 11 | /** 12 | * The total number of tests that didn't kill any mutants (this includes notCovering). 13 | */ 14 | covering: number; 15 | 16 | /** 17 | * The total number of tests that didn't even cover a single mutant (useless tests?). 18 | */ 19 | notCovering: number; 20 | } 21 | -------------------------------------------------------------------------------- /packages/metrics/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "types": ["node"], 5 | "outDir": "../dist/src" 6 | }, 7 | "references": [ 8 | { 9 | "path": "../../report-schema/src" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/metrics/stryker.conf.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | 3 | /** 4 | * @type {import('@stryker-mutator/api/core').PartialStrykerOptions & typeof import('../../stryker.parent.json')} 5 | */ 6 | const config = JSON.parse(await fs.readFile('../../stryker.parent.json', 'utf-8')); 7 | 8 | config.dashboard = { module: 'metrics' }; 9 | export default config; 10 | -------------------------------------------------------------------------------- /packages/metrics/test/helpers/factories.ts: -------------------------------------------------------------------------------- 1 | import type { FileResult, Location, MutantResult, MutationTestResult, TestDefinition, TestFile } from 'mutation-testing-report-schema'; 2 | 3 | import { FileUnderTestModel, TestFileModel } from '../../src/index.js'; 4 | 5 | export function createTestFileModel(overrides?: Partial): TestFileModel { 6 | return new TestFileModel(createTestFile(overrides), overrides?.name ?? 'foo.spec.js'); 7 | } 8 | 9 | export function createFileUnderTestModel(overrides?: Partial): FileUnderTestModel { 10 | return new FileUnderTestModel(createFileResult(overrides), overrides?.name ?? 'foo.js'); 11 | } 12 | 13 | export function createMutationTestResult(overrides?: Partial): MutationTestResult { 14 | return { 15 | files: {}, 16 | schemaVersion: '1.0', 17 | thresholds: { 18 | high: 80, 19 | low: 60, 20 | }, 21 | ...overrides, 22 | }; 23 | } 24 | 25 | export function createMutantResult(overrides?: Partial): MutantResult { 26 | return { 27 | id: '42', 28 | location: createLocation(), 29 | mutatorName: 'FooMutator', 30 | replacement: '"foo"', 31 | status: 'Killed', 32 | ...overrides, 33 | }; 34 | } 35 | 36 | export function createLocation(overrides?: Partial): Location { 37 | return { 38 | end: { 39 | column: 4, 40 | line: 3, 41 | }, 42 | start: { 43 | column: 2, 44 | line: 1, 45 | }, 46 | ...overrides, 47 | }; 48 | } 49 | 50 | export function createFileResult(overrides?: Partial): FileResult { 51 | return { 52 | language: 'js', 53 | mutants: [createMutantResult()], 54 | source: 'console.log("foo");', 55 | ...overrides, 56 | }; 57 | } 58 | 59 | export function createTestDefinition(overrides?: Partial): TestDefinition { 60 | return { 61 | id: '52', 62 | name: 'foo should be bar', 63 | ...overrides, 64 | }; 65 | } 66 | 67 | export function createTestFile(overrides?: Partial): TestFile { 68 | return { 69 | tests: [createTestDefinition()], 70 | ...overrides, 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /packages/metrics/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "types": ["mocha", "node"], 5 | "outDir": "../dist/test" 6 | }, 7 | "references": [ 8 | { 9 | "path": "../src/tsconfig.json" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/metrics/test/unit/helpers.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | 3 | import { normalizeFileNames } from '../../src/index.js'; 4 | 5 | describe(normalizeFileNames.name, () => { 6 | it('should replace \\ with /', () => { 7 | const input: Record = { 8 | 'src\\components\\foo.js': 4, 9 | 'test\\components\\foo.spec.js': 5, 10 | }; 11 | 12 | const out = normalizeFileNames(input); 13 | 14 | const expected: Record = { 15 | 'src/components/foo.js': 4, 16 | 'test/components/foo.spec.js': 5, 17 | }; 18 | expect(out).deep.eq(expected); 19 | }); 20 | 21 | it('should remove common root path', () => { 22 | const input: Record = { 23 | 'c:\\tmp\\repo\\src\\components\\foo.js': 4, 24 | 'c:\\tmp\\repo\\test\\components\\foo.spec.js': 5, 25 | }; 26 | 27 | const out = normalizeFileNames(input); 28 | 29 | const expected: Record = { 30 | 'src/components/foo.js': 4, 31 | 'test/components/foo.spec.js': 5, 32 | }; 33 | expect(out).deep.eq(expected); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/metrics/test/unit/model/test-file-model.spec.ts: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | import type { TestFile } from 'mutation-testing-report-schema'; 3 | 4 | import { TestFileModel, TestModel } from '../../../src/index.js'; 5 | import { createLocation, createTestDefinition, createTestFile } from '../../helpers/factories.js'; 6 | 7 | describe(TestFileModel.name, () => { 8 | it('should copy over all values from file result', () => { 9 | const fileResult: Required = { 10 | source: 'describe("foo")', 11 | tests: [], 12 | }; 13 | expect(new TestFileModel(fileResult, '')).deep.contains(fileResult); 14 | }); 15 | 16 | it('should set the file name', () => { 17 | const sut = new TestFileModel(createTestFile(), 'bar.spec.js'); 18 | expect(sut.name).deep.eq('bar.spec.js'); 19 | }); 20 | 21 | it('should create test model instances', () => { 22 | const testFile = createTestFile({ 23 | tests: [createTestDefinition({ id: 'test-1' })], 24 | }); 25 | const actual = new TestFileModel(testFile, ''); 26 | expect(actual.tests[0]).instanceOf(TestModel); 27 | }); 28 | 29 | describe(TestFileModel.prototype.getLines.name, () => { 30 | // Implementation of `getLines(location)` is tested in file-under-test-model.spec.ts 31 | 32 | it('should throw when the source is undefined', () => { 33 | const sut = new TestFileModel(createTestFile({ source: undefined }), ''); 34 | expect(() => sut.getLines(createLocation())).throws('sourceFile.source is undefined'); 35 | }); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /packages/metrics/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./src/tsconfig.json" 6 | }, 7 | { 8 | "path": "./test/tsconfig.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/real-time/.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !README.md 3 | !dist/src/**/* 4 | !src/**/*.ts 5 | tsconfig.tsbuildinfo 6 | -------------------------------------------------------------------------------- /packages/real-time/README.md: -------------------------------------------------------------------------------- 1 | [![Mutation testing badge](https://img.shields.io/endpoint?style=flat&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fstryker-mutator%2Fmutation-testing-elements%2Fmaster%3Fmodule%3Dreal-time)](https://badge-api.stryker-mutator.io/github.com/stryker-mutator/mutation-testing-elements/master?module=real-time) 2 | [![Build Status](https://github.com/stryker-mutator/mutation-testing-elements/workflows/CI/badge.svg)](https://github.com/stryker-mutator/mutation-testing-elements/actions?query=workflow%3ACI+branch%3Amaster) 3 | 4 | # Mutation testing Real time 5 | 6 | A NodeJS helper package to help with the server side of real-time reporting. 7 | 8 | ## Usage example 9 | 10 | ```ts 11 | import { createServer } from 'http'; 12 | import { RealTimeReporter } from 'mutation-testing-real-time'; 13 | 14 | const reporter = new RealTimeReporter({ accessControlAllowOrigin: '*' }); 15 | const server = new createServer((req, res) => { 16 | if (req.url === '/sse') { 17 | reporter.add(res); 18 | } 19 | }); 20 | 21 | // Whenever a mutant result comes in: 22 | reporter.sendMutantTested({ id: '1', status: 'Killed' }); 23 | // ... 24 | 25 | // Whenever we are done: 26 | reporter.sendFinished(); 27 | ``` 28 | 29 | ## API Reference 30 | 31 | ### `RealTimeReporter` 32 | 33 | #### `RealTimeReporter.prototype.sendMutantTested` [`(Partial) => void`] 34 | 35 | #### `RealTimeReporter.prototype.sendFinished` [`() => void`] 36 | 37 | #### `Event: 'client-connected'` 38 | 39 | - `client: MutationEventSender` 40 | 41 | Emitted each time a client connects to this real time reporter. 42 | 43 | #### `Event: 'client-disconnected'` 44 | 45 | - `client: MutationEventSender` 46 | 47 | Emitted each time a client disconnects from this real time reporter. 48 | -------------------------------------------------------------------------------- /packages/real-time/eslint.config.js: -------------------------------------------------------------------------------- 1 | import mte from 'eslint-config-mte'; 2 | 3 | export default [...mte]; 4 | -------------------------------------------------------------------------------- /packages/real-time/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mutation-testing-real-time", 3 | "version": "3.5.3", 4 | "description": "Helpers to create a NodeJS server for real-time reporting for a mutation testing elements report", 5 | "module": "dist/src/index.js", 6 | "exports": { 7 | ".": "./dist/src/index.js" 8 | }, 9 | "type": "module", 10 | "nx": { 11 | "name": "real-time" 12 | }, 13 | "keywords": [ 14 | "mutation", 15 | "testing", 16 | "real-time", 17 | "middleware" 18 | ], 19 | "scripts": { 20 | "test": "mocha --node-option enable-source-maps --forbid-only --forbid-pending dist/test/**/*.js", 21 | "lint": "eslint .", 22 | "stryker": "stryker run" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "git+https://github.com/stryker-mutator/mutation-testing-elements.git", 27 | "directory": "packages/real-time" 28 | }, 29 | "homepage": "https://github.com/stryker-mutator/mutation-testing-elements/tree/master/packages/real-time#readme", 30 | "devDependencies": { 31 | "eventsource": "4.0.0", 32 | "rxjs": "7.8.2" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /packages/real-time/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './mutation-event-sender.js'; 2 | export * from './real-time-options.js'; 3 | export * from './real-time-reporter.js'; 4 | -------------------------------------------------------------------------------- /packages/real-time/src/mutation-event-sender.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'node:events'; 2 | 3 | import type { ServerResponse } from 'http'; 4 | import type { MutantResult } from 'mutation-testing-report-schema'; 5 | 6 | import type { RealTimeOptions } from './real-time-options.js'; 7 | 8 | export class MutationEventSender extends EventEmitter { 9 | #response: ServerResponse; 10 | 11 | constructor(res: ServerResponse, onFinish: () => void, { accessControlAllowOrigin }: RealTimeOptions = {}) { 12 | super(); 13 | 14 | this.#response = res; 15 | const headers: Record = { 16 | 'Content-Type': 'text/event-stream', 17 | 'Cache-Control': 'no-cache', 18 | Connection: 'keep-alive', 19 | }; 20 | if (accessControlAllowOrigin) { 21 | headers['Access-Control-Allow-Origin'] = accessControlAllowOrigin; 22 | } 23 | this.#response.writeHead(200, headers); 24 | this.#response.flushHeaders(); 25 | this.#response.on('close', onFinish); 26 | this.#response.on('error', onFinish); 27 | } 28 | 29 | public sendMutantTested(mutant: Partial): void { 30 | this.#send('mutant-tested', mutant); 31 | } 32 | 33 | public sendFinished(): void { 34 | this.#send('finished', {}); 35 | } 36 | 37 | #send(event: string, payload: T): void { 38 | this.#response.write(`event: ${event}\n`); 39 | this.#response.write(`data: ${JSON.stringify(payload)}\n\n`); 40 | // Flush the response buffer to ensure the client receives the event immediately 41 | this.#response.flush?.(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/real-time/src/real-time-options.ts: -------------------------------------------------------------------------------- 1 | export interface RealTimeOptions { 2 | accessControlAllowOrigin?: string; 3 | } 4 | -------------------------------------------------------------------------------- /packages/real-time/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "types": ["node"], 5 | "outDir": "../dist/src" 6 | }, 7 | "references": [ 8 | { 9 | "path": "../../report-schema/src" 10 | } 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /packages/real-time/src/types.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'http' { 2 | interface ServerResponse { 3 | /** 4 | * Forces the partially-compressed response to be flushed to the client. 5 | * 6 | * Only available when using compression. 7 | */ 8 | flush?: () => void; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/real-time/stryker.conf.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs/promises'; 2 | 3 | /** 4 | * @type {import('@stryker-mutator/api/core').PartialStrykerOptions & typeof import('../../stryker.parent.json')} 5 | */ 6 | const config = JSON.parse(await fs.readFile('../../stryker.parent.json', 'utf-8')); 7 | 8 | config.dashboard = { module: 'real-time' }; 9 | export default config; 10 | -------------------------------------------------------------------------------- /packages/real-time/test/integration/mutant-event-source.ts: -------------------------------------------------------------------------------- 1 | import { EventSource } from 'eventsource'; 2 | import type { MutantResult } from 'mutation-testing-report-schema/api'; 3 | import { fromEvent, ReplaySubject, shareReplay, takeUntil } from 'rxjs'; 4 | 5 | export class MutantEventSource extends EventSource { 6 | constructor(port: number) { 7 | super(`http://localhost:${port}`); 8 | } 9 | 10 | #closedSubject = new ReplaySubject(); 11 | mutantResult$ = fromEvent(this, 'mutant-tested', (event: { data: string }) => JSON.parse(event.data) as Partial).pipe( 12 | takeUntil(this.#closedSubject), 13 | shareReplay(), 14 | ); 15 | 16 | override close() { 17 | super.close(); 18 | this.#closedSubject.next(); 19 | this.#closedSubject.complete(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/real-time/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "types": ["mocha", "node", "../src/types.d.ts"], 5 | "outDir": "../dist/test" 6 | }, 7 | "references": [{ "path": "../src" }] 8 | } 9 | -------------------------------------------------------------------------------- /packages/real-time/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./src/tsconfig.json" 6 | }, 7 | { 8 | "path": "./test/tsconfig.json" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /packages/report-schema/.npmignore: -------------------------------------------------------------------------------- 1 | **/* 2 | !README.md 3 | !dist/{src,src-generated}/**/* 4 | !{src,src-generated}/**/*.{ts,json} 5 | tsconfig.tsbuildinfo 6 | -------------------------------------------------------------------------------- /packages/report-schema/README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/stryker-mutator/mutation-testing-elements/workflows/CI/badge.svg)](https://github.com/stryker-mutator/mutation-testing-elements/actions?query=workflow%3ACI+branch%3Amaster) 2 | 3 | # Mutation testing elements schema 4 | 5 | The json schema for mutation testing elements. 6 | 7 | The schema can be found [here](https://github.com/stryker-mutator/mutation-testing-elements/blob/master/packages/report-schema/src/mutation-testing-report-schema.json). 8 | 9 | ## JavaScript 10 | 11 | Install it with npm like this: 12 | 13 | ```sh 14 | $ npm i mutation-testing-report-schema 15 | ``` 16 | 17 | Use it from typescript or js (with babel) 18 | 19 | ```ts 20 | import { schema } from 'mutation-testing-elements-schema'; 21 | ``` 22 | 23 | For example, use [AJV](https://github.com/epoberezkin/ajv#ajv-another-json-schema-validator): 24 | 25 | ```ts 26 | const schemaValidator = new Ajv(); 27 | schemaValidator.addSchema(schema, 'mutation-testing-report-schema'); 28 | schemaValidator.validate('mutation-testing-report-schema', { 29 | /*.. report as json ...*/ 30 | }); 31 | ``` 32 | 33 | ## Other languages 34 | 35 | Download the schema using https://www.unpkg.com/mutation-testing-report-schema@VERSION/dist/src/mutation-testing-report-schema.json 36 | 37 | For the current version, see package.json. 38 | -------------------------------------------------------------------------------- /packages/report-schema/eslint.config.js: -------------------------------------------------------------------------------- 1 | import mte from 'eslint-config-mte'; 2 | 3 | export default [...mte]; 4 | -------------------------------------------------------------------------------- /packages/report-schema/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mutation-testing-report-schema", 3 | "version": "3.5.1", 4 | "description": "The json schema for a mutation testing report.", 5 | "main": "dist/src/index.js", 6 | "type": "module", 7 | "typings": "dist/src/index.d.ts", 8 | "exports": { 9 | ".": "./dist/src/index.js", 10 | "./api": "./dist/src-generated/schema.js", 11 | "./mutation-testing-report-schema.json": "./dist/src/mutation-testing-report-schema.json" 12 | }, 13 | "typesVersions": { 14 | "*": { 15 | "api": [ 16 | "dist/src-generated/schema.d.ts" 17 | ] 18 | } 19 | }, 20 | "nx": { 21 | "name": "report-schema" 22 | }, 23 | "scripts": { 24 | "test": "mocha --node-option \"enable-source-maps\" --forbid-only --forbid-pending dist/test/**/*.js", 25 | "postpublish": "PUBLISH_SCHEMA=true ../metrics-scala/npmProjPublish.sh", 26 | "generate": "node ../../tasks/schema2ts.mjs src/mutation-testing-report-schema.json src-generated/schema.ts", 27 | "lint": "eslint ." 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+https://github.com/stryker-mutator/mutation-testing-elements.git", 32 | "directory": "packages/report-schema" 33 | }, 34 | "license": "Apache-2.0", 35 | "bugs": { 36 | "url": "https://github.com/stryker-mutator/mutation-testing-elements/issues" 37 | }, 38 | "homepage": "https://github.com/stryker-mutator/mutation-testing-elements/tree/master/packages/elements#readme", 39 | "devDependencies": { 40 | "ajv": "8.17.1", 41 | "eslint-config-mte": "*" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /packages/report-schema/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from '../src-generated/schema.js'; 2 | import fs from 'fs'; 3 | 4 | // eslint-disable-next-line @typescript-eslint/consistent-type-imports 5 | type SchemaType = typeof import('./mutation-testing-report-schema.json'); 6 | 7 | export const schema: SchemaType = JSON.parse(fs.readFileSync(new URL('./mutation-testing-report-schema.json', import.meta.url), 'utf-8')); 8 | -------------------------------------------------------------------------------- /packages/report-schema/src/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "types": ["node"], 5 | "resolveJsonModule": true, 6 | "outDir": "../dist", 7 | "rootDir": "../" 8 | }, 9 | "include": ["../src/**/*", "../src/**/*.json", "../src-generated/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /packages/report-schema/test/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.settings.json", 3 | "compilerOptions": { 4 | "types": ["mocha", "node"], 5 | "outDir": "../dist/test", 6 | "resolveJsonModule": true 7 | }, 8 | "references": [ 9 | { 10 | "path": "../src/tsconfig.json" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/additional-properties-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "foo": "bar", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60, 7 | "bar": "baz" 8 | }, 9 | "files": { 10 | "test.js": { 11 | "baz": "qux", 12 | "language": "javascript", 13 | "source": "function add(a, b) {\n return a + b;\n}", 14 | "mutants": [ 15 | { 16 | "qux": "foo", 17 | "id": "1", 18 | "mutatorName": "Arithmetic Operator", 19 | "replacement": "-", 20 | "description": "Replaced + with -", 21 | "location": { 22 | "foo": "bar", 23 | "start": { 24 | "bar": "baz", 25 | "line": 2, 26 | "column": 12 27 | }, 28 | "end": { 29 | "baz": "qux", 30 | "line": 2, 31 | "column": 13 32 | } 33 | }, 34 | "status": "Survived" 35 | }, 36 | { 37 | "id": "2", 38 | "mutatorName": "Block Statement", 39 | "replacement": "{}", 40 | "description": "Removed block statement", 41 | "location": { 42 | "start": { 43 | "line": 1, 44 | "column": 20 45 | }, 46 | "end": { 47 | "line": 3, 48 | "column": 1 49 | } 50 | }, 51 | "status": "Killed" 52 | } 53 | ] 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/data-url.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "framework": { 9 | "name": "Stryker", 10 | "branding": { 11 | "homepageUrl": "https://stryker-mutator.io", 12 | "imageUrl": "data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E" 13 | } 14 | }, 15 | "testFiles": { 16 | "test/foo.spec.js": { 17 | "tests": [ 18 | { 19 | "id": "test-1", 20 | "name": "foo should be bar", 21 | "location": { 22 | "start": { 23 | "line": 4, 24 | "column": 3 25 | } 26 | } 27 | } 28 | ] 29 | } 30 | }, 31 | "files": {} 32 | } 33 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-end-location.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "testFiles": { 9 | "test/foo.spec.js": { 10 | "tests": [ 11 | { 12 | "id": "test-1", 13 | "name": "foo should be bar", 14 | "location": { 15 | "start": { 16 | "line": 4, 17 | "column": 3 18 | } 19 | } 20 | } 21 | ] 22 | } 23 | }, 24 | "files": {} 25 | } 26 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-framework-name.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "framework": { 9 | "homepageUrl": "https://stryker-mutator.io", 10 | "imageUrl": "https://stryker-mutator.io/img/stryker.svg", 11 | "dependencies": { 12 | "@stryker-mutator/jest-runner": "1.0.2", 13 | "nodejs": "12" 14 | } 15 | }, 16 | "files": {} 17 | } 18 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-mutant-location-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "thresholds": { 4 | "high": 80, 5 | "low": 60 6 | }, 7 | "files": { 8 | "test.js": { 9 | "language": "javascript", 10 | "source": "function add(a, b) {\n return a + b;\n}", 11 | "mutants": [ 12 | { 13 | "id": "2", 14 | "mutatorName": "Block Statement", 15 | "replacement": "{}", 16 | "location": { 17 | "start": { 18 | "line": 1, 19 | "column": 20 20 | } 21 | }, 22 | "status": "Killed" 23 | } 24 | ] 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-performance-fields.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "performance": {}, 9 | "files": {} 10 | } 11 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-replacement-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "thresholds": { 4 | "high": 80, 5 | "low": 60 6 | }, 7 | "files": { 8 | "test.js": { 9 | "language": "javascript", 10 | "source": "function add(a, b) {\n return a + b;\n}", 11 | "mutants": [ 12 | { 13 | "id": "2", 14 | "mutatorName": "Block Statement", 15 | "location": { 16 | "start": { 17 | "line": 1, 18 | "column": 1 19 | }, 20 | "end": { 21 | "line": 1, 22 | "column": 4 23 | } 24 | }, 25 | "status": "Killed" 26 | } 27 | ] 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-system-ci.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "system": {}, 9 | "files": {} 10 | } 11 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-system-cpu-logical-cores.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "system": { 9 | "ci": true, 10 | "cpu": {} 11 | }, 12 | "files": {} 13 | } 14 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-system-os-platform.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "system": { 9 | "ci": true, 10 | "os": {} 11 | }, 12 | "files": {} 13 | } 14 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-system-ram-total.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "system": { 9 | "ci": true, 10 | "ram": {} 11 | }, 12 | "files": {} 13 | } 14 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-test-files.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "files": {} 9 | } 10 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-test-name.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "testFiles": { 9 | "test/foo.spec.js": { 10 | "tests": [ 11 | { 12 | "id": "test-1" 13 | } 14 | ] 15 | } 16 | }, 17 | "files": {} 18 | } 19 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-tests.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../src/mutation-testing-report-schema.json", 3 | "schemaVersion": "1.0", 4 | "thresholds": { 5 | "high": 80, 6 | "low": 60 7 | }, 8 | "testFiles": { 9 | "test/foo.spec.js": {} 10 | }, 11 | "files": {} 12 | } 13 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/missing-version-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "thresholds": { 3 | "high": 80, 4 | "low": 60 5 | }, 6 | "files": { 7 | "test.js": { 8 | "language": "javascript", 9 | "source": "function add(a, b) {\n return a + b;\n}", 10 | "mutants": [ 11 | { 12 | "id": "1", 13 | "mutatorName": "Arithmetic Operator", 14 | "replacement": "-", 15 | "location": { 16 | "start": { 17 | "line": 2, 18 | "column": 12 19 | }, 20 | "end": { 21 | "line": 2, 22 | "column": 13 23 | } 24 | }, 25 | "status": "Survived" 26 | }, 27 | { 28 | "id": "2", 29 | "mutatorName": "Block Statement", 30 | "replacement": "{}", 31 | "location": { 32 | "start": { 33 | "line": 1, 34 | "column": 20 35 | }, 36 | "end": { 37 | "line": 3, 38 | "column": 1 39 | } 40 | }, 41 | "status": "Killed" 42 | } 43 | ] 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/thresholds/threshold-too-high-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "thresholds": { 4 | "high": 101, 5 | "low": 20 6 | }, 7 | "files": { 8 | "test.js": { 9 | "language": "javascript", 10 | "source": "function add(a, b) {\n return a + b;\n}", 11 | "mutants": [ 12 | { 13 | "id": "1", 14 | "mutatorName": "Arithmetic Operator", 15 | "replacement": "-", 16 | "location": { 17 | "start": { 18 | "line": 2, 19 | "column": 12 20 | }, 21 | "end": { 22 | "line": 2, 23 | "column": 13 24 | } 25 | }, 26 | "status": "Survived" 27 | }, 28 | { 29 | "id": "2", 30 | "mutatorName": "Block Statement", 31 | "replacement": "{}", 32 | "location": { 33 | "start": { 34 | "line": 1, 35 | "column": 20 36 | }, 37 | "end": { 38 | "line": 3, 39 | "column": 1 40 | } 41 | }, 42 | "status": "Killed" 43 | } 44 | ] 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/report-schema/testResources/thresholds/threshold-too-low-report.json: -------------------------------------------------------------------------------- 1 | { 2 | "schemaVersion": "1.0", 3 | "thresholds": { 4 | "high": 80, 5 | "low": -1 6 | }, 7 | "files": { 8 | "test.js": { 9 | "language": "javascript", 10 | "source": "function add(a, b) {\n return a + b;\n}", 11 | "mutants": [ 12 | { 13 | "id": "1", 14 | "mutatorName": "Arithmetic Operator", 15 | "replacement": "-", 16 | "location": { 17 | "start": { 18 | "line": 2, 19 | "column": 12 20 | }, 21 | "end": { 22 | "line": 2, 23 | "column": 13 24 | } 25 | }, 26 | "status": "Survived" 27 | }, 28 | { 29 | "id": "2", 30 | "mutatorName": "Block Statement", 31 | "replacement": "{}", 32 | "location": { 33 | "start": { 34 | "line": 1, 35 | "column": 20 36 | }, 37 | "end": { 38 | "line": 3, 39 | "column": 1 40 | } 41 | }, 42 | "status": "Killed" 43 | } 44 | ] 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/report-schema/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { 5 | "path": "./src" 6 | }, 7 | { 8 | "path": "./test" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /stryker.parent.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@stryker-mutator/core/schema/stryker-schema.json", 3 | "packageManager": "npm", 4 | "reporters": ["html", "clear-text", "progress", "dashboard"], 5 | "testRunner": "mocha", 6 | "buildCommand": "tsc -b", 7 | "coverageAnalysis": "perTest", 8 | "checkers": ["typescript"], 9 | "mochaOptions": { 10 | "spec": ["dist/test/unit/**/*.js"] 11 | }, 12 | "concurrency": 2 13 | } 14 | -------------------------------------------------------------------------------- /tasks/download-incremental-reports.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for package in elements metrics real-time 4 | do 5 | if [ "$BRANCH_NAME" ] 6 | then 7 | echo "Downloading $package/$BRANCH_NAME..." 8 | curl -s --dump-header .header.out --create-dirs -o packages/$package/reports/stryker-incremental.json https://dashboard.stryker-mutator.io/api/reports/github.com/stryker-mutator/mutation-testing-elements/$BRANCH_NAME?module=$package 9 | if cat .header.out | grep HTTP | grep 404 10 | then 11 | echo "- falling back to $package/master..." 12 | curl -s --dump-header .header.out --create-dirs -o packages/$package/reports/stryker-incremental.json https://dashboard.stryker-mutator.io/api/reports/github.com/stryker-mutator/mutation-testing-elements/master?module=$package 13 | fi 14 | rm .header.out 15 | else 16 | echo "Downloading $package/master..." 17 | curl -s --create-dirs -o packages/$package/reports/stryker-incremental.json https://dashboard.stryker-mutator.io/api/reports/github.com/stryker-mutator/mutation-testing-elements/master?module=$package 18 | fi 19 | done 20 | echo 'Done ✅' 21 | -------------------------------------------------------------------------------- /tasks/schema2ts.mjs: -------------------------------------------------------------------------------- 1 | import { resolve, dirname, relative } from 'path'; 2 | import { promises as fs } from 'fs'; 3 | import { compileFromFile } from 'json-schema-to-typescript'; 4 | import { fileURLToPath } from 'url'; 5 | 6 | const [, , src, target] = process.argv.filter((arg) => !arg.startsWith('-')); 7 | 8 | if (!target) { 9 | const fileName = fileURLToPath(import.meta.url); 10 | throw new Error(`Usage node ${relative(process.cwd(), fileName)} schema.json out.ts`); 11 | } 12 | 13 | const ts = await compileFromFile(resolve(src), { 14 | additionalProperties: false, 15 | }); 16 | await fs.mkdir(dirname(target), { recursive: true }); 17 | await fs.writeFile(target, ts, 'utf-8'); 18 | console.log(`✅ ${src} -> ${target}`); 19 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "extends": "./tsconfig.settings.json", 4 | "references": [ 5 | { 6 | "path": "packages/elements" 7 | }, 8 | { 9 | "path": "packages/report-schema" 10 | }, 11 | { 12 | "path": "packages/metrics" 13 | }, 14 | { 15 | "path": "packages/real-time" 16 | }, 17 | { 18 | "path": "./tsconfig.node.json" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.settings.json", 3 | "compilerOptions": { 4 | "types": ["node"], 5 | "noEmit": true, 6 | "checkJs": true, 7 | "resolveJsonModule": true 8 | }, 9 | "include": [ 10 | "packages/elements/test/integration/lib/SseServer.ts", 11 | "packages/*/*.config.ts", 12 | "packages/*/*.config.js", 13 | "packages/*/*.conf.js", 14 | "stryker.parent.json" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "node16", 5 | "moduleResolution": "node16", 6 | "lib": ["es2022"], 7 | "strict": true, 8 | "composite": true, 9 | "declaration": true, 10 | "declarationMap": true, 11 | "isolatedModules": true, 12 | "esModuleInterop": true, 13 | "noImplicitReturns": true, 14 | "noFallthroughCasesInSwitch": true, 15 | "forceConsistentCasingInFileNames": true, 16 | "experimentalDecorators": true, 17 | "sourceMap": true, 18 | "skipLibCheck": true 19 | } 20 | } 21 | --------------------------------------------------------------------------------