From 337d63f49c7a0d3e3c0db5a4b24dd2bb628f112e Mon Sep 17 00:00:00 2001 From: gabalafou Date: Tue, 15 Oct 2024 17:06:30 +0300 Subject: [PATCH 1/3] Add React router tests --- src/layouts/PageLayout.tsx | 1 + src/preferences.tsx | 30 ++++----- test/playwright/memory-router-test.html | 12 ++++ test/playwright/test_react_router.py | 88 +++++++++++++++++++++++++ webpack.config.js | 10 ++- 5 files changed, 123 insertions(+), 18 deletions(-) create mode 100644 test/playwright/memory-router-test.html create mode 100644 test/playwright/test_react_router.py diff --git a/src/layouts/PageLayout.tsx b/src/layouts/PageLayout.tsx index f9ab730f..12f5599d 100644 --- a/src/layouts/PageLayout.tsx +++ b/src/layouts/PageLayout.tsx @@ -57,6 +57,7 @@ export const PageLayout = () => { justifyContent: "center", height: "100%" }} + data-testid="no-environment-selected" > Select an environment to show details diff --git a/src/preferences.tsx b/src/preferences.tsx index 34d0ba94..07a1efc2 100644 --- a/src/preferences.tsx +++ b/src/preferences.tsx @@ -17,50 +17,50 @@ export interface IPreferences { routerType: "browser" | "memory"; } -const { condaStoreConfig = {} } = - typeof window !== "undefined" && (window as any); +let condaStoreConfig: any = {}; +if (typeof window !== "undefined" && "condaStoreConfig" in window) { + condaStoreConfig = window.condaStoreConfig; +} export const prefDefault: Readonly = { apiUrl: - process.env.REACT_APP_API_URL ?? condaStoreConfig.REACT_APP_API_URL ?? + process.env.REACT_APP_API_URL ?? "http://localhost:8080/conda-store/", authMethod: - (process.env.REACT_APP_AUTH_METHOD as IPreferences["authMethod"]) ?? (condaStoreConfig.REACT_APP_AUTH_METHOD as IPreferences["authMethod"]) ?? + (process.env.REACT_APP_AUTH_METHOD as IPreferences["authMethod"]) ?? "cookie", authToken: - process.env.REACT_APP_AUTH_TOKEN ?? condaStoreConfig.REACT_APP_AUTH_TOKEN ?? + process.env.REACT_APP_AUTH_TOKEN ?? "", loginUrl: - process.env.REACT_APP_LOGIN_PAGE_URL ?? condaStoreConfig.REACT_APP_LOGIN_PAGE_URL ?? + process.env.REACT_APP_LOGIN_PAGE_URL ?? "http://localhost:8080/conda-store/login?next=", styleType: - process.env.REACT_APP_STYLE_TYPE ?? condaStoreConfig.REACT_APP_STYLE_TYPE ?? + process.env.REACT_APP_STYLE_TYPE ?? "green-accent", - showAuthButton: process.env.REACT_APP_SHOW_AUTH_BUTTON - ? JSON.parse(process.env.REACT_APP_SHOW_AUTH_BUTTON) - : condaStoreConfig !== undefined && - condaStoreConfig.REACT_APP_SHOW_AUTH_BUTTON !== undefined - ? JSON.parse(condaStoreConfig.REACT_APP_SHOW_AUTH_BUTTON) - : true, + showAuthButton: + (condaStoreConfig.REACT_APP_SHOW_AUTH_BUTTON ?? + process.env.REACT_APP_SHOW_AUTH_BUTTON ?? + "true") === "true", logoutUrl: - process.env.REACT_APP_LOGOUT_PAGE_URL ?? condaStoreConfig.REACT_APP_LOGOUT_PAGE_URL ?? + process.env.REACT_APP_LOGOUT_PAGE_URL ?? "http://localhost:8080/conda-store/logout?next=/", routerType: - process.env.REACT_APP_ROUTER_TYPE ?? condaStoreConfig.REACT_APP_ROUTER_TYPE ?? + process.env.REACT_APP_ROUTER_TYPE ?? "browser" }; diff --git a/test/playwright/memory-router-test.html b/test/playwright/memory-router-test.html new file mode 100644 index 00000000..3439116a --- /dev/null +++ b/test/playwright/memory-router-test.html @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/test/playwright/test_react_router.py b/test/playwright/test_react_router.py new file mode 100644 index 00000000..e2108e6e --- /dev/null +++ b/test/playwright/test_react_router.py @@ -0,0 +1,88 @@ +"""Test app with different types of React routers + +- browser router (uses the history API) +- memory router (uses in-app memory) + +Ref: https://reactrouter.com/en/main/routers/create-memory-router +""" + +import pytest +import re +from playwright.sync_api import Page, expect + + +@pytest.fixture +def test_config(): + return {"base_url": "http://localhost:8000"} + + +def test_browser_router_200_ok(page: Page, test_config): + """With browser router, a known route should show the corresponding view + """ + # Check that when going to a known route (in this case, the route to create + # a new environment), the app loads the view for that route. + page.goto(test_config["base_url"] + "/default/new-environment") + + # We know we are at the correct view (i.e., new environment form) if there + # is a textbox to enter the name of the new environment. + expect(page.get_by_role("textbox", name="environment name")).to_be_visible() + + +def test_memory_router_200_ok(): + """With memory router, all routes are 200 (OK) so there's nothing to test there + """ + pass + + +def test_browser_router_404_not_found(page: Page, test_config): + """With browser router, an unknown route should result in a 404 not found error + """ + page.goto(test_config["base_url"] + "/this-is-not-an-app-route") + expect(page.get_by_text("404")).to_be_visible() + + +def test_memory_router_404_not_found(page: Page, test_config): + """The memory router has been configured to load the root view at any route + """ + # The route `/memory-router-test.html` is not a route recognized by the + # React app. With the browser router, an unknown route would give a 404. + page.goto(test_config["base_url"] + "/memory-router-test.html") + expect(page.get_by_test_id("no-environment-selected")).to_be_visible() + + +def test_browser_router_updates_location(page: Page, test_config): + """With browser router, following a link should update browser URL + """ + # Go to root view and verify that it loaded + page.goto(test_config["base_url"]) + expect(page.get_by_test_id("no-environment-selected")).to_be_visible() + + # Get and click link to "create new environment" + page.get_by_role("button", name="default").get_by_role( + "link", + # Note the accessible name is determined by the aria-label, + # not the link text + name=re.compile("new.*environment", re.IGNORECASE), + ).click() + + # With browser router, the window location should update in response to + # clicking an app link + expect(page).to_have_url(re.compile("/default/new-environment")) + + +def test_memory_router_does_not_update_location(page: Page, test_config): + """With memory router, following a link should NOT update browser URL + """ + page.goto(test_config["base_url"] + "/memory-router-test.html") + + # Get and click link to "create new environment" + page.get_by_role("button", name="default").get_by_role( + "link", + # Note the accessible name is determined by the aria-label, + # not the link text + name=re.compile("new.*environment", re.IGNORECASE), + ).click() + + # With memory router, the window location should **not** update in response + # to clicking an app link + expect(page).to_have_url(re.compile("/memory-router-test.html")) diff --git a/webpack.config.js b/webpack.config.js index d97a2b34..6cc21c38 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,7 +1,7 @@ /* * Copyright (c) 2020, conda-store development team * - * This file is distributed under the terms of the BSD 3 Clause license. + * This file is distributed under the terms of the BSD 3 Clause license. * The full license can be found in the LICENSE file. */ @@ -18,7 +18,7 @@ const isProd = process.env.NODE_ENV === "production"; const ASSET_PATH = isProd ? "" : "/"; const version = packageJson.version; -// Calculate hash based on content, will be used when generating production +// Calculate hash based on content, will be used when generating production // bundles const cssLoader = { loader: "css-loader", @@ -92,6 +92,10 @@ module.exports = { }, plugins: [ new HtmlWebpackPlugin({ title: "conda-store" }), + new HtmlWebpackPlugin({ + filename: "memory-router-test.html", + template: "test/playwright/memory-router-test.html", + }), new MiniCssExtractPlugin({ filename: "[name].css", }), @@ -101,7 +105,7 @@ module.exports = { new webpack.DefinePlugin({ 'process.env.VERSION': JSON.stringify(version), }), - // Add comment to generated files indicating the hash and ui version + // Add comment to generated files indicating the hash and ui version // this is helpful for vendoring with server new webpack.BannerPlugin({ banner: `file: [file], fullhash:[fullhash] - ui version: ${version}`, From 3b6a1d7bbff39c7a91607f6baedc97cf306ef6a4 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Mon, 21 Oct 2024 19:32:18 +0300 Subject: [PATCH 2/3] fix bad merge --- src/preferences.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/preferences.tsx b/src/preferences.tsx index be350179..e792103e 100644 --- a/src/preferences.tsx +++ b/src/preferences.tsx @@ -68,8 +68,8 @@ export const prefDefault: Readonly = { "browser", urlBasename: - process.env.REACT_APP_URL_BASENAME ?? condaStoreConfig.REACT_APP_URL_BASENAME ?? + process.env.REACT_APP_URL_BASENAME ?? "/" }; From 91b84c5371aea04d22efd2d98fefd020f61b5fc5 Mon Sep 17 00:00:00 2001 From: gabalafou Date: Thu, 24 Oct 2024 11:04:18 +0200 Subject: [PATCH 3/3] case-insensitive true --- src/preferences.tsx | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/preferences.tsx b/src/preferences.tsx index e792103e..12706f88 100644 --- a/src/preferences.tsx +++ b/src/preferences.tsx @@ -33,8 +33,8 @@ export const prefDefault: Readonly = { "http://localhost:8080/conda-store/", authMethod: - (condaStoreConfig.REACT_APP_AUTH_METHOD as IPreferences["authMethod"]) ?? - (process.env.REACT_APP_AUTH_METHOD as IPreferences["authMethod"]) ?? + condaStoreConfig.REACT_APP_AUTH_METHOD ?? + process.env.REACT_APP_AUTH_METHOD ?? "cookie", authToken: @@ -52,10 +52,11 @@ export const prefDefault: Readonly = { process.env.REACT_APP_STYLE_TYPE ?? "green-accent", - showAuthButton: - (condaStoreConfig.REACT_APP_SHOW_AUTH_BUTTON ?? + showAuthButton: /true/i.test( + condaStoreConfig.REACT_APP_SHOW_AUTH_BUTTON ?? process.env.REACT_APP_SHOW_AUTH_BUTTON ?? - "true") === "true", + "true" + ), logoutUrl: condaStoreConfig.REACT_APP_LOGOUT_PAGE_URL ??