diff --git a/web/config/jest-setup.ts b/web/config/jest-setup.ts new file mode 100644 index 0000000000..e664f05232 --- /dev/null +++ b/web/config/jest-setup.ts @@ -0,0 +1,2 @@ +import "intersection-observer"; +import "@testing-library/jest-dom"; diff --git a/web/package.json b/web/package.json index f31ef6a38a..f43d77d64b 100644 --- a/web/package.json +++ b/web/package.json @@ -194,6 +194,9 @@ "not dead" ], "jest": { + "setupFilesAfterEnv": [ + "/config/jest-setup.ts" + ], "testEnvironmentOptions": { "customExportConditions": [ "" diff --git a/web/src/components/upgrade_service/AppConfig.tsx b/web/src/components/upgrade_service/AppConfig.tsx index ed033cc77d..9e393ce30c 100644 --- a/web/src/components/upgrade_service/AppConfig.tsx +++ b/web/src/components/upgrade_service/AppConfig.tsx @@ -603,7 +603,7 @@ export const AppConfig = ({ ); })} -
+
diff --git a/web/src/components/upgrade_service/ConfirmAndDeploy.tsx b/web/src/components/upgrade_service/ConfirmAndDeploy.tsx index e5615bbfff..eecf0139c1 100644 --- a/web/src/components/upgrade_service/ConfirmAndDeploy.tsx +++ b/web/src/components/upgrade_service/ConfirmAndDeploy.tsx @@ -218,7 +218,10 @@ const ConfirmAndDeploy = ({ return (
-
+
{location.pathname.includes("version-history") && (
navigate(-1)}> -
+
{ + const api = "http://test-api"; + const slug = "my-test-app"; + + it("Loading screen is present", async () => { + const { getByText } = render( + + + } /> + + + ); + + expect(getByText("Checking required steps...")).toBeDefined(); + }); + + describe("Initial state request", () => { + let server: SetupServerApi; + + // Override the API url used by the query + beforeAll(() => { + process.env.API_ENDPOINT = api; + }); + + afterAll(() => { + process.env.API_ENDPOINT = undefined; + }); + + afterEach(() => { + // Remove any handlers added + // in individual tests (runtime handlers). + server.resetHandlers(); + server.close(); + }); + + it("We get routed to the config section if the initial request succeeds and the app is configurable", async () => { + const slug = "my-test-app"; + server = setupServer( + http.get(`${api}/upgrade-service/app/${slug}`, () => { + return HttpResponse.json({ + isConfigurable: true, + hasPreflight: false, + }); + }) + ); + server.listen(); + + const { findByTestId } = render( + + + } /> + + + ); + + await findByTestId("config-area"); + }); + + it("We get routed to the preflight section if the initial request succeeds and the app is not configurable", async () => { + const slug = "my-test-app"; + server = setupServer( + http.get(`${api}/upgrade-service/app/${slug}`, () => { + return HttpResponse.json({ + isConfigurable: false, + hasPreflight: true, + }); + }) + ); + server.listen(); + + const { findByTestId, getByText } = render( + + + } /> + + + ); + + await findByTestId("preflight-check-area"); + + expect(getByText("Back: Config")).toBeDisabled(); + }); + + it("We get routed to the confirm and deploy section if the initial request succeeds and the app is not configurable and doesn't have preflights", async () => { + const slug = "my-test-app"; + server = setupServer( + http.get(`${api}/upgrade-service/app/${slug}`, () => { + return HttpResponse.json({ + isConfigurable: false, + hasPreflight: false, + }); + }) + ); + server.listen(); + + const { findByTestId, getByText } = render( + + + } /> + + + ); + + await findByTestId("preflight-check-area"); + + expect(getByText("Back: Config")).toBeDisabled(); + }); + + it("We show an error if the get info request fails", async () => { + const slug = "my-test-app"; + server = setupServer( + http.get(`${api}/upgrade-service/app/${slug}`, () => { + return new HttpResponse("Not found", { status: 404 }); + }) + ); + server.listen(); + + const { findByText } = render( + + + } /> + + + ); + + await findByText("Encountered an error"); + }); + }); +}); diff --git a/web/src/components/upgrade_service/hooks/getUpgradeInfo.test.jsx b/web/src/components/upgrade_service/hooks/getUpgradeInfo.test.tsx similarity index 78% rename from web/src/components/upgrade_service/hooks/getUpgradeInfo.test.jsx rename to web/src/components/upgrade_service/hooks/getUpgradeInfo.test.tsx index 239a0a299b..5d58134fbc 100644 --- a/web/src/components/upgrade_service/hooks/getUpgradeInfo.test.jsx +++ b/web/src/components/upgrade_service/hooks/getUpgradeInfo.test.tsx @@ -2,19 +2,20 @@ * @jest-environment jest-fixed-jsdom */ import { http, HttpResponse } from "msw"; -import { setupServer } from "msw/node"; +import { setupServer, SetupServerApi } from "msw/node"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { renderHook } from "@testing-library/react-hooks"; import { useGetUpgradeInfo } from "./getUpgradeInfo"; +import { ReactElement } from "react"; describe("useGetUpgradeInfo", () => { const api = "http://test-api"; - let server; - let queryClient; - let wrapper; + let server: SetupServerApi; + let queryClient: QueryClient; + let wrapper: ({ children }: { children: ReactElement }) => ReactElement; afterEach(() => { - // Remove any handlers you may have added + // Remove any handlers added // in individual tests (runtime handlers). server.resetHandlers(); server.close(); @@ -35,7 +36,6 @@ describe("useGetUpgradeInfo", () => { const slug = "my-test-app"; server = setupServer( http.get(`${api}/upgrade-service/app/${slug}`, () => { - console.log("On handler"); return HttpResponse.json({ isConfigurable: true, hasPreflight: false, @@ -46,12 +46,13 @@ describe("useGetUpgradeInfo", () => { const { result, waitFor } = renderHook(useGetUpgradeInfo, { initialProps: { api, slug }, + // @ts-expect-error: struggling to make the wrapper types comply, ignoring for now wrapper, }); await waitFor(() => result.current.isSuccess); - expect(result.current.data.isConfigurable).toBe.true; - expect(result.current.data.hasPreflight).toBe.false; + expect(result.current.data.isConfigurable).toStrictEqual(true); + expect(result.current.data.hasPreflight).toStrictEqual(false); }); it("non JSON response throws an error and is handled by the hook", async () => { @@ -65,6 +66,7 @@ describe("useGetUpgradeInfo", () => { const { result, waitFor } = renderHook(useGetUpgradeInfo, { initialProps: { api, slug, retry: 0 }, + // @ts-expect-error: struggling to make the wrapper types comply, ignoring for now wrapper, }); @@ -83,6 +85,7 @@ describe("useGetUpgradeInfo", () => { const { result, waitFor } = renderHook(useGetUpgradeInfo, { initialProps: { api, slug, retry: 0 }, + // @ts-expect-error: struggling to make the wrapper types comply, ignoring for now wrapper, }); @@ -101,6 +104,7 @@ describe("useGetUpgradeInfo", () => { const { result, waitFor } = renderHook(useGetUpgradeInfo, { initialProps: { api, slug, retry: 0 }, + // @ts-expect-error: struggling to make the wrapper types comply, ignoring for now wrapper, }); diff --git a/web/src/components/upgrade_service/hooks/getUpgradeInfo.tsx b/web/src/components/upgrade_service/hooks/getUpgradeInfo.tsx index 828e0e2f9d..2c6c87161f 100644 --- a/web/src/components/upgrade_service/hooks/getUpgradeInfo.tsx +++ b/web/src/components/upgrade_service/hooks/getUpgradeInfo.tsx @@ -11,6 +11,9 @@ type UpgradeInfoParams = { slug: string; }; +// Set the retries to 0 when testing +const DEFAULT_RETRY = process.env.NODE_ENV === "test" ? 0 : 3; + async function getUpgradeInfo({ api = process.env.API_ENDPOINT, slug, @@ -39,7 +42,11 @@ async function getUpgradeInfo({ } } -function useGetUpgradeInfo({ slug, api, retry = 3 }: UpgradeInfoParams) { +function useGetUpgradeInfo({ + slug, + api, + retry = DEFAULT_RETRY, +}: UpgradeInfoParams) { return useQuery({ queryFn: () => getUpgradeInfo({ slug, api }), queryKey: ["upgrade-info", slug], diff --git a/web/tsconfig.json b/web/tsconfig.json index 9fb7b0ba81..65bae8beec 100644 --- a/web/tsconfig.json +++ b/web/tsconfig.json @@ -119,7 +119,8 @@ "noFallthroughCasesInSwitch": true // Report errors for fallthrough cases in switch statement }, "include": [ - "src/**/*" // *** The files TypeScript should type check *** + "src/**/*", // *** The files TypeScript should type check *** + "config/jest-setup.ts" ], "exclude": ["node_modules", "dist"] // *** The files to not type check *** } diff --git a/web/yarn.lock b/web/yarn.lock index f6a8569057..82b3b1833c 100644 --- a/web/yarn.lock +++ b/web/yarn.lock @@ -1397,13 +1397,20 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.23.9", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": version "7.23.9" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.9.tgz#47791a15e4603bb5f905bc0753801cf21d6345f7" integrity sha512-0CX6F+BI2s9dkUqr08KFrAIZgNFj75rdBU/DjCyYLIaV/quFjkk6T+EJ2LkZHyZTbEV4L5p97mNkUsHl2wLFAw== dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.12.13": + version "7.25.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.7.tgz#7ffb53c37a8f247c8c4d335e89cdf16a2e0d0fb6" + integrity sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.12.7", "@babel/template@^7.24.7", "@babel/template@^7.25.0", "@babel/template@^7.3.3": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" @@ -10506,7 +10513,7 @@ highlight-words@1.2.2: history@^4.9.0: version "4.10.1" - resolved "https://registry.npmjs.org/history/-/history-4.10.1.tgz" + resolved "https://registry.yarnpkg.com/history/-/history-4.10.1.tgz#33371a65e3a83b267434e2b3f3b1b4c58aad4cf3" integrity sha512-36nwAD620w12kuzPAsyINPWJqlNbij+hpK1k9XRloDtym8mxzGYl2c17LnV6IAGB2Dmg4tEa7G7DlawS0+qjew== dependencies: "@babel/runtime" "^7.1.2" @@ -10891,6 +10898,11 @@ interpret@^3.1.1: resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4" integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ== +intersection-observer@^0.12.2: + version "0.12.2" + resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.12.2.tgz#4a45349cc0cd91916682b1f44c28d7ec737dc375" + integrity sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg== + invariant@^2.2.2: version "2.2.4" resolved "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz" @@ -11402,8 +11414,8 @@ is-wsl@^2.1.1, is-wsl@^2.2.0: isarray@0.0.1: version "0.0.1" - resolved "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" @@ -13908,9 +13920,9 @@ path-to-regexp@0.1.10: integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== path-to-regexp@^1.7.0: - version "1.8.0" - resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz" - integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + version "1.9.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.9.0.tgz#5dc0753acbf8521ca2e0f137b4578b917b10cf24" + integrity sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g== dependencies: isarray "0.0.1" @@ -15380,7 +15392,7 @@ resolve-from@^5.0.0: resolve-pathname@^3.0.0: version "3.0.0" - resolved "https://registry.npmjs.org/resolve-pathname/-/resolve-pathname-3.0.0.tgz" + resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd" integrity sha512-C7rARubxI8bXFNB/hqcp/4iUeIXJhJZvFPFPiSPRnhU5UPxzMFIl+2E6yY6c4k9giDJAhtV+enfA+G89N6Csng== resolve-protobuf-schema@^2.1.0: @@ -16998,14 +17010,19 @@ tiny-emitter@^2.0.0: resolved "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz" integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q== -tiny-invariant@^1.0.2, tiny-invariant@^1.3.1: +tiny-invariant@^1.0.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.3.tgz#46680b7a873a0d5d10005995eb90a70d74d60127" + integrity sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg== + +tiny-invariant@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.3.1.tgz#8560808c916ef02ecfd55e66090df23a4b7aa642" integrity sha512-AD5ih2NlSssTCwsMznbvwMZpJ1cbhkGd2uueNxzv2jDlEeZdU04JQfRnggJQ8DrcVBGjAsCKwFBbDlVNtEMlzw== tiny-warning@^1.0.0: version "1.0.3" - resolved "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz" + resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== tmpl@1.0.5: @@ -17786,7 +17803,7 @@ validate-npm-package-license@^3.0.1: value-equal@^1.0.1: version "1.0.1" - resolved "https://registry.npmjs.org/value-equal/-/value-equal-1.0.1.tgz" + resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c" integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw== vary@~1.1.2: