Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -281,3 +288,45 @@ test('parseRepoExplorerTitle', () => {
undefined,
);
});

test('waitFor - immediately true', async () => {
const detection = () => true;
const result = await pageDetect.utils.waitFor(detection);
assert.equal(result, true);
});

test('waitFor - becomes true', async () => {
let callCount = 0;
const detection = () => {
callCount++;
return callCount >= 3;
};

const result = await pageDetect.utils.waitFor(detection);
assert.equal(result, true);
assert.ok(callCount >= 3);
});

test('waitFor - 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.utils.waitFor(detection);
assert.equal(result, false);

// 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;
}
});
30 changes: 30 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,35 @@ import {addTests} from './collector.ts';
const $ = <E extends Element>(selector: string) => document.querySelector<E>(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 {utils} from 'github-url-detection';
*
* async function init() {
* if (!await utils.waitFor(isOrganizationProfile)) {
* return;
* }
* // Do something when on organization profile
* }
* ```
*/
async function waitFor(detection: () => boolean): Promise<boolean> {
// 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__', [
Expand Down Expand Up @@ -962,4 +991,5 @@ export const utils = {
getCleanGistPathname,
getRepositoryInfo: getRepo,
parseRepoExplorerTitle,
waitFor,
};
24 changes: 24 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,30 @@ if (pageDetect.isOrganizationProfile()) {
}
```

### Async detections with `waitFor`

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 {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 utils.waitFor(isOrganizationProfile)) {
return; // Not an organization profile
}

// The page is now confirmed to be an organization profile
console.log('On organization profile!');
}
```

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
- 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.
Expand Down