diff --git a/.eslintrc.js b/.eslintrc.js index db4d77c42..92becc44b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,6 +3,8 @@ module.exports = { browser: true, jquery: true, "jest/globals": true, + node: true, + es6: true, }, extends: ["eslint:recommended"], ignorePatterns: ["**/dist"], diff --git a/js/packages/binderhub-client/lib/autodetect.js b/js/packages/binderhub-client/lib/autodetect.js index 6de1d2579..6127f7200 100644 --- a/js/packages/binderhub-client/lib/autodetect.js +++ b/js/packages/binderhub-client/lib/autodetect.js @@ -1,10 +1,3 @@ -import { fetch as fetchPolyfill } from "whatwg-fetch"; - -// Use native browser fetch if available, and use the polyfill if not available -// (e.g. in tests https://github.com/jestjs/jest/issues/13834#issuecomment-1407375787) -// @todo: this is only a problem in the jest tests, so get rid of this and mock fetch instead -const fetch = window.fetch || fetchPolyfill; - /** * Dict holding cached values of API request to _config endpoint for base URL */ @@ -50,7 +43,7 @@ export async function detect(baseUrl, text) { return { providerPrefix: provider, repository: m.groups.repo, - ref: m.groups.ref, + ref: m.groups.ref || null, path: m.groups.filepath || m.groups.urlpath || null, pathType: m.groups.filepath ? "filepath" diff --git a/js/packages/binderhub-client/package.json b/js/packages/binderhub-client/package.json index 089c12986..b2ab42c3c 100644 --- a/js/packages/binderhub-client/package.json +++ b/js/packages/binderhub-client/package.json @@ -14,8 +14,7 @@ }, "homepage": "https://github.com/jupyterhub/binderhub#readme", "dependencies": { - "event-iterator": "^2.0.0", "event-source-polyfill": "^1.0.31", - "whatwg-fetch": "^3.6.19" + "event-iterator": "^2.0.0" } } diff --git a/js/packages/binderhub-client/tests/autodetect.test.js b/js/packages/binderhub-client/tests/autodetect.test.js new file mode 100644 index 000000000..73ca91929 --- /dev/null +++ b/js/packages/binderhub-client/tests/autodetect.test.js @@ -0,0 +1,119 @@ +import { getRepoProviders, detect } from "../lib/autodetect"; +import { readFileSync } from "node:fs"; + +const mybinderConfig = JSON.parse( + readFileSync(`${__dirname}/fixtures/repoprovider-config.json`, { + encoding: "utf-8", + }), +); + +// Mock fetch() +// https://www.leighhalliday.com/mock-fetch-jest +global.fetch = jest.fn((url) => { + if (url == "https://binder.example.org/_config") { + return Promise.resolve({ + json: () => Promise.resolve(mybinderConfig), + }); + } + return Promise.reject(`Unexpected URL ${url}`); +}); + +beforeEach(() => { + fetch.mockClear(); +}); + +test("getRepoProviders requests and caches the repo provider configs", async () => { + const config = await getRepoProviders("https://binder.example.org"); + expect(config).toEqual(mybinderConfig); + + await getRepoProviders("https://binder.example.org"); + expect(fetch).toHaveBeenCalledTimes(1); +}); + +test("detect returns null if no provider matches", async () => { + const result = await detect( + "https://binder.example.org", + "https://github.com/binder-examples/conda/pulls", + ); + expect(result).toBeNull(); +}); + +test("detect parses a repo with no path", async () => { + const expected = { + providerPrefix: "gh", + repository: "binder-examples/conda", + ref: null, + path: null, + pathType: null, + providerName: "GitHub", + }; + const result = await detect( + "https://binder.example.org", + "https://github.com/binder-examples/conda", + ); + expect(result).toEqual(expected); +}); + +test("detect parses a repo with a ref but no path", async () => { + const expected = { + providerPrefix: "gh", + repository: "binder-examples/conda", + ref: "abc", + path: null, + pathType: null, + providerName: "GitHub", + }; + const result = await detect( + "https://binder.example.org", + "https://github.com/binder-examples/conda/tree/abc", + ); + expect(result).toEqual(expected); +}); + +test("detect parses a repo with a ref and file path", async () => { + const expected = { + providerPrefix: "gh", + repository: "binder-examples/conda", + ref: "f00a783", + path: "index.ipynb", + pathType: "filepath", + providerName: "GitHub", + }; + const result = await detect( + "https://binder.example.org", + "https://github.com/binder-examples/conda/blob/f00a783/index.ipynb", + ); + expect(result).toEqual(expected); +}); + +test("detect parses a repo with a ref and directory path", async () => { + const expected = { + providerPrefix: "gh", + repository: "binder-examples/conda", + ref: "f00a783", + path: ".github/workflows", + pathType: "urlpath", + providerName: "GitHub", + }; + const result = await detect( + "https://binder.example.org", + "https://github.com/binder-examples/conda/tree/f00a783/.github/workflows", + ); + expect(result).toEqual(expected); +}); + +test("detect checks other repo providers", async () => { + const expected = { + providerPrefix: "gl", + repository: "gitlab-org/gitlab-foss", + ref: "v16.4.4", + path: "README.md", + pathType: "filepath", + providerName: "GitLab.com", + }; + const result = await detect( + "https://binder.example.org", + "https://gitlab.com/gitlab-org/gitlab-foss/-/blob/v16.4.4/README.md", + ); + expect(result).toEqual(expected); +}); diff --git a/js/packages/binderhub-client/tests/fixtures/repoprovider-config.json b/js/packages/binderhub-client/tests/fixtures/repoprovider-config.json new file mode 100644 index 000000000..b9cbab818 --- /dev/null +++ b/js/packages/binderhub-client/tests/fixtures/repoprovider-config.json @@ -0,0 +1,74 @@ +{ + "gh": { + "text": "GitHub repository name or URL", + "tag_text": "Git ref (branch, tag, or commit)", + "ref_prop_disabled": false, + "label_prop_disabled": false, + "display_name": "GitHub", + "regex_detect": [ + "^https://github\\.com/(?[^/]+/[^/]+)(/blob/(?[^/]+)(/(?.+))?)?$", + "^https://github\\.com/(?[^/]+/[^/]+)(/tree/(?[^/]+)(/(?.+))?)?$" + ] + }, + "gist": { + "text": "Gist ID (username/gistId) or URL", + "tag_text": "Git commit SHA", + "ref_prop_disabled": false, + "label_prop_disabled": false, + "display_name": "Gist", + "regex_detect": [ + "^https://gist\\.github\\.com/(?[^/]+/[^/]+)(/(?[^/]+))?$" + ] + }, + "git": { + "text": "Arbitrary git repository URL (http://git.example.com/repo)", + "tag_text": "Git ref (branch, tag, or commit)", + "ref_prop_disabled": false, + "label_prop_disabled": false, + "display_name": "Git repository", + "regex_detect": null + }, + "gl": { + "text": "GitLab.com repository or URL", + "tag_text": "Git ref (branch, tag, or commit)", + "ref_prop_disabled": false, + "label_prop_disabled": false, + "display_name": "GitLab.com", + "regex_detect": [ + "^https://gitlab\\.com/(?[^/]+/[^/]+(/[^/-][^/]+)*)(/-/blob/(?[^/]+)(/(?.+))?)?$", + "^https://gitlab\\.com/(?[^/]+/[^/]+(/[^/-][^/]+)*)(/-/tree/(?[^/]+)(/(?.+))?)?$" + ] + }, + "zenodo": { + "text": "Zenodo DOI (10.5281/zenodo.3242074)", + "tag_text": "Git ref (branch, tag, or commit)", + "ref_prop_disabled": true, + "label_prop_disabled": true, + "display_name": "Zenodo DOI", + "regex_detect": null + }, + "figshare": { + "text": "Figshare DOI (10.6084/m9.figshare.9782777.v1)", + "tag_text": "Git ref (branch, tag, or commit)", + "ref_prop_disabled": true, + "label_prop_disabled": true, + "display_name": "Figshare DOI", + "regex_detect": null + }, + "hydroshare": { + "text": "Hydroshare resource id or URL", + "tag_text": "Git ref (branch, tag, or commit)", + "ref_prop_disabled": true, + "label_prop_disabled": true, + "display_name": "Hydroshare resource", + "regex_detect": null + }, + "dataverse": { + "text": "Dataverse DOI (10.7910/DVN/TJCLKP)", + "tag_text": "Git ref (branch, tag, or commit)", + "ref_prop_disabled": true, + "label_prop_disabled": true, + "display_name": "Dataverse DOI", + "regex_detect": null + } +}