From 3d29702496b8d879a5a541c06a3dc4f4914bd16c Mon Sep 17 00:00:00 2001 From: Simon Li Date: Sat, 27 Sep 2025 19:40:40 +0100 Subject: [PATCH 1/6] LinkGenerator: set repo to empty if invalid --- js/packages/binderhub-react-components/src/LinkGenerator.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/packages/binderhub-react-components/src/LinkGenerator.jsx b/js/packages/binderhub-react-components/src/LinkGenerator.jsx index 6288b45c3..40b310bc7 100644 --- a/js/packages/binderhub-react-components/src/LinkGenerator.jsx +++ b/js/packages/binderhub-react-components/src/LinkGenerator.jsx @@ -201,6 +201,8 @@ export function LinkGenerator({ const results = re.exec(repo); if (results !== null && results.groups && results.groups.repo) { setRepo(results.groups.repo); + } else { + setRepo(""); } } else { setRepo(e.target.value); From 0c667a2469db90a3149114c68e919fef8c8f55f3 Mon Sep 17 00:00:00 2001 From: Simon Li Date: Sat, 27 Sep 2025 19:41:18 +0100 Subject: [PATCH 2/6] FakeProvider: make more realistic for testing --- binderhub/repoproviders.py | 9 +++++++-- testing/local-binder-mocked-hub/binderhub_config.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/binderhub/repoproviders.py b/binderhub/repoproviders.py index 538a88c3d..799901545 100644 --- a/binderhub/repoproviders.py +++ b/binderhub/repoproviders.py @@ -226,8 +226,13 @@ class FakeProvider(RepoProvider): "displayName": "Fake", "id": "fake", "enabled": False, - "spec": {"validateRegex": ".*"}, - "repo": {"label": "Fake Repo", "placeholder": "", "urlEncode": False}, + "spec": {"validateRegex": ".+"}, + "detect": {"regex": "(?.+)"}, + "repo": { + "label": "Fake Repo", + "placeholder": "example: fake", + "urlEncode": False, + }, "ref": { "enabled": False, }, diff --git a/testing/local-binder-mocked-hub/binderhub_config.py b/testing/local-binder-mocked-hub/binderhub_config.py index 142b36c34..e8ead12e7 100644 --- a/testing/local-binder-mocked-hub/binderhub_config.py +++ b/testing/local-binder-mocked-hub/binderhub_config.py @@ -14,7 +14,7 @@ c.BinderHub.use_registry = True c.BinderHub.registry_class = FakeRegistry c.BinderHub.builder_required = False -c.BinderHub.repo_providers = {"gh": FakeProvider} +c.BinderHub.repo_providers = {"fake": FakeProvider} c.BinderHub.build_class = FakeBuild # Uncomment the following line to enable BinderHub's API only mode From 17e8e18e8acab8900f171b343e4a797a2280a6f2 Mon Sep 17 00:00:00 2001 From: Simon Li Date: Sat, 27 Sep 2025 21:12:13 +0100 Subject: [PATCH 3/6] Remove invalid scripts.lint --- js/packages/binderhub-react-components/package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/js/packages/binderhub-react-components/package.json b/js/packages/binderhub-react-components/package.json index ee17021b2..28b44a050 100644 --- a/js/packages/binderhub-react-components/package.json +++ b/js/packages/binderhub-react-components/package.json @@ -7,7 +7,6 @@ }, "scripts": { "build": "esbuild src/*.jsx --loader:.ico=dataurl --bundle --external:react --outdir=dist", - "lint": "eslint binderhub/static/js js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { From 821c45dd4cb3e3dfc5836b3ae13c9e18475d4e63 Mon Sep 17 00:00:00 2001 From: Simon Li Date: Sat, 27 Sep 2025 21:59:26 +0100 Subject: [PATCH 4/6] Add initial test for LinkGenerator --- .../babel.config.js | 6 ++ .../binderhub-react-components/package.json | 8 +- .../src/LinkGenerator.test.jsx | 77 +++++++++++++++++++ 3 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 js/packages/binderhub-react-components/babel.config.js create mode 100644 js/packages/binderhub-react-components/src/LinkGenerator.test.jsx diff --git a/js/packages/binderhub-react-components/babel.config.js b/js/packages/binderhub-react-components/babel.config.js new file mode 100644 index 000000000..898ec947a --- /dev/null +++ b/js/packages/binderhub-react-components/babel.config.js @@ -0,0 +1,6 @@ +module.exports = { + presets: [ + ["@babel/preset-env", { targets: { node: "current" } }], + ["@babel/preset-react", { runtime: "automatic" }], + ], +}; diff --git a/js/packages/binderhub-react-components/package.json b/js/packages/binderhub-react-components/package.json index 28b44a050..e4ae90599 100644 --- a/js/packages/binderhub-react-components/package.json +++ b/js/packages/binderhub-react-components/package.json @@ -7,7 +7,13 @@ }, "scripts": { "build": "esbuild src/*.jsx --loader:.ico=dataurl --bundle --external:react --outdir=dist", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jest" + }, + "jest": { + "testEnvironment": "jsdom", + "setupFilesAfterEnv": [ + "/../../../setupTests.js" + ] }, "repository": { "type": "git", diff --git a/js/packages/binderhub-react-components/src/LinkGenerator.test.jsx b/js/packages/binderhub-react-components/src/LinkGenerator.test.jsx new file mode 100644 index 000000000..677b400a5 --- /dev/null +++ b/js/packages/binderhub-react-components/src/LinkGenerator.test.jsx @@ -0,0 +1,77 @@ +import { render, screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { LinkGenerator } from "./LinkGenerator"; +import { useState } from "react"; + +const mockProviders = window.pageConfig.repoProviders; + +const publicBaseUrl = new URL("https://example.org/"); + +function TestLinkGeneratorWrapper() { + const [selectedProvider, setSelectedProvider] = useState(mockProviders[0]); + const [repo, setRepo] = useState(""); + const [reference, setReference] = useState(""); + const [urlPath, setUrlPath] = useState(""); + const [isLaunching, setIsLaunching] = useState(false); + + return ( + + ); +} + +describe("LinkGenerator", () => { + it("updates launch-url from repo, ref and file", async () => { + const user = userEvent.setup(); + render(); + + // This lookup uses the aria label + const repoInput = screen.getByRole("textbox", { + name: "Enter repository URL", + }); + await user.type(repoInput, "my-org/my-repo"); + + const refInput = screen.getByLabelText("Git ref (branch, tag, or commit)"); + await user.type(refInput, "my-branch"); + + const pathInput = screen.getByLabelText("File to open (in JupyterLab)"); + await user.type(pathInput, "notebooks/test.ipynb"); + + const expectedUrl = + "https://example.org/v2/gh/my-org/my-repo/my-branch?urlpath=%2Fdoc%2Ftree%2Fnotebooks%2Ftest.ipynb"; + expect(screen.getByTestId("launch-url").textContent).toBe(expectedUrl); + }); + + it("renders initial placeholder and restores it if repo is deleted", async () => { + const user = userEvent.setup(); + render(); + + const defaultLaunchUrl = + "Fill in the fields to see a URL for sharing your Binder."; + expect(screen.getByTestId("launch-url").textContent).toBe(defaultLaunchUrl); + + const repoInput = screen.getByRole("textbox", { + name: "Enter repository URL", + }); + await user.type(repoInput, "x"); + + expect(screen.getByTestId("launch-url").textContent).toBe( + "https://example.org/v2/gh/x/HEAD", + ); + + await user.type(repoInput, "{backspace}"); + expect(screen.getByTestId("launch-url").textContent).toBe(defaultLaunchUrl); + }); +}); From 1233f02d1a8db2b40bc85e969d42e95fe04e7005 Mon Sep 17 00:00:00 2001 From: Simon Li Date: Sat, 27 Sep 2025 22:01:56 +0100 Subject: [PATCH 5/6] GH jest workflow should be triggered by everything under js/** --- .github/workflows/jest.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/jest.yml b/.github/workflows/jest.yml index 0223df31c..ea29486da 100644 --- a/.github/workflows/jest.yml +++ b/.github/workflows/jest.yml @@ -5,12 +5,12 @@ on: pull_request: paths: - "binderhub/static/js/**" - - "js/packages/binderhub-client/**" + - "js/**" - ".github/workflows/jest.yml" push: paths: - "binderhub/static/js/**" - - "js/packages/binderhub-client/**" + - "js/**" - ".github/workflows/jest.yml" branches-ignore: - "dependabot/**" From 8c841c37aa9fe92569c2f11033c8b79c72bd0d5c Mon Sep 17 00:00:00 2001 From: Simon Li Date: Tue, 30 Sep 2025 14:23:32 +0100 Subject: [PATCH 6/6] Add preset-env preset-react to binderhub-react-components package.json --- js/packages/binderhub-react-components/package.json | 2 ++ 1 file changed, 2 insertions(+) diff --git a/js/packages/binderhub-react-components/package.json b/js/packages/binderhub-react-components/package.json index e4ae90599..f242f327c 100644 --- a/js/packages/binderhub-react-components/package.json +++ b/js/packages/binderhub-react-components/package.json @@ -29,6 +29,8 @@ }, "homepage": "https://github.com/jupyterhub/binderhub#readme", "devDependencies": { + "@babel/preset-env": "^7.28.3", + "@babel/preset-react": "^7.27.1", "esbuild": "^0.25.6", "eslint": "^9.31.0" },