Repository: freeipa/freeipa-webui
Pull request #687: Properly integrates prettier into eslint and updates cypress
Author: login: duzda
Labels: name: ack
description: Pull Request approved, it can be merged
Created: 2025-03-28T09:03:12Z | Merged: 2025-04-03T14:09:17Z

Also removes editorconfig.
Sync across tsconfig, eslint and prettier.
Edits pipeline as we no longer need prettier separate.

Removes unused files.
Updates code to use ESM import instead of requires.
Enforces the usage of TypeScript.
Removes type-ignores and other dirty hacks.

Requires #686 to be merged.

## Summary by Sourcery

Modernize project configuration and testing infrastructure by migrating from Jest to Vitest, integrating Prettier with ESLint, and updating TypeScript and Node.js configuration

New Features:
- Added support for Node.js version 18 and above

Enhancements:
- Migrated from Jest to Vitest for unit testing
- Integrated Prettier configuration with ESLint
- Updated TypeScript configuration to be more strict
- Removed separate Prettier and EditorConfig configurations

Build:
- Updated package.json scripts to use Vitest
- Removed Jest-related dependencies
- Added Vitest and related dependencies

CI:
- Removed Prettier check from GitHub Actions workflow
- Updated test command in CI pipeline

Tests:
- Updated test files to use Vitest mocking syntax
- Added cleanup after each test
- Removed type-ignore comments in test files

Chores:
- Removed unused configuration files
- Updated README to recommend using nvm
- Updated PatternFly version reference


Diff:
diff --git a/.editorconfig b/.editorconfig
deleted file mode 100644
index a5f058a0a..000000000
--- a/.editorconfig
+++ /dev/null
@@ -1,23 +0,0 @@
-# https://editorconfig.org
-root = true
-
-[*]
-charset = utf-8
-end_of_line = lf
-indent_size = 2
-indent_style = space
-insert_final_newline = true
-max_line_length = 80
-trim_trailing_whitespace = true
-
-[*.md]
-max_line_length = 0
-trim_trailing_whitespace = false
-
-[Makefile]
-indent_style = tab
-indent_size = unset
-trim_trailing_whitespace = unset
-
-[COMMIT_EDITMSG]
-max_line_length = 80
diff --git a/.eslintignore b/.eslintignore
index ac3c1198c..02483a249 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -1,3 +1,3 @@
 node_modules/*
 dist/*
-vite.config.ts
+coverage/*
diff --git a/.eslintrc b/.eslintrc
index a4ef4e948..3c19cc98e 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -10,17 +10,26 @@
     "jsx": true,
     "js": true,
     "useJSXTextNode": true,
-    "project": ["./tsconfig.json", "./cypress/tsconfig.json"],
+    "project": ["./tsconfig.json"],
     "tsconfigRootDir": "."
   },
   "extends": [
     "eslint:recommended",
     "plugin:react/recommended",
-    "plugin:@typescript-eslint/recommended"
+    "plugin:@typescript-eslint/recommended",
+    "plugin:prettier/recommended"
+  ],
+  "plugins": [
+    "@typescript-eslint",
+    "react-hooks",
+    "eslint-plugin-react-hooks",
+    "prettier"
   ],
-  "plugins": ["@typescript-eslint", "react-hooks", "eslint-plugin-react-hooks"],
   "env": {
     "browser": true,
     "node": true
+  },
+  "rules": {
+    "prettier/prettier": "error"
   }
 }
diff --git a/.prettierignore b/.prettierignore
index 4b7e16f97..02483a249 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -1,3 +1,3 @@
-# Ignore artifacts:
-dist
-coverage
+node_modules/*
+dist/*
+coverage/*
diff --git a/cypress/e2e/example.cy.ts b/cypress/e2e/example.cy.ts
deleted file mode 100644
index ef868f0bb..000000000
--- a/cypress/e2e/example.cy.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-describe("example", () => {
-  it("passes", () => {
-    cy.visit("/");
-  });
-});
diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json
deleted file mode 100644
index 02e425437..000000000
--- a/cypress/fixtures/example.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
-  "name": "Using fixtures to represent data",
-  "email": "hello@cypress.io",
-  "body": "Fixtures are a great way to mock data for responses to routes"
-}
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
deleted file mode 100644
index d07885355..000000000
--- a/cypress/plugins/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-const cucumber = require("cypress-cucumber-preprocessor").default;
-const { verifyDownloadTasks } = require("cy-verify-downloads");
-
-module.exports = (on) => {
-  on("file:preprocessor", cucumber());
-  on("task", verifyDownloadTasks);
-};
diff --git a/cypress/plugins/index.ts b/cypress/plugins/index.ts
new file mode 100644
index 000000000..ceaf621fe
--- /dev/null
+++ b/cypress/plugins/index.ts
@@ -0,0 +1,30 @@
+import { verifyDownloadTasks } from "cy-verify-downloads";
+import { defineConfig } from "cypress";
+import createBundler from "@bahmutov/cypress-esbuild-preprocessor";
+import { addCucumberPreprocessorPlugin } from "@badeball/cypress-cucumber-preprocessor";
+import { createEsbuildPlugin } from "@badeball/cypress-cucumber-preprocessor/esbuild";
+
+export default defineConfig({
+  e2e: {
+    specPattern: "**/*.feature",
+    async setupNodeEvents(
+      on: Cypress.PluginEvents,
+      config: Cypress.PluginConfigOptions
+    ): Promise<Cypress.PluginConfigOptions> {
+      // This is required for the preprocessor to be able to generate JSON reports after each run, and more,
+      await addCucumberPreprocessorPlugin(on, config);
+
+      on(
+        "file:preprocessor",
+        createBundler({
+          plugins: [createEsbuildPlugin(config)],
+        })
+      );
+
+      on("task", verifyDownloadTasks);
+
+      // Make sure to return the config object as it might have been modified by the plugin.
+      return config;
+    },
+  },
+});
diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts
index 9c07f9ad4..4ad78d7ee 100644
--- a/cypress/support/commands.ts
+++ b/cypress/support/commands.ts
@@ -1,5 +1,3 @@
-/* eslint-disable @typescript-eslint/no-namespace */
-/// <reference types="cypress" />
 // ***********************************************
 // This example commands.ts shows you how to
 // create various custom commands and overwrite
@@ -26,19 +24,10 @@
 // -- This will overwrite an existing command --
 // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
 //
-export {}; // needed for modification of global scope to work properly
 
-require("cy-verify-downloads").addCustomCommand();
+import { addCustomCommand } from "cy-verify-downloads";
 
-declare global {
-  namespace Cypress {
-    interface Chainable {
-      createTestUser(username: string): Chainable<void>;
-      loginAsAnUser(username: string, password: string): Chainable<void>;
-      userCleanup(): Chainable<void>;
-    }
-  }
-}
+addCustomCommand();
 
 Cypress.Commands.add("loginAsAnUser", (username: string, password: string) => {
   // temporary solution as the new UI doesn't have login page yet
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
index 1bcdaa616..f6044d698 100644
--- a/cypress/support/e2e.ts
+++ b/cypress/support/e2e.ts
@@ -15,8 +15,9 @@
 
 // Import commands.js using ES2015 syntax:
 import "./commands";
+import { addCustomCommand } from "cy-verify-downloads";
 
-require("cy-verify-downloads").addCustomCommand();
+addCustomCommand();
 
 before(() => {
   Cypress.session.clearCurrentSessionData().then();
diff --git a/cypress/support/index.d.ts b/cypress/support/index.d.ts
new file mode 100644
index 000000000..6bc7f33fa
--- /dev/null
+++ b/cypress/support/index.d.ts
@@ -0,0 +1,11 @@
+/// <reference types="cypress" />
+
+// eslint-disable-next-line @typescript-eslint/no-namespace
+declare namespace Cypress {
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  interface Chainable<Subject> {
+    createTestUser(username: string): Chainable<void>;
+    loginAsAnUser(username: string, password: string): Chainable<void>;
+    userCleanup(): Chainable<void>;
+  }
+}
diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json
deleted file mode 100644
index 1472aef23..000000000
--- a/cypress/tsconfig.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-  "extends": "../tsconfig.json",
-  "compilerOptions": {
-    "noEmit": true,
-    // be explicit about types included
-    "types": ["cypress", "cy-verify-downloads"],
-    "paths": {
-      "@badeball/cypress-cucumber-preprocessor/*": [
-        "./node_modules/@badeball/cypress-cucumber-preprocessor/dist/subpath-entrypoints/*"
-      ]
-    },
-    "isolatedModules": false
-  },
-  "include": [
-    "../node_modules/cypress",
-    "./**/*.ts",
-    "./cypress.config.ts",
-    "./cypress"
-  ],
-  "exclude": [],
-  "files": ["cypress.config.ts"]
-}
diff --git a/package-lock.json b/package-lock.json
index 4e4d1a0eb..d4fc0e77d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -39,7 +39,7 @@
         "esbuild-plugin-polyfill-node": "^0.3.0",
         "eslint": "^8.12.0",
         "eslint-config-prettier": "^8.5.0",
-        "eslint-plugin-prettier": "^4.0.0",
+        "eslint-plugin-prettier": "^4.2.1",
         "eslint-plugin-react": "^7.29.4",
         "eslint-plugin-react-hooks": "^4.6.0",
         "prettier": "^2.6.1",
@@ -5099,6 +5099,7 @@
       "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz",
       "integrity": "sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==",
       "dev": true,
+      "license": "MIT",
       "dependencies": {
         "prettier-linter-helpers": "^1.0.0"
       },
diff --git a/package.json b/package.json
index f8e7f115e..243d14cd8 100644
--- a/package.json
+++ b/package.json
@@ -53,7 +53,7 @@
     "esbuild-plugin-polyfill-node": "^0.3.0",
     "eslint": "^8.12.0",
     "eslint-config-prettier": "^8.5.0",
-    "eslint-plugin-prettier": "^4.0.0",
+    "eslint-plugin-prettier": "^4.2.1",
     "eslint-plugin-react": "^7.29.4",
     "eslint-plugin-react-hooks": "^4.6.0",
     "prettier": "^2.6.1",
diff --git a/tests/features/steps/common.ts b/tests/features/steps/common.ts
index 55447ad9a..d91ef936f 100644
--- a/tests/features/steps/common.ts
+++ b/tests/features/steps/common.ts
@@ -1,5 +1,3 @@
-/* eslint-disable @typescript-eslint/ban-ts-comment */
-/// <reference types="cypress" />
 import { When, Then, Given } from "@badeball/cypress-cucumber-preprocessor";
 
 // navigation
@@ -39,7 +37,6 @@ Given("I am logged in as {string}", (username: string) => {
   cy.wait(1000);
   cy.url().then(($url) => {
     if ($url.includes("modern_ui/login")) {
-      // @ts-ignore
       cy.loginAsAnUser(
         Cypress.env("admin_login"),
         Cypress.env("admin_password")
@@ -51,7 +48,6 @@ Given("I am logged in as {string}", (username: string) => {
     { timeout: 6000 }
   ).then(($ele) => {
     if ($ele.text() !== username) {
-      // @ts-ignore
       cy.loginAsAnUser(
         Cypress.env("admin_login"),
         Cypress.env("admin_password")
@@ -63,7 +59,6 @@ Given("I am logged in as {string}", (username: string) => {
 When(
   "I log in as {string} with password {string}",
   (username: string, password: string) => {
-    // @ts-ignore
     cy.loginAsAnUser(username, password);
   }
 );
diff --git a/tests/features/steps/user_handling.ts b/tests/features/steps/user_handling.ts
index a9ce17919..784288109 100644
--- a/tests/features/steps/user_handling.ts
+++ b/tests/features/steps/user_handling.ts
@@ -1,4 +1,3 @@
-/* eslint-disable @typescript-eslint/ban-ts-comment */
 import { When, Then, Given } from "@badeball/cypress-cucumber-preprocessor";
 
 const SSH_RSA_valid_1: string =
@@ -96,7 +95,6 @@ const userCert_valid_2: Record<string, string> = {
 };
 // Functions specific to active_users_handling.feature
 Given("sample testing user {string} exists", (username: string) => {
-  // @ts-ignore
   cy.createTestUser(username);
 });
 
@@ -336,7 +334,6 @@ Then("I should see value of {string} in the text area", (keyID: string) => {
 });
 
 Then("file with extension {string} should be downloaded", (ext: string) => {
-  // @ts-ignore
   cy.verifyDownload("." + ext, { contains: true });
 });
 
diff --git a/tsconfig.json b/tsconfig.json
index 33db64008..45258156e 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -21,14 +21,13 @@
     "noEmit": false,
     "resolveJsonModule": true,
     "moduleResolution": "node",
-    "isolatedModules": true
+    "isolatedModules": true,
+    "paths": {
+      "@badeball/cypress-cucumber-preprocessor/*": [
+        "./node_modules/@badeball/cypress-cucumber-preprocessor/dist/subpath-entrypoints/*"
+      ]
+    }
   },
-  "include": ["src", "cypress"],
-  "exclude": [
-    "./cypress/cypress.config.ts",
-    "./cypress/**/*",
-    "cypress",
-    "node_modules"
-  ],
+  "exclude": ["node_modules/*", "dist/*", "coverage/*"],
   "types": ["@testing-library/react"]
 }


Reviews:
user: login: sourcery-ai[bot]
body: Hey @duzda - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- Consider running `eslint --fix` as part of the CI pipeline to automatically fix linting issues.
- Ensure that the changes to tsconfig.json `exclude` still meet the needs of the project.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟢 **General issues**: all looks good
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟢 **Complexity**: all looks good
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-03-28T09:03:40Z
user: login: sourcery-ai[bot]
body: Hey @duzda - I've reviewed your changes - here's some feedback:

**Overall Comments**:
- Consider using `vi.mock` instead of `jest.mock` for Vitest compatibility.
- The change from Jest to Vitest requires updating the test setup and configuration files.

<details>
<summary>Here's what I looked at during the review</summary>

- 🟢 **General issues**: all looks good
- 🟢 **Security**: all looks good
- 🟢 **Testing**: all looks good
- 🟡 **Complexity**: 1 issue found
- 🟢 **Documentation**: all looks good
</details>

***

<details>
<summary>Sourcery is free for open source - if you like our reviews please consider sharing them ✨</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20got%20an%20instant%20code%20review%20from%20%40SourceryAI%2C%20and%20it%20was%20brilliant%21%20It%27s%20free%20for%20open%20source%20and%20has%20a%20free%20trial%20for%20private%20code.%20Check%20it%20out%20https%3A//sourcery.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https://sourcery.ai)
- [Facebook](https://www.facebook.com/sharer/sharer.php?u=https://sourcery.ai)

</details>

<sub>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
</sub>
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-01T08:25:24Z
user: login: carma12
body: Even if we integrate Prettier into the project, ideally we should keep the CI gating and allow the users to manually execute the `npm run prettier` command, IMHO.
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-01T13:56:43Z
user: login: duzda
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-01T16:31:47Z
user: login: carma12
body: LGTM.
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-03T13:58:26Z

Review comments:
diff_hunk: @@ -234,7 +242,7 @@ describe("IpaCertificates Component", () => {
     });
 
     // Certificate should be valid
-    jest.useFakeTimers().setSystemTime(new Date("2030-02-02"));
path: src/components/Form/IpaCertificates/IpaCertificates.test.tsx
user: login: sourcery-ai[bot]
body: **issue (complexity):** Consider extracting the repeated timer setup into a shared hook to avoid individual configuration in each test.

Consider extracting the repeated timer setup into a shared hook so that each test doesn't individually configure the fake timer. For example, in your `describe` block you can add:

```js
beforeEach(() => {
  vi.useFakeTimers();
  vi.setSystemTime(new Date("2030-02-02"));
});

afterEach(() => {
  vi.useRealTimers();
  cleanup();
});
```

Then remove all individual calls to `vi.useFakeTimers().setSystemTime(new Date("2030-02-02"))` from the test blocks. This centralizes the timer configuration, reduces duplication, and lowers the cognitive load without changing functionality.
created_at: 2025-04-01T08:25:23Z
author_association: NONE
diff_hunk: @@ -39,9 +39,6 @@ jobs:
       - name: Install project
         run: npm install
 
-      - name: Prettier
-        run: npm run prettier
path: .github/workflows/gating.yml
user: login: carma12
body: I think it is not a good idea to remove this as it is used in the Github CI jobs.
created_at: 2025-04-01T13:51:25Z
author_association: COLLABORATOR
diff_hunk: @@ -25,8 +25,6 @@
     "cypress:open": "cypress open --config-file cypress/cypress.config.ts",
     "lint": "eslint --color --max-warnings 0 src/",
     "lint:fix": "eslint --fix src/",
-    "prettier": "prettier --check .",
-    "prettier:fix": "prettier --write .",
path: package.json
user: login: carma12
body: Same as this two lines. Ideally we should offer the chance to manually execute the prettier command.
created_at: 2025-04-01T13:52:19Z
author_association: COLLABORATOR
diff_hunk: @@ -39,9 +39,6 @@ jobs:
       - name: Install project
         run: npm install
 
-      - name: Prettier
-        run: npm run prettier
path: .github/workflows/gating.yml
user: login: duzda
body: Good point, if something breaks down, we'd wanna ensure that CI always takes care of it.
created_at: 2025-04-01T16:31:47Z
author_association: CONTRIBUTOR

PR comments:
user: login: sourcery-ai[bot]
created_at: 2025-03-28T09:03:16Z
body: <!-- Generated by sourcery-ai[bot]: start review_guide -->

## Reviewer's Guide by Sourcery

This pull request integrates Prettier with ESLint, migrates from Jest to Vitest, updates Cypress, and streamlines project configuration by removing redundant files and configurations. It also enforces TypeScript usage and updates dependencies.

_No diagrams generated as the changes look simple and do not need a visual representation._

### File-Level Changes

| Change | Details | Files |
| ------ | ------- | ----- |
| Migrated from Jest to Vitest for unit testing. | <ul><li>Replaced `jest` with `vi` for mocks and spies.</li><li>Updated test scripts in `package.json` to use `vitest` commands.</li><li>Installed `@vitest/coverage-v8` for coverage reports.</li><li>Configured `vite.config.ts` for Vitest.</li><li>Added `setupFiles` entry in `vite.config.ts` to include setup files for testing.</li><li>Removed `jest` and `@types/jest` dependencies.</li><li>Removed `ts-jest` dependency.</li><li>Removed `jest-environment-jsdom` dependency.</li></ul> | `src/components/Form/IpaCertificates/IpaCertificates.test.tsx`<br/>`README.md`<br/>`package.json`<br/>`src/components/Form/IpaCertificateMappingData/IpaCertificateMappingData.test.tsx`<br/>`src/components/Form/PrincipalAliasMultiTextBox/PrincipalAliasMultiTextBox.test.tsx`<br/>`src/components/layouts/BreadCrumb/BreadCrumb.test.tsx`<br/>`src/components/Form/IpaSshPublicKeys/IpaSshPublicKeys.test.tsx`<br/>`vite.config.ts`<br/>`src/components/Form/IpaTextboxList/IpaTextboxList.test.tsx`<br/>`src/components/Form/IpaNumberInput/IpaNumberInput.test.tsx`<br/>`src/components/Form/IpaCheckbox/IpaCheckbox.test.tsx`<br/>`src/components/Form/IpaCheckboxes/IpaCheckboxes.test.tsx`<br/>`src/components/Form/IpaTextArea/IpaTextArea.test.tsx`<br/>`src/components/Form/IpaTextInput/IpaTextInput.test.tsx`<br/>`src/components/Form/IpaToggleGroup/IpaToggleGroup.test.tsx`<br/>`src/components/Form/DateTimeSelector/DateTimeSelector.test.tsx`<br/>`src/components/Form/IpaCalendar/IpaCalendar.test.tsx`<br/>`src/components/Form/IpaDropdownSearch/IpaDropdownSearch.test.tsx`<br/>`src/components/Form/IpaPACType/IpaPACType.test.tsx`<br/>`src/components/Form/IpaSelect/IpaSelect.test.tsx`<br/>`src/components/Form/IpaTextContent/IpaTextContent.test.tsx`<br/>`src/components/Form/IpaTextInputFromList/IpaTextInputFromList.test.tsx` |
| Integrated Prettier with ESLint and removed standalone Prettier configuration. | <ul><li>Removed separate Prettier configuration files.</li><li>Removed Prettier check from GitHub Actions workflow.</li><li>Updated ESLint configuration to include Prettier plugin.</li><li>Removed standalone Prettier commands from `package.json` scripts.</li></ul> | `.github/workflows/gating.yml`<br/>`package.json` |
| Removed EditorConfig configuration. | <ul><li>Deleted `.editorconfig` file.</li></ul> | `.editorconfig` |
| Updated TypeScript configuration. | <ul><li>Modified `tsconfig.json` to exclude test and coverage directories.</li><li>Set `allowJs` to `false` in `tsconfig.json` to enforce TypeScript usage.</li></ul> | `tsconfig.json` |
| Updated Cypress configuration and support files. | <ul><li>Added cucumber preprocessor plugin.</li><li>Removed example cypress tests.</li><li>Added `cy-verify-downloads` command.</li><li>Added `cypress/support/index.d.ts` to declare custom commands.</li></ul> | `cypress/support/commands.ts`<br/>`cypress/plugins/index.ts`<br/>`cypress/support/index.d.ts` |

---

<details>
<summary>Tips and commands</summary>

#### Interacting with Sourcery

- **Trigger a new review:** Comment `@sourcery-ai review` on the pull request.
- **Continue discussions:** Reply directly to Sourcery's review comments.
- **Generate a GitHub issue from a review comment:** Ask Sourcery to create an
  issue from a review comment by replying to it. You can also reply to a
  review comment with `@sourcery-ai issue` to create an issue from it.
- **Generate a pull request title:** Write `@sourcery-ai` anywhere in the pull
  request title to generate a title at any time. You can also comment
  `@sourcery-ai title` on the pull request to (re-)generate the title at any time.
- **Generate a pull request summary:** Write `@sourcery-ai summary` anywhere in
  the pull request body to generate a PR summary at any time exactly where you
  want it. You can also comment `@sourcery-ai summary` on the pull request to
  (re-)generate the summary at any time.
- **Generate reviewer's guide:** Comment `@sourcery-ai guide` on the pull
  request to (re-)generate the reviewer's guide at any time.
- **Resolve all Sourcery comments:** Comment `@sourcery-ai resolve` on the
  pull request to resolve all Sourcery comments. Useful if you've already
  addressed all the comments and don't want to see them anymore.
- **Dismiss all Sourcery reviews:** Comment `@sourcery-ai dismiss` on the pull
  request to dismiss all existing Sourcery reviews. Especially useful if you
  want to start fresh with a new review - don't forget to comment
  `@sourcery-ai review` to trigger a new review!
- **Generate a plan of action for an issue:** Comment `@sourcery-ai plan` on
  an issue to generate a plan of action for it.

#### Customizing Your Experience

Access your [dashboard](https://app.sourcery.ai) to:
- Enable or disable review features such as the Sourcery-generated pull request
  summary, the reviewer's guide, and others.
- Change the review language.
- Add, remove or edit custom review instructions.
- Adjust other review settings.

#### Getting Help

- [Contact our support team](mailto:support@sourcery.ai) for questions or feedback.
- Visit our [documentation](https://docs.sourcery.ai) for detailed guides and information.
- Keep in touch with the Sourcery team by following us on [X/Twitter](https://x.com/SourceryAI), [LinkedIn](https://www.linkedin.com/company/sourcery-ai/) or [GitHub](https://github.com/sourcery-ai).

</details>

<!-- Generated by sourcery-ai[bot]: end review_guide -->
author_association: NONE
user: login: duzda
created_at: 2025-04-01T08:24:26Z
body: @sourcery-ai review
author_association: CONTRIBUTOR
user: login: duzda
created_at: 2025-04-01T08:29:01Z
body: When reviewing please triple check, that all the types are working correctly.
author_association: CONTRIBUTOR
user: login: duzda
created_at: 2025-04-02T08:19:44Z
body: Rebased and integrated proposed changes, thanks
author_association: CONTRIBUTOR