mirror of
https://github.com/komodorio/helm-dashboard.git
synced 2026-03-24 11:48:04 +00:00
Added Error Boundary (#649)
* Added Error Boundary * Test improvements * Introduced useDevLogger * Updated Cypress to latest and aligned the tests * Added eslint-enable * Set allowCypressEnv: false for security reasons.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import { defineConfig } from "cypress";
|
import { defineConfig } from "cypress";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
|
allowCypressEnv: false,
|
||||||
component: {
|
component: {
|
||||||
devServer: {
|
devServer: {
|
||||||
framework: "react",
|
framework: "react",
|
||||||
@@ -9,8 +10,9 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
e2e: {
|
e2e: {
|
||||||
setupNodeEvents(on, config) {
|
baseUrl: "http://localhost:5173",
|
||||||
// implement node event listeners here
|
// setupNodeEvents(on, config) {
|
||||||
},
|
// // implement node event listeners here
|
||||||
|
// },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,17 +4,15 @@ describe("Adding repository flow", () => {
|
|||||||
const addChartRepositoryButton = "[data-cy='add-chart-repository-button']";
|
const addChartRepositoryButton = "[data-cy='add-chart-repository-button']";
|
||||||
|
|
||||||
it("Adding new chart repository", () => {
|
it("Adding new chart repository", () => {
|
||||||
cy.intercept("GET", "http://localhost:5173/status", {
|
cy.intercept("GET", "/status", {
|
||||||
fixture: "status.json",
|
fixture: "status.json",
|
||||||
}).as("status");
|
}).as("status");
|
||||||
|
|
||||||
cy.intercept("GET", "http://localhost:5173/api/helm/releases", {
|
cy.intercept("GET", "/api/helm/releases", {
|
||||||
fixture: "releases.json",
|
fixture: "releases.json",
|
||||||
}).as("releases");
|
}).as("releases");
|
||||||
|
|
||||||
cy.visit(
|
cy.visit("/#/minikube/installed?filteredNamespace=default");
|
||||||
"http://localhost:5173/#/minikube/installed?filteredNamespace=default"
|
|
||||||
);
|
|
||||||
|
|
||||||
cy.get("[data-cy='navigation-link']").contains("Repository").click();
|
cy.get("[data-cy='navigation-link']").contains("Repository").click();
|
||||||
cy.get("[data-cy='install-repository-button']").click();
|
cy.get("[data-cy='install-repository-button']").click();
|
||||||
@@ -22,11 +20,12 @@ describe("Adding repository flow", () => {
|
|||||||
cy.get(addChartNameInput).type("Komodorio");
|
cy.get(addChartNameInput).type("Komodorio");
|
||||||
cy.get(addChartUrlInput).type("https://helm-charts.komodor.io");
|
cy.get(addChartUrlInput).type("https://helm-charts.komodor.io");
|
||||||
|
|
||||||
cy.intercept("GET", "http://localhost:5173/api/helm/repositories", {
|
cy.intercept("GET", "/api/helm/repositories", {
|
||||||
fixture: "repositories.json",
|
fixture: "repositories.json",
|
||||||
}).as("repositories");
|
}).as("repositories");
|
||||||
|
|
||||||
cy.get(addChartRepositoryButton).click();
|
cy.get(addChartRepositoryButton).click();
|
||||||
|
cy.wait("@repositories");
|
||||||
|
|
||||||
cy.contains("https://helm-charts.komodor.io");
|
cy.contains("https://helm-charts.komodor.io");
|
||||||
|
|
||||||
@@ -36,15 +35,13 @@ describe("Adding repository flow", () => {
|
|||||||
.contains("Install")
|
.contains("Install")
|
||||||
.click();
|
.click();
|
||||||
|
|
||||||
cy.intercept("POST", "http://localhost:5173/api/helm/releases/default", {
|
cy.intercept("POST", "/api/helm/releases/default", {
|
||||||
fixture: "defaultReleases.json",
|
fixture: "defaultReleases.json",
|
||||||
}).as("defaultReleases");
|
}).as("defaultReleases");
|
||||||
|
|
||||||
cy.intercept(
|
cy.intercept("GET", "/api/helm/releases/default/helm-dashboard/history", {
|
||||||
"GET",
|
fixture: "history.json",
|
||||||
"http://localhost:5173/api/helm/releases/default/helm-dashboard/history",
|
}).as("history");
|
||||||
{ fixture: "history.json" }
|
|
||||||
).as("history");
|
|
||||||
|
|
||||||
cy.contains("Confirm").click();
|
cy.contains("Confirm").click();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import "./commands";
|
import "./commands";
|
||||||
import { mount } from "cypress/react18";
|
import { mount } from "cypress/react";
|
||||||
|
|
||||||
|
/* eslint-disable @typescript-eslint/no-namespace */
|
||||||
declare global {
|
declare global {
|
||||||
namespace Cypress {
|
namespace Cypress {
|
||||||
interface Chainable {
|
interface Chainable {
|
||||||
@@ -8,5 +9,5 @@ declare global {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* eslint-enable @typescript-eslint/no-namespace */
|
||||||
Cypress.Commands.add("mount", mount);
|
Cypress.Commands.add("mount", mount);
|
||||||
214
frontend/package-lock.json
generated
214
frontend/package-lock.json
generated
@@ -18,6 +18,7 @@
|
|||||||
"luxon": "^3.7.2",
|
"luxon": "^3.7.2",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"react-error-boundary": "^6.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-intersection-observer": "^10.0.0",
|
"react-intersection-observer": "^10.0.0",
|
||||||
"react-modern-drawer": "^1.4.0",
|
"react-modern-drawer": "^1.4.0",
|
||||||
@@ -43,7 +44,7 @@
|
|||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
"autoprefixer": "^10.4.22",
|
"autoprefixer": "^10.4.22",
|
||||||
"babel-plugin-react-compiler": "^1.0.0",
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
"cypress": "^13.3.0",
|
"cypress": "15.10.0",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-enpitech": "^1.0.17",
|
"eslint-config-enpitech": "^1.0.17",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
@@ -409,17 +410,6 @@
|
|||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@colors/colors": {
|
|
||||||
"version": "1.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
|
|
||||||
"integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"optional": true,
|
|
||||||
"engines": {
|
|
||||||
"node": ">=0.1.90"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@cypress/request": {
|
"node_modules/@cypress/request": {
|
||||||
"version": "3.0.10",
|
"version": "3.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.10.tgz",
|
||||||
@@ -2926,6 +2916,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -2948,6 +2940,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||||
"version": "0.10.1",
|
"version": "0.10.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||||
|
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
@@ -2958,6 +2952,8 @@
|
|||||||
},
|
},
|
||||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||||
"version": "2.8.1",
|
"version": "2.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
|
||||||
|
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"inBundle": true,
|
"inBundle": true,
|
||||||
"license": "0BSD",
|
"license": "0BSD",
|
||||||
@@ -3219,6 +3215,7 @@
|
|||||||
"integrity": "sha512-22MG6T02Hos2JWfa1o5jsIByn+bc5iOt1IS4xyg6OG68Bu+wMonVZzdrgCw693++rpLE9RUT/Bx15BeDzO0j+g==",
|
"integrity": "sha512-22MG6T02Hos2JWfa1o5jsIByn+bc5iOt1IS4xyg6OG68Bu+wMonVZzdrgCw693++rpLE9RUT/Bx15BeDzO0j+g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~5.26.4"
|
"undici-types": "~5.26.4"
|
||||||
}
|
}
|
||||||
@@ -3310,6 +3307,13 @@
|
|||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/tmp": {
|
||||||
|
"version": "0.2.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.2.6.tgz",
|
||||||
|
"integrity": "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/trusted-types": {
|
"node_modules/@types/trusted-types": {
|
||||||
"version": "2.0.7",
|
"version": "2.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||||
@@ -4035,13 +4039,6 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/async": {
|
|
||||||
"version": "3.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
|
||||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/async-function": {
|
"node_modules/async-function": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
|
||||||
@@ -4236,9 +4233,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/baseline-browser-mapping": {
|
"node_modules/baseline-browser-mapping": {
|
||||||
"version": "2.8.31",
|
"version": "2.9.19",
|
||||||
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.31.tgz",
|
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
|
||||||
"integrity": "sha512-a28v2eWrrRWPpJSzxc+mKwm0ZtVx/G8SepdQZDArnXYU/XS+IF6mp8aB/4E+hH1tyGCoDo3KlUCdlSxGDsRkAw==",
|
"integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -4566,16 +4563,6 @@
|
|||||||
"node": ">= 16"
|
"node": ">= 16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/check-more-types": {
|
|
||||||
"version": "2.24.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
|
|
||||||
"integrity": "sha512-Pj779qHxV2tuapviy1bSZNEL1maXr13bPYpsvSDB68HlYcYuhlDrmGd63i0JHMCLKzc7rUSNIrpdJlhVlNwrxA==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
@@ -4592,9 +4579,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/ci-info": {
|
"node_modules/ci-info": {
|
||||||
"version": "3.8.0",
|
"version": "4.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz",
|
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz",
|
||||||
"integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==",
|
"integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"funding": [
|
"funding": [
|
||||||
{
|
{
|
||||||
@@ -4637,9 +4624,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cli-table3": {
|
"node_modules/cli-table3": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.1",
|
||||||
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.1.tgz",
|
||||||
"integrity": "sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==",
|
"integrity": "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4649,7 +4636,7 @@
|
|||||||
"node": "10.* || >= 12.*"
|
"node": "10.* || >= 12.*"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@colors/colors": "1.5.0"
|
"colors": "1.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/cli-truncate": {
|
"node_modules/cli-truncate": {
|
||||||
@@ -4729,6 +4716,17 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/colors": {
|
||||||
|
"version": "1.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||||
|
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"optional": true,
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.1.90"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/combined-stream": {
|
"node_modules/combined-stream": {
|
||||||
"version": "1.0.8",
|
"version": "1.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
@@ -4897,27 +4895,27 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/cypress": {
|
"node_modules/cypress": {
|
||||||
"version": "13.6.2",
|
"version": "15.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/cypress/-/cypress-15.10.0.tgz",
|
||||||
"integrity": "sha512-TW3bGdPU4BrfvMQYv1z3oMqj71YI4AlgJgnrycicmPZAXtvywVFZW9DAToshO65D97rCWfG/kqMFsYB6Kp91gQ==",
|
"integrity": "sha512-OtUh7OMrfEjKoXydlAD1CfG2BvKxIqgWGY4/RMjrqQ3BKGBo5JFKoYNH+Tpcj4xKxWH4XK0Xri+9y8WkxhYbqQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@cypress/request": "^3.0.0",
|
"@cypress/request": "^3.0.10",
|
||||||
"@cypress/xvfb": "^1.2.4",
|
"@cypress/xvfb": "^1.2.4",
|
||||||
"@types/node": "^18.17.5",
|
|
||||||
"@types/sinonjs__fake-timers": "8.1.1",
|
"@types/sinonjs__fake-timers": "8.1.1",
|
||||||
"@types/sizzle": "^2.3.2",
|
"@types/sizzle": "^2.3.2",
|
||||||
|
"@types/tmp": "^0.2.3",
|
||||||
"arch": "^2.2.0",
|
"arch": "^2.2.0",
|
||||||
"blob-util": "^2.0.2",
|
"blob-util": "^2.0.2",
|
||||||
"bluebird": "^3.7.2",
|
"bluebird": "^3.7.2",
|
||||||
"buffer": "^5.6.0",
|
"buffer": "^5.7.1",
|
||||||
"cachedir": "^2.3.0",
|
"cachedir": "^2.3.0",
|
||||||
"chalk": "^4.1.0",
|
"chalk": "^4.1.0",
|
||||||
"check-more-types": "^2.24.0",
|
"ci-info": "^4.1.0",
|
||||||
"cli-cursor": "^3.1.0",
|
"cli-cursor": "^3.1.0",
|
||||||
"cli-table3": "~0.6.1",
|
"cli-table3": "0.6.1",
|
||||||
"commander": "^6.2.1",
|
"commander": "^6.2.1",
|
||||||
"common-tags": "^1.8.0",
|
"common-tags": "^1.8.0",
|
||||||
"dayjs": "^1.10.4",
|
"dayjs": "^1.10.4",
|
||||||
@@ -4929,12 +4927,10 @@
|
|||||||
"extract-zip": "2.0.1",
|
"extract-zip": "2.0.1",
|
||||||
"figures": "^3.2.0",
|
"figures": "^3.2.0",
|
||||||
"fs-extra": "^9.1.0",
|
"fs-extra": "^9.1.0",
|
||||||
"getos": "^3.2.1",
|
"hasha": "5.2.2",
|
||||||
"is-ci": "^3.0.0",
|
|
||||||
"is-installed-globally": "~0.4.0",
|
"is-installed-globally": "~0.4.0",
|
||||||
"lazy-ass": "^1.6.0",
|
|
||||||
"listr2": "^3.8.3",
|
"listr2": "^3.8.3",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.23",
|
||||||
"log-symbols": "^4.0.0",
|
"log-symbols": "^4.0.0",
|
||||||
"minimist": "^1.2.8",
|
"minimist": "^1.2.8",
|
||||||
"ospath": "^1.2.2",
|
"ospath": "^1.2.2",
|
||||||
@@ -4942,9 +4938,10 @@
|
|||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"proxy-from-env": "1.0.0",
|
"proxy-from-env": "1.0.0",
|
||||||
"request-progress": "^3.0.0",
|
"request-progress": "^3.0.0",
|
||||||
"semver": "^7.5.3",
|
|
||||||
"supports-color": "^8.1.1",
|
"supports-color": "^8.1.1",
|
||||||
"tmp": "~0.2.1",
|
"systeminformation": "^5.27.14",
|
||||||
|
"tmp": "~0.2.4",
|
||||||
|
"tree-kill": "1.2.2",
|
||||||
"untildify": "^4.0.0",
|
"untildify": "^4.0.0",
|
||||||
"yauzl": "^2.10.0"
|
"yauzl": "^2.10.0"
|
||||||
},
|
},
|
||||||
@@ -4952,7 +4949,7 @@
|
|||||||
"cypress": "bin/cypress"
|
"cypress": "bin/cypress"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^16.0.0 || ^18.0.0 || >=20.0.0"
|
"node": "^20.1.0 || ^22.0.0 || >=24.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/damerau-levenshtein": {
|
"node_modules/damerau-levenshtein": {
|
||||||
@@ -7415,16 +7412,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/getos": {
|
|
||||||
"version": "3.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz",
|
|
||||||
"integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"async": "^3.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/getpass": {
|
"node_modules/getpass": {
|
||||||
"version": "0.1.7",
|
"version": "0.1.7",
|
||||||
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
|
||||||
@@ -7660,6 +7647,33 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hasha": {
|
||||||
|
"version": "5.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz",
|
||||||
|
"integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"is-stream": "^2.0.0",
|
||||||
|
"type-fest": "^0.8.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/hasha/node_modules/type-fest": {
|
||||||
|
"version": "0.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
|
||||||
|
"integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "(MIT OR CC0-1.0)",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/hasown": {
|
"node_modules/hasown": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
@@ -8093,19 +8107,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/is-ci": {
|
|
||||||
"version": "3.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz",
|
|
||||||
"integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"ci-info": "^3.2.0"
|
|
||||||
},
|
|
||||||
"bin": {
|
|
||||||
"is-ci": "bin.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/is-core-module": {
|
"node_modules/is-core-module": {
|
||||||
"version": "2.16.1",
|
"version": "2.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
|
||||||
@@ -8812,16 +8813,6 @@
|
|||||||
"language-subtag-registry": "~0.3.2"
|
"language-subtag-registry": "~0.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lazy-ass": {
|
|
||||||
"version": "1.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
|
|
||||||
"integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==",
|
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": "> 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/levn": {
|
"node_modules/levn": {
|
||||||
"version": "0.4.1",
|
"version": "0.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
|
||||||
@@ -10856,6 +10847,15 @@
|
|||||||
"react": "^19.2.0"
|
"react": "^19.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-error-boundary": {
|
||||||
|
"version": "6.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-6.1.0.tgz",
|
||||||
|
"integrity": "sha512-02k9WQ/mUhdbXir0tC1NiMesGzRPaCsJEWU/4bcFrbY1YMZOtHShtZP6zw0SJrBWA/31H0KT9/FgdL8+sPKgHA==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-icons": {
|
"node_modules/react-icons": {
|
||||||
"version": "5.5.0",
|
"version": "5.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz",
|
||||||
@@ -12367,6 +12367,33 @@
|
|||||||
"url": "https://opencollective.com/synckit"
|
"url": "https://opencollective.com/synckit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/systeminformation": {
|
||||||
|
"version": "5.30.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-5.30.7.tgz",
|
||||||
|
"integrity": "sha512-33B/cftpaWdpvH+Ho9U1b08ss8GQuLxrWHelbJT1yw4M48Taj8W3ezcPuaLoIHZz5V6tVHuQPr5BprEfnBLBMw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"os": [
|
||||||
|
"darwin",
|
||||||
|
"linux",
|
||||||
|
"win32",
|
||||||
|
"freebsd",
|
||||||
|
"openbsd",
|
||||||
|
"netbsd",
|
||||||
|
"sunos",
|
||||||
|
"android"
|
||||||
|
],
|
||||||
|
"bin": {
|
||||||
|
"systeminformation": "lib/cli.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "Buy me a coffee",
|
||||||
|
"url": "https://www.buymeacoffee.com/systeminfo"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tabbable": {
|
"node_modules/tabbable": {
|
||||||
"version": "6.3.0",
|
"version": "6.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.3.0.tgz",
|
||||||
@@ -12564,6 +12591,16 @@
|
|||||||
"node": ">=16"
|
"node": ">=16"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/tree-kill": {
|
||||||
|
"version": "1.2.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||||
|
"integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"tree-kill": "cli.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/tree-sitter": {
|
"node_modules/tree-sitter": {
|
||||||
"version": "0.21.1",
|
"version": "0.21.1",
|
||||||
"resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz",
|
"resolved": "https://registry.npmjs.org/tree-sitter/-/tree-sitter-0.21.1.tgz",
|
||||||
@@ -12865,7 +12902,8 @@
|
|||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
|
"optional": true
|
||||||
},
|
},
|
||||||
"node_modules/universalify": {
|
"node_modules/universalify": {
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
"luxon": "^3.7.2",
|
"luxon": "^3.7.2",
|
||||||
"react": "^19.2.0",
|
"react": "^19.2.0",
|
||||||
"react-dom": "^19.2.0",
|
"react-dom": "^19.2.0",
|
||||||
|
"react-error-boundary": "^6.1.0",
|
||||||
"react-icons": "^5.5.0",
|
"react-icons": "^5.5.0",
|
||||||
"react-intersection-observer": "^10.0.0",
|
"react-intersection-observer": "^10.0.0",
|
||||||
"react-modern-drawer": "^1.4.0",
|
"react-modern-drawer": "^1.4.0",
|
||||||
@@ -39,7 +40,7 @@
|
|||||||
"@vitejs/plugin-react": "^5.1.1",
|
"@vitejs/plugin-react": "^5.1.1",
|
||||||
"autoprefixer": "^10.4.22",
|
"autoprefixer": "^10.4.22",
|
||||||
"babel-plugin-react-compiler": "^1.0.0",
|
"babel-plugin-react-compiler": "^1.0.0",
|
||||||
"cypress": "^13.3.0",
|
"cypress": "15.10.0",
|
||||||
"eslint": "^9.39.1",
|
"eslint": "^9.39.1",
|
||||||
"eslint-config-enpitech": "^1.0.17",
|
"eslint-config-enpitech": "^1.0.17",
|
||||||
"eslint-config-prettier": "^10.1.8",
|
"eslint-config-prettier": "^10.1.8",
|
||||||
@@ -80,7 +81,9 @@
|
|||||||
"prettier": "npx prettier src/ --check",
|
"prettier": "npx prettier src/ --check",
|
||||||
"prettier:fix": "npm run prettier -- --write",
|
"prettier:fix": "npm run prettier -- --write",
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
"cypress:run": "cypress run"
|
"cypress:run": "cypress run",
|
||||||
|
"cypress:component": "cypress run --component",
|
||||||
|
"cypress:component:open": "cypress open --component"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import { ErrorModalContext } from "./context/ErrorModalContext";
|
|||||||
import GlobalErrorModal from "./components/modal/GlobalErrorModal";
|
import GlobalErrorModal from "./components/modal/GlobalErrorModal";
|
||||||
import { AppContextProvider } from "./context/AppContext";
|
import { AppContextProvider } from "./context/AppContext";
|
||||||
import apiService from "./API/apiService";
|
import apiService from "./API/apiService";
|
||||||
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
|
import ErrorFallback from "./components/ErrorFallback";
|
||||||
|
|
||||||
const DocsPage = lazy(() => import("./pages/DocsPage"));
|
const DocsPage = lazy(() => import("./pages/DocsPage"));
|
||||||
|
|
||||||
@@ -53,6 +55,7 @@ export default function App() {
|
|||||||
<AppContextProvider>
|
<AppContextProvider>
|
||||||
<ErrorModalContext.Provider value={value}>
|
<ErrorModalContext.Provider value={value}>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<ErrorBoundary FallbackComponent={ErrorFallback}>
|
||||||
<HashRouter>
|
<HashRouter>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="docs/*" element={<DocsPage />} />
|
<Route path="docs/*" element={<DocsPage />} />
|
||||||
@@ -72,6 +75,7 @@ export default function App() {
|
|||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
|
</ErrorBoundary>
|
||||||
<GlobalErrorModal
|
<GlobalErrorModal
|
||||||
isOpen={!!shouldShowErrorModal}
|
isOpen={!!shouldShowErrorModal}
|
||||||
onClose={() => setShowErrorModal(undefined)}
|
onClose={() => setShowErrorModal(undefined)}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { mount } from "cypress/react18";
|
import { mount } from "cypress/react";
|
||||||
import { Button } from "./common/Button/Button";
|
import { Button } from "./common/Button/Button";
|
||||||
|
|
||||||
describe("Button component tests", () => {
|
describe("Button component tests", () => {
|
||||||
|
|||||||
@@ -45,6 +45,14 @@ const renderClustersList = (props: ClustersListProps) => {
|
|||||||
|
|
||||||
describe("ClustersList", () => {
|
describe("ClustersList", () => {
|
||||||
it("Got one cluster information", () => {
|
it("Got one cluster information", () => {
|
||||||
|
cy.intercept("GET", "/api/k8s/contexts", [
|
||||||
|
{
|
||||||
|
Name: "minikube",
|
||||||
|
Namespace: "default",
|
||||||
|
IsCurrent: true,
|
||||||
|
},
|
||||||
|
]).as("getClusters");
|
||||||
|
|
||||||
renderClustersList({
|
renderClustersList({
|
||||||
selectedCluster: "minikube",
|
selectedCluster: "minikube",
|
||||||
filteredNamespaces: ["default"],
|
filteredNamespaces: ["default"],
|
||||||
@@ -52,12 +60,21 @@ describe("ClustersList", () => {
|
|||||||
installedReleases: [generateTestReleaseData()],
|
installedReleases: [generateTestReleaseData()],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cy.wait("@getClusters");
|
||||||
cy.get(".data-cy-clusterName").contains("minikube");
|
cy.get(".data-cy-clusterName").contains("minikube");
|
||||||
cy.get(".data-cy-clusterList-namespace").contains("default");
|
cy.get(".data-cy-clusterList-namespace").contains("default");
|
||||||
cy.get(".data-cy-clustersInput").should("be.checked");
|
cy.get(".data-cy-clustersInput").should("be.checked");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Dont have a cluster chekced", () => {
|
it("Dont have a cluster chekced", () => {
|
||||||
|
cy.intercept("GET", "/api/k8s/contexts", [
|
||||||
|
{
|
||||||
|
Name: "minikube",
|
||||||
|
Namespace: "default",
|
||||||
|
IsCurrent: true,
|
||||||
|
},
|
||||||
|
]).as("getClusters");
|
||||||
|
|
||||||
renderClustersList({
|
renderClustersList({
|
||||||
selectedCluster: "",
|
selectedCluster: "",
|
||||||
filteredNamespaces: [""],
|
filteredNamespaces: [""],
|
||||||
@@ -65,6 +82,7 @@ describe("ClustersList", () => {
|
|||||||
installedReleases: [generateTestReleaseData()],
|
installedReleases: [generateTestReleaseData()],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
cy.wait("@getClusters");
|
||||||
cy.get(".data-cy-clustersInput").should("not.be.checked");
|
cy.get(".data-cy-clustersInput").should("not.be.checked");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
113
frontend/src/components/ErrorFallback/ErrorFallback.cy.tsx
Normal file
113
frontend/src/components/ErrorFallback/ErrorFallback.cy.tsx
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
import ErrorFallback from "./ErrorFallback";
|
||||||
|
import { mount } from "cypress/react";
|
||||||
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component tests for ErrorFallback
|
||||||
|
* Tests the error fallback UI and reset functionality
|
||||||
|
*/
|
||||||
|
describe("ErrorFallback", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Ensure portal root exists for createPortal
|
||||||
|
if (!document.getElementById("portal")) {
|
||||||
|
const portalDiv = document.createElement("div");
|
||||||
|
portalDiv.id = "portal";
|
||||||
|
document.body.appendChild(portalDiv);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should render error modal with error message and hint", () => {
|
||||||
|
const mockError = new Error("Test error message");
|
||||||
|
const mockReset = cy.stub().as("resetErrorBoundary");
|
||||||
|
|
||||||
|
mount(<ErrorFallback error={mockError} resetErrorBoundary={mockReset} />);
|
||||||
|
|
||||||
|
// Verify modal is open (checking document directly because of portal)
|
||||||
|
cy.get("#portal").should("be.visible");
|
||||||
|
cy.get("#portal").should("contain", "Application Error");
|
||||||
|
cy.get("#portal").should("contain", "Test error message");
|
||||||
|
|
||||||
|
// Verify Komodor hint is present (from GlobalErrorModal)
|
||||||
|
cy.get("#portal").should("contain", "Sign up for free.");
|
||||||
|
cy.get("#portal a")
|
||||||
|
.should("have.attr", "href")
|
||||||
|
.and("include", "komodor.com");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should call resetErrorBoundary when modal is closed", () => {
|
||||||
|
const mockError = new Error("Test error");
|
||||||
|
const mockReset = cy.stub().as("resetErrorBoundary");
|
||||||
|
|
||||||
|
mount(<ErrorFallback error={mockError} resetErrorBoundary={mockReset} />);
|
||||||
|
|
||||||
|
// Find and click close button (using the selector from Modal.tsx)
|
||||||
|
cy.get("[data-modal-hide='staticModal']").click();
|
||||||
|
|
||||||
|
// Verify reset was called
|
||||||
|
cy.get("@resetErrorBoundary").should("have.been.calledOnce");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should handle non-Error objects gracefully", () => {
|
||||||
|
const mockError = "String error" as unknown as Error;
|
||||||
|
const mockReset = cy.stub().as("resetErrorBoundary");
|
||||||
|
|
||||||
|
mount(<ErrorFallback error={mockError} resetErrorBoundary={mockReset} />);
|
||||||
|
|
||||||
|
// Should show fallback message
|
||||||
|
cy.get("#portal").should(
|
||||||
|
"contain",
|
||||||
|
"An unexpected error occurred. Please try again."
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should log error in development mode", () => {
|
||||||
|
const mockError = new Error("Test error for logging");
|
||||||
|
const mockReset = cy.stub();
|
||||||
|
|
||||||
|
cy.window().then((win) => {
|
||||||
|
cy.spy(win.console, "error").as("consoleError");
|
||||||
|
});
|
||||||
|
|
||||||
|
mount(<ErrorFallback error={mockError} resetErrorBoundary={mockReset} />);
|
||||||
|
|
||||||
|
// In dev mode, error should be logged
|
||||||
|
cy.get("@consoleError").should("have.been.called");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should catch errors from a real component and recover after reset (Integration)", () => {
|
||||||
|
const BuggyComponent = ({ shouldCrash }: { shouldCrash: boolean }) => {
|
||||||
|
if (shouldCrash) {
|
||||||
|
throw new Error("Integrated crash");
|
||||||
|
}
|
||||||
|
return <div data-cy="recovered">Recovered successfully!</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestWrapper = () => {
|
||||||
|
const [shouldCrash, setShouldCrash] = useState(true);
|
||||||
|
return (
|
||||||
|
<ErrorBoundary
|
||||||
|
FallbackComponent={ErrorFallback}
|
||||||
|
onReset={() => setShouldCrash(false)}
|
||||||
|
>
|
||||||
|
<BuggyComponent shouldCrash={shouldCrash} />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
mount(<TestWrapper />);
|
||||||
|
|
||||||
|
// Verify modal caught the real throw
|
||||||
|
cy.get("#portal").should("be.visible").and("not.be.empty");
|
||||||
|
cy.get("#portal").should("contain", "Integrated crash");
|
||||||
|
|
||||||
|
// Click close to reset
|
||||||
|
cy.get("[data-modal-hide='staticModal']").click();
|
||||||
|
|
||||||
|
// Verify modal is gone (portal should be empty) and component recovered
|
||||||
|
cy.get("#portal").should("be.empty");
|
||||||
|
cy.get("[data-cy='recovered']")
|
||||||
|
.should("be.visible")
|
||||||
|
.and("contain", "Recovered successfully!");
|
||||||
|
});
|
||||||
|
});
|
||||||
116
frontend/src/components/ErrorFallback/ErrorFallback.stories.tsx
Normal file
116
frontend/src/components/ErrorFallback/ErrorFallback.stories.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
import type { Meta, StoryObj } from "@storybook/react-vite";
|
||||||
|
import ErrorFallback from "./ErrorFallback";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { ErrorBoundary } from "react-error-boundary";
|
||||||
|
import Button from "../Button";
|
||||||
|
|
||||||
|
const meta = {
|
||||||
|
title: "Components/ErrorFallback",
|
||||||
|
component: ErrorFallback,
|
||||||
|
parameters: {
|
||||||
|
// More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout
|
||||||
|
layout: "centered",
|
||||||
|
},
|
||||||
|
tags: ["autodocs"],
|
||||||
|
argTypes: {
|
||||||
|
resetErrorBoundary: { action: "reset" },
|
||||||
|
},
|
||||||
|
} satisfies Meta<typeof ErrorFallback>;
|
||||||
|
|
||||||
|
export default meta;
|
||||||
|
type Story = StoryObj<typeof meta>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default error fallback with a standard error message
|
||||||
|
*/
|
||||||
|
export const Default: Story = {
|
||||||
|
args: {
|
||||||
|
error: new Error("Something went wrong in the application"),
|
||||||
|
resetErrorBoundary: () => {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const InteractiveIntegration: Story = {
|
||||||
|
args: {
|
||||||
|
error: new Error("Interactive Demo Error"),
|
||||||
|
resetErrorBoundary: () => {},
|
||||||
|
},
|
||||||
|
render: (args) => {
|
||||||
|
const BuggyComponent = () => {
|
||||||
|
const [shouldError, setShouldError] = useState(false);
|
||||||
|
|
||||||
|
if (shouldError) {
|
||||||
|
throw new Error(
|
||||||
|
"This is a real runtime error caught by the ErrorBoundary!"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-96 rounded border bg-white p-8 text-center shadow-md">
|
||||||
|
<h3 className="mb-4 text-lg font-bold">Interactive Demo</h3>
|
||||||
|
<p className="mb-6 text-sm text-gray-600">
|
||||||
|
Clicking the button below will cause this component to throw a
|
||||||
|
runtime error during render.
|
||||||
|
</p>
|
||||||
|
<Button onClick={() => setShouldError(true)}>
|
||||||
|
Trigger Runtime Error
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ErrorBoundary
|
||||||
|
FallbackComponent={(props) => (
|
||||||
|
<ErrorFallback
|
||||||
|
{...props}
|
||||||
|
resetErrorBoundary={() => {
|
||||||
|
props.resetErrorBoundary();
|
||||||
|
args.resetErrorBoundary();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<BuggyComponent />
|
||||||
|
</ErrorBoundary>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Long error message to test text wrapping
|
||||||
|
*/
|
||||||
|
export const LongErrorMessage: Story = {
|
||||||
|
args: {
|
||||||
|
error: new Error(
|
||||||
|
"This is a very long error message that should demonstrate how the error modal handles text wrapping and displays lengthy error descriptions to the user. The error boundary should gracefully handle this scenario."
|
||||||
|
),
|
||||||
|
resetErrorBoundary: () => {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Non-Error object to test fallback behavior
|
||||||
|
*/
|
||||||
|
export const NonErrorObject: Story = {
|
||||||
|
args: {
|
||||||
|
error: "String error message" as unknown as Error,
|
||||||
|
resetErrorBoundary: () => {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error with stack trace (useful for development)
|
||||||
|
*/
|
||||||
|
export const WithStackTrace: Story = {
|
||||||
|
args: {
|
||||||
|
error: (() => {
|
||||||
|
try {
|
||||||
|
throw new Error("Error with detailed stack trace");
|
||||||
|
} catch (e) {
|
||||||
|
return e as Error;
|
||||||
|
}
|
||||||
|
})(),
|
||||||
|
resetErrorBoundary: () => {},
|
||||||
|
},
|
||||||
|
};
|
||||||
33
frontend/src/components/ErrorFallback/ErrorFallback.tsx
Normal file
33
frontend/src/components/ErrorFallback/ErrorFallback.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import type { FallbackProps } from "react-error-boundary";
|
||||||
|
import GlobalErrorModal from "../modal/GlobalErrorModal";
|
||||||
|
import { useDevLogger } from "../../hooks/useDevLogger";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error fallback component for React Error Boundary
|
||||||
|
* Uses the existing GlobalErrorModal for consistent error display
|
||||||
|
* @param error - The error that was caught
|
||||||
|
* @param resetErrorBoundary - Function to reset the error boundary state
|
||||||
|
*/
|
||||||
|
const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => {
|
||||||
|
useDevLogger(error);
|
||||||
|
|
||||||
|
const handleClose = () => {
|
||||||
|
// Reset the error boundary to allow the component tree to re-render
|
||||||
|
resetErrorBoundary();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<GlobalErrorModal
|
||||||
|
isOpen={true}
|
||||||
|
onClose={handleClose}
|
||||||
|
titleText="Application Error"
|
||||||
|
contentText={
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "An unexpected error occurred. Please try again."
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ErrorFallback;
|
||||||
1
frontend/src/components/ErrorFallback/index.ts
Normal file
1
frontend/src/components/ErrorFallback/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export { default } from "./ErrorFallback";
|
||||||
9
frontend/src/hooks/useDevLogger.ts
Normal file
9
frontend/src/hooks/useDevLogger.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
export const useDevLogger = (error: unknown) => {
|
||||||
|
useEffect(() => {
|
||||||
|
if (import.meta.env.DEV) {
|
||||||
|
console.error("UnexpectedError", error);
|
||||||
|
}
|
||||||
|
}, [error]);
|
||||||
|
};
|
||||||
@@ -14,8 +14,7 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx"
|
||||||
"types": ["cypress"]
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src",
|
"src",
|
||||||
|
|||||||
@@ -7,5 +7,5 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"strict": true
|
"strict": true
|
||||||
},
|
},
|
||||||
"include": ["vite.config.ts"]
|
"include": ["vite.config.ts", "cypress.config.ts"]
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user