diff --git a/src/lib/kernel/InterceptedHistoryApi.svelte.test.ts b/src/lib/kernel/InterceptedHistoryApi.svelte.test.ts index 18c59d7..fe6bf66 100644 --- a/src/lib/kernel/InterceptedHistoryApi.svelte.test.ts +++ b/src/lib/kernel/InterceptedHistoryApi.svelte.test.ts @@ -7,15 +7,20 @@ describe('InterceptedHistoryApi', () => { const initialUrl = 'http://example.com/'; let historyApi: InterceptedHistoryApi; let browserMocks: ReturnType; + let origPushState: typeof globalThis.window.history.pushState; + let origReplaceState: typeof globalThis.window.history.replaceState; beforeEach(() => { browserMocks = setupBrowserMocks(initialUrl); + origPushState = globalThis.window.history.pushState; + origReplaceState = globalThis.window.history.replaceState; historyApi = new InterceptedHistoryApi(); }); afterEach(() => { historyApi.dispose(); browserMocks.cleanup(); + vi.resetAllMocks(); }); describe('constructor', () => { @@ -24,9 +29,10 @@ describe('InterceptedHistoryApi', () => { expect(historyApi.url.href).toBe(initialUrl); }); - test('Should replace window.history with itself.', () => { + test("Should replace window.history's pushState and replaceState methods.", () => { // Assert. - expect(globalThis.window.history).toBe(historyApi); + expect(globalThis.window.history.pushState).not.toBe(origPushState); + expect(globalThis.window.history.replaceState).not.toBe(origReplaceState); }); test('Should use provided initial URL.', () => { @@ -315,14 +321,13 @@ describe('InterceptedHistoryApi', () => { test('Should call the original history method when navigation is not cancelled.', () => { // Arrange. - const originalPushState = vi.spyOn(browserMocks.history, 'pushState'); const state = { path: { test: 'value' }, hash: {} }; // Act. historyApi.pushState(state, '', 'http://example.com/other'); // Assert. - expect(originalPushState).toHaveBeenCalledWith(state, '', 'http://example.com/other'); + expect(origPushState).toHaveBeenCalledWith(state, '', 'http://example.com/other'); }); }); @@ -375,15 +380,13 @@ describe('InterceptedHistoryApi', () => { expect(callback).not.toHaveBeenCalled(); }); - test('Should restore original window.history.', () => { - // Arrange. - const originalHistory = browserMocks.history; - + test('Should restore original window.history methods.', () => { // Act. historyApi.dispose(); // Assert. - expect(globalThis.window.history).toBe(originalHistory); + expect(globalThis.window.history.pushState).toBe(origPushState); + expect(globalThis.window.history.replaceState).toBe(origReplaceState); }); test('Should be safe to call multiple times.', () => { @@ -422,7 +425,6 @@ describe('InterceptedHistoryApi', () => { }); historyApi.on('beforeNavigate', callback1); historyApi2.on('beforeNavigate', callback2); - expect(globalThis.window.history).toBe(historyApi2); // Act. historyApi2.pushState({}, '', 'http://example.com/test'); diff --git a/src/lib/kernel/InterceptedHistoryApi.svelte.ts b/src/lib/kernel/InterceptedHistoryApi.svelte.ts index 44dee9e..61d86b2 100644 --- a/src/lib/kernel/InterceptedHistoryApi.svelte.ts +++ b/src/lib/kernel/InterceptedHistoryApi.svelte.ts @@ -20,13 +20,16 @@ export class InterceptedHistoryApi extends StockHistoryApi implements FullModeHi navigationCancelled: {} }; #nextSubId = 0; - #originalHistory: History | undefined; + #origReplaceState; + #origPushState; constructor(initialUrl?: string, initialState?: State) { super(initialUrl, initialState); if (globalThis.window) { - this.#originalHistory = globalThis.window.history; - globalThis.window.history = this; + this.#origReplaceState = globalThis.window.history.replaceState; + this.#origPushState = globalThis.window.history.pushState; + globalThis.window.history.replaceState = this.replaceState.bind(this); + globalThis.window.history.pushState = this.pushState.bind(this); } } @@ -82,7 +85,7 @@ export class InterceptedHistoryApi extends StockHistoryApi implements FullModeHi ); event.state = this.state; } - this.#originalHistory?.[`${method}State`](event.state, unused, url); + (method === 'push' ? this.#origPushState : this.#origReplaceState)?.bind(globalThis.window.history)(event.state, unused, url); this.url.href = globalThis.window?.location?.href ?? new URL(url ?? '', this.url).href; this.state = event.state as State; } @@ -108,8 +111,9 @@ export class InterceptedHistoryApi extends StockHistoryApi implements FullModeHi beforeNavigate: {}, navigationCancelled: {} }; - if (this.#originalHistory) { - globalThis.window.history = this.#originalHistory; + if (globalThis.window) { + globalThis.window.history.replaceState = this.#origReplaceState!; + globalThis.window.history.pushState = this.#origPushState!; } super.dispose(); }