From 588f603d6568c294625cf68ae8019772ad0b016c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 10:04:10 +0000 Subject: [PATCH 1/5] Initial plan From 3c07716059cfc8ba1f018fdb02973f80cf71da05 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 10:10:12 +0000 Subject: [PATCH 2/5] Add wait helper function for async detection Implement the wait() async helper function that waits for a detection to return true by repeatedly checking it on each animation frame. This is useful for DOM-based detections that need to wait for elements to appear before the document is fully loaded. - Add wait() function that loops using requestAnimationFrame - Add comprehensive tests for the wait function - Update test infrastructure to support async detection functions - Add JSDoc documentation with usage examples Co-authored-by: fregante <1402241+fregante@users.noreply.github.com> --- index.test.ts | 60 ++++++++++++++++++++++++++++++++++++++++++++++++--- index.ts | 29 +++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 3 deletions(-) diff --git a/index.test.ts b/index.test.ts index 944d520d..382f3190 100644 --- a/index.test.ts +++ b/index.test.ts @@ -5,8 +5,15 @@ import stripIndent from 'strip-indent'; import {getAllUrls, getTests} from './collector.js'; import * as pageDetect from './index.js'; -(globalThis as any).document = {title: ''}; +(globalThis as any).document = {title: '', readyState: 'loading'}; (globalThis as any).location = new URL('https://github.com/'); +(globalThis as any).requestAnimationFrame = (callback: FrameRequestCallback) => setTimeout(() => { + callback(Date.now()); +}, 0) as unknown as number; + +(globalThis as any).cancelAnimationFrame = (id: number) => { + clearTimeout(id); +}; const allUrls = getAllUrls(); @@ -15,12 +22,20 @@ for (const [detectName, detect] of Object.entries(pageDetect)) { continue; } + // Skip wait and other utility functions + if (detectName === 'wait') { + continue; + } + const validURLs = getTests(detectName); if (validURLs[0] === 'combinedTestOnly' || String(detect).startsWith('() =>')) { continue; } + // Type assertion for TypeScript to understand this is a detection function + const detectionFn = detect as (url?: URL | HTMLAnchorElement | Location) => boolean; + test(detectName + ' has tests', () => { assert.ok( Array.isArray(validURLs), @@ -35,7 +50,7 @@ for (const [detectName, detect] of Object.entries(pageDetect)) { for (const url of validURLs) { test(`${detectName} ${url.replace('https://github.com', '')}`, () => { assert.ok( - detect(new URL(url)), + detectionFn(new URL(url)), stripIndent(` Is this URL \`${detectName}\`? ${url.replace('https://github.com', '')} @@ -56,7 +71,7 @@ for (const [detectName, detect] of Object.entries(pageDetect)) { if (!validURLs.includes(url)) { test(`${detectName} NO ${url}`, () => { assert.equal( - detect(new URL(url)), + detectionFn(new URL(url)), false, stripIndent(` Is this URL \`${detectName}\`? @@ -281,3 +296,42 @@ test('parseRepoExplorerTitle', () => { undefined, ); }); + +test('wait - immediately true', async () => { + const detection = () => true; + const result = await pageDetect.wait(detection); + assert.equal(result, true); +}); + +test('wait - becomes true', async () => { + let callCount = 0; + const detection = () => { + callCount++; + return callCount >= 3; + }; + + const result = await pageDetect.wait(detection); + assert.equal(result, true); + assert.ok(callCount >= 3); +}); + +test('wait - false when document complete', async () => { + // Save original state + const originalReadyState = Object.getOwnPropertyDescriptor(document, 'readyState'); + + // Mock document.readyState to be 'complete' + Object.defineProperty(document, 'readyState', { + writable: true, + configurable: true, + value: 'complete', + }); + + const detection = () => false; + const result = await pageDetect.wait(detection); + assert.equal(result, false); + + // Restore original state + if (originalReadyState) { + Object.defineProperty(document, 'readyState', originalReadyState); + } +}); diff --git a/index.ts b/index.ts index 3e9988b8..6293ed0a 100644 --- a/index.ts +++ b/index.ts @@ -4,6 +4,35 @@ import {addTests} from './collector.ts'; const $ = (selector: string) => document.querySelector(selector); const exists = (selector: string) => Boolean($(selector)); +/** + * Waits for a detection to return true by repeatedly checking it on each animation frame. + * Useful for DOM-based detections that need to wait for elements to appear. + * @param detection - A detection function to check repeatedly + * @returns A promise that resolves to the final result of the detection + * @example + * ``` + * import {wait, isOrganizationProfile} from 'github-url-detection'; + * + * async function init() { + * if (!await wait(isOrganizationProfile)) { + * return; + * } + * // Do something when on organization profile + * } + * ``` + */ +export async function wait(detection: () => boolean): Promise { + // eslint-disable-next-line no-await-in-loop -- We need to wait on each frame + while (!detection() && document.readyState !== 'complete') { + // eslint-disable-next-line no-await-in-loop + await new Promise(resolve => { + requestAnimationFrame(resolve); + }); + } + + return detection(); +} + const combinedTestOnly = ['combinedTestOnly']; // To be used only to skip tests of combined functions, i.e. isPageA() || isPageB() TEST: addTests('__urls_that_dont_match__', [ From 95239d0dd15dc1599736651fb27521dbbdcf5105 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 10:11:21 +0000 Subject: [PATCH 3/5] Add wait helper documentation to README Co-authored-by: fregante <1402241+fregante@users.noreply.github.com> --- readme.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/readme.md b/readme.md index f2fba7a7..5784b4d7 100644 --- a/readme.md +++ b/readme.md @@ -67,6 +67,30 @@ if (pageDetect.isOrganizationProfile()) { } ``` +### Async detections with `wait` + +The `wait` helper function allows you to wait for a detection to become true by repeatedly checking it on each animation frame. This is useful for DOM-based detections that need to wait for elements to appear before the document is fully loaded. + +```js +import {wait, isOrganizationProfile} from 'github-url-detection'; + +async function init() { + // Wait for the detection to return true or for the document to be complete + if (!await wait(isOrganizationProfile)) { + return; // Not an organization profile + } + + // The page is now confirmed to be an organization profile + console.log('On organization profile!'); +} +``` + +The `wait` function: +- Repeatedly calls the detection function on each animation frame +- Stops when the detection returns `true` or when `document.readyState` is `'complete'` +- Returns the final result of the detection +- Works with any detection function that returns a boolean + ## Related - [github-reserved-names](https://github.com/Mottie/github-reserved-names) - Get a list, or check if a user or organization name is reserved by GitHub. From 4efdd75191ef2a7cfb7eb5e0924e2de71ed50f78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 10:13:03 +0000 Subject: [PATCH 4/5] Fix test cleanup in wait test to handle undefined descriptor Co-authored-by: fregante <1402241+fregante@users.noreply.github.com> --- index.test.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/index.test.ts b/index.test.ts index 382f3190..e3da7f1f 100644 --- a/index.test.ts +++ b/index.test.ts @@ -333,5 +333,8 @@ test('wait - false when document complete', async () => { // Restore original state if (originalReadyState) { Object.defineProperty(document, 'readyState', originalReadyState); + } else { + // If readyState wasn't a property before, delete it + delete (document as any).readyState; } }); From b177e7395084244e31c1d734cc1e5789dd25f4e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 24 Jan 2026 10:49:35 +0000 Subject: [PATCH 5/5] Rename wait to waitFor and export via utils only - Renamed wait() to waitFor() as requested - Moved export to utils object instead of direct export - Reverted type assertion changes in index.test.ts - Updated README to show usage via utils.waitFor() - Updated JSDoc example to reflect new API Co-authored-by: fregante <1402241+fregante@users.noreply.github.com> --- index.test.ts | 24 ++++++++---------------- index.ts | 7 ++++--- readme.md | 10 +++++----- 3 files changed, 17 insertions(+), 24 deletions(-) diff --git a/index.test.ts b/index.test.ts index e3da7f1f..d18d2a45 100644 --- a/index.test.ts +++ b/index.test.ts @@ -22,20 +22,12 @@ for (const [detectName, detect] of Object.entries(pageDetect)) { continue; } - // Skip wait and other utility functions - if (detectName === 'wait') { - continue; - } - const validURLs = getTests(detectName); if (validURLs[0] === 'combinedTestOnly' || String(detect).startsWith('() =>')) { continue; } - // Type assertion for TypeScript to understand this is a detection function - const detectionFn = detect as (url?: URL | HTMLAnchorElement | Location) => boolean; - test(detectName + ' has tests', () => { assert.ok( Array.isArray(validURLs), @@ -50,7 +42,7 @@ for (const [detectName, detect] of Object.entries(pageDetect)) { for (const url of validURLs) { test(`${detectName} ${url.replace('https://github.com', '')}`, () => { assert.ok( - detectionFn(new URL(url)), + detect(new URL(url)), stripIndent(` Is this URL \`${detectName}\`? ${url.replace('https://github.com', '')} @@ -71,7 +63,7 @@ for (const [detectName, detect] of Object.entries(pageDetect)) { if (!validURLs.includes(url)) { test(`${detectName} NO ${url}`, () => { assert.equal( - detectionFn(new URL(url)), + detect(new URL(url)), false, stripIndent(` Is this URL \`${detectName}\`? @@ -297,25 +289,25 @@ test('parseRepoExplorerTitle', () => { ); }); -test('wait - immediately true', async () => { +test('waitFor - immediately true', async () => { const detection = () => true; - const result = await pageDetect.wait(detection); + const result = await pageDetect.utils.waitFor(detection); assert.equal(result, true); }); -test('wait - becomes true', async () => { +test('waitFor - becomes true', async () => { let callCount = 0; const detection = () => { callCount++; return callCount >= 3; }; - const result = await pageDetect.wait(detection); + const result = await pageDetect.utils.waitFor(detection); assert.equal(result, true); assert.ok(callCount >= 3); }); -test('wait - false when document complete', async () => { +test('waitFor - false when document complete', async () => { // Save original state const originalReadyState = Object.getOwnPropertyDescriptor(document, 'readyState'); @@ -327,7 +319,7 @@ test('wait - false when document complete', async () => { }); const detection = () => false; - const result = await pageDetect.wait(detection); + const result = await pageDetect.utils.waitFor(detection); assert.equal(result, false); // Restore original state diff --git a/index.ts b/index.ts index 6293ed0a..a14492cd 100644 --- a/index.ts +++ b/index.ts @@ -11,17 +11,17 @@ const exists = (selector: string) => Boolean($(selector)); * @returns A promise that resolves to the final result of the detection * @example * ``` - * import {wait, isOrganizationProfile} from 'github-url-detection'; + * import {utils} from 'github-url-detection'; * * async function init() { - * if (!await wait(isOrganizationProfile)) { + * if (!await utils.waitFor(isOrganizationProfile)) { * return; * } * // Do something when on organization profile * } * ``` */ -export async function wait(detection: () => boolean): Promise { +async function waitFor(detection: () => boolean): Promise { // eslint-disable-next-line no-await-in-loop -- We need to wait on each frame while (!detection() && document.readyState !== 'complete') { // eslint-disable-next-line no-await-in-loop @@ -991,4 +991,5 @@ export const utils = { getCleanGistPathname, getRepositoryInfo: getRepo, parseRepoExplorerTitle, + waitFor, }; diff --git a/readme.md b/readme.md index 5784b4d7..bd41b2c0 100644 --- a/readme.md +++ b/readme.md @@ -67,16 +67,16 @@ if (pageDetect.isOrganizationProfile()) { } ``` -### Async detections with `wait` +### Async detections with `waitFor` -The `wait` helper function allows you to wait for a detection to become true by repeatedly checking it on each animation frame. This is useful for DOM-based detections that need to wait for elements to appear before the document is fully loaded. +The `waitFor` helper function allows you to wait for a detection to become true by repeatedly checking it on each animation frame. This is useful for DOM-based detections that need to wait for elements to appear before the document is fully loaded. ```js -import {wait, isOrganizationProfile} from 'github-url-detection'; +import {utils, isOrganizationProfile} from 'github-url-detection'; async function init() { // Wait for the detection to return true or for the document to be complete - if (!await wait(isOrganizationProfile)) { + if (!await utils.waitFor(isOrganizationProfile)) { return; // Not an organization profile } @@ -85,7 +85,7 @@ async function init() { } ``` -The `wait` function: +The `waitFor` function: - Repeatedly calls the detection function on each animation frame - Stops when the detection returns `true` or when `document.readyState` is `'complete'` - Returns the final result of the detection