Skip to content

Commit 03f32c6

Browse files
committed
feat(browser): implement navigate utility for SPA and cross-origin navigation
1 parent 80ae17f commit 03f32c6

File tree

5 files changed

+142
-2
lines changed

5 files changed

+142
-2
lines changed

packages/browser/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export * from './__legacy__/worker/worker-receiver';
4646
export {AsgardeoBrowserConfig} from './models/config';
4747

4848
export {default as hasAuthParamsInUrl} from './utils/hasAuthParamsInUrl';
49+
export {default as navigate} from './utils/navigate';
4950

5051
export {default as AsgardeoBrowserClient} from './AsgardeoBrowserClient';
5152

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
import {vi} from 'vitest';
20+
import navigate from '../navigate';
21+
22+
describe('navigate', () => {
23+
const originalLocation = window.location;
24+
25+
beforeEach(() => {
26+
// @ts-ignore
27+
window.history.pushState = vi.fn();
28+
// @ts-ignore
29+
window.dispatchEvent = vi.fn();
30+
// @ts-ignore
31+
window.location.assign = vi.fn();
32+
// @ts-ignore
33+
delete window.location;
34+
// @ts-ignore
35+
window.location = {
36+
...originalLocation,
37+
assign: vi.fn(),
38+
origin: 'https://localhost:5173',
39+
href: 'https://localhost:5173/',
40+
};
41+
});
42+
43+
afterEach(() => {
44+
vi.clearAllMocks();
45+
// @ts-ignore
46+
window.location = originalLocation;
47+
});
48+
49+
it('should call window.history.pushState with the correct arguments for same-origin', () => {
50+
navigate('/test-url');
51+
expect(window.history.pushState).toHaveBeenCalledWith(null, '', '/test-url');
52+
expect(window.location.assign).not.toHaveBeenCalled();
53+
});
54+
55+
it('should dispatch a PopStateEvent with state null for same-origin', () => {
56+
navigate('/test-url');
57+
expect(window.dispatchEvent).toHaveBeenCalledWith(
58+
expect.objectContaining({
59+
type: 'popstate',
60+
state: null,
61+
}),
62+
);
63+
expect(window.location.assign).not.toHaveBeenCalled();
64+
});
65+
66+
it('should use window.location.assign for cross-origin URLs', () => {
67+
const crossOriginUrl = 'https://accounts.asgardeo.io/t/dxlab/accountrecoveryendpoint/register.do';
68+
navigate(crossOriginUrl);
69+
expect(window.location.assign).toHaveBeenCalledWith(crossOriginUrl);
70+
expect(window.history.pushState).not.toHaveBeenCalled();
71+
expect(window.dispatchEvent).not.toHaveBeenCalled();
72+
});
73+
74+
it('should use window.location.assign for malformed URLs', () => {
75+
const malformedUrl = 'http://[::1'; // Invalid URL
76+
navigate(malformedUrl);
77+
expect(window.location.assign).toHaveBeenCalledWith(malformedUrl);
78+
expect(window.history.pushState).not.toHaveBeenCalled();
79+
expect(window.dispatchEvent).not.toHaveBeenCalled();
80+
});
81+
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com).
3+
*
4+
* WSO2 LLC. licenses this file to you under the Apache License,
5+
* Version 2.0 (the "License"); you may not use this file except
6+
* in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing,
12+
* software distributed under the License is distributed on an
13+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
* KIND, either express or implied. See the License for the
15+
* specific language governing permissions and limitations
16+
* under the License.
17+
*/
18+
19+
/**
20+
* Navigates to a new URL within the browser.
21+
*
22+
* - For same-origin URLs (relative paths or absolute URLs with the same origin),
23+
* uses the History API and dispatches a `popstate` event (SPA navigation).
24+
* - For cross-origin URLs, performs a full page load using `window.location.assign`.
25+
*
26+
* This allows seamless navigation for both SPA routes and external links.
27+
*
28+
* @param url - The target URL to navigate to. Can be a path, query, or absolute URL.
29+
*
30+
* @example
31+
* ```typescript
32+
* // SPA navigation (same origin)
33+
* navigate('/dashboard');
34+
*
35+
* // SPA navigation with query
36+
* navigate('/search?q=asgardeo');
37+
*
38+
* // Cross-origin navigation (full page load)
39+
* navigate('https://accounts.asgardeo.io/t/dxlab/accountrecoveryendpoint/register.do');
40+
* ```
41+
*/
42+
const navigate = (url: string): void => {
43+
try {
44+
const targetUrl = new URL(url, window.location.origin);
45+
if (targetUrl.origin === window.location.origin) {
46+
window.history.pushState(null, '', targetUrl.pathname + targetUrl.search + targetUrl.hash);
47+
window.dispatchEvent(new PopStateEvent('popstate', {state: null}));
48+
} else {
49+
window.location.assign(targetUrl.href);
50+
}
51+
} catch {
52+
// If URL constructor fails (e.g., malformed URL), fallback to location.assign
53+
window.location.assign(url);
54+
}
55+
};
56+
57+
export default navigate;

packages/browser/tsconfig.spec.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
"compilerOptions": {
44
"outDir": "dist",
55
"module": "commonjs",
6-
"types": ["jest", "node"]
6+
"types": ["vitest/globals"]
77
},
88
"include": [
99
"test-configs",
10-
"jest.config.js",
10+
"vitest.config.ts",
1111
"**/*.test.ts",
1212
"**/*.spec.ts",
1313
"**/*.test.js",

packages/browser/vitest.config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ export default defineConfig({
2626
instances: [{browser: 'chromium'}],
2727
provider: 'playwright',
2828
},
29+
globals: true,
2930
},
3031
});

0 commit comments

Comments
 (0)