diff --git a/packages/router-component-store/src/lib/router-history-store/router-history.store.spec.ts b/packages/router-component-store/src/lib/router-history-store/router-history.store.spec.ts index cd4d8d1..ea8fcd1 100644 --- a/packages/router-component-store/src/lib/router-history-store/router-history.store.spec.ts +++ b/packages/router-component-store/src/lib/router-history-store/router-history.store.spec.ts @@ -30,6 +30,14 @@ function createTestComponent(name: string, selector: string) { >< Back + > Next + Home About Company @@ -45,6 +53,11 @@ class TestAppComponent { event.preventDefault(); this.routerHistory.onNavigateBack(); } + + onNext(event: MouseEvent) { + event.preventDefault(); + this.routerHistory.onNavigateForward(); + } } describe(RouterHistoryStore.name, () => { @@ -98,55 +111,176 @@ describe(RouterHistoryStore.name, () => { } it('the URLs behave like the History API when navigating using links', async () => { - expect.assertions(2); + expect.assertions(3); const { click, routerHistory } = await setup(); // At Home + // Previous: None + // Next: None await click('#about-link'); // At About + // Previous: Home + // Next: None await click('#company-link'); // At Company + // Previous: About + // Next: None await click('#products-link'); // At Products + // Previous: Company + // Next: None expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/products'); expect(await firstValueFrom(routerHistory.previousUrl$)).toBe('/company'); + expect(await firstValueFrom(routerHistory.nextUrl$)).toBe(undefined); }); it('the URLs behave like the History API when navigating back', async () => { - expect.assertions(2); + expect.assertions(3); + + const { click, routerHistory } = await setup(); + + // At Home + // Previous: None + // Next: None + await click('#about-link'); + // At About + // Previous: Home + // Next: None + await click('#company-link'); + // At Company + // Previous: About + // Next: None + await click('#back-link'); + // At About + // Previous: Home + // Next: Company + + expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/about'); + expect(await firstValueFrom(routerHistory.previousUrl$)).toBe('/home'); + expect(await firstValueFrom(routerHistory.nextUrl$)).toBe('/company'); + }); + + it('the URLs behave like the History API when navigating back twice', async () => { + expect.assertions(3); + + const { click, routerHistory } = await setup(); + + // At Home + // Previous: None + // Next: None + await click('#about-link'); + // At About + // Previous: Home + // Next: None + await click('#company-link'); + // At Company + // Previous: About + // Next: None + await click('#back-link'); + // At About + // Previous: Home + // Next: Company + await click('#back-link'); + // At Home + // Previous: None + // Next: About + + expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/home'); + expect(await firstValueFrom(routerHistory.previousUrl$)).toBe(undefined); + expect(await firstValueFrom(routerHistory.nextUrl$)).toBe('/about'); + }); + + it('the URLs behave like the History API when navigating back twice then forward', async () => { + expect.assertions(3); const { click, routerHistory } = await setup(); // At Home + // Previous: None + // Next: None await click('#about-link'); // At About + // Previous: Home + // Next: None await click('#company-link'); // At Company + // Previous: About + // Next: None await click('#back-link'); // At About + // Previous: Home + // Next: Company + await click('#back-link'); + // At Home + // Previous: None + // Next: About + await click('#forward-link'); + // At About + // Previous: Home + // Next: Company expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/about'); expect(await firstValueFrom(routerHistory.previousUrl$)).toBe('/home'); + expect(await firstValueFrom(routerHistory.nextUrl$)).toBe('/company'); }); it('the URLs behave like the History API when navigating back then using links', async () => { - expect.assertions(2); + expect.assertions(3); const { click, routerHistory } = await setup(); // At Home + // Previous: None + // Next: None await click('#about-link'); // At About + // Previous: Home + // Next: None await click('#company-link'); // At Company + // Previous: About + // Next: None await click('#back-link'); // At About + // Previous: Home + // Next: Company await click('#products-link'); // At Products + // Previous: About + // Next: None expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/products'); expect(await firstValueFrom(routerHistory.previousUrl$)).toBe('/about'); + expect(await firstValueFrom(routerHistory.nextUrl$)).toBe(undefined); + }); + + it('the URLs behave like the History API when navigating back then forward', async () => { + expect.assertions(3); + + const { click, routerHistory } = await setup(); + + // At Home + await click('#about-link'); + // At About + // Previous: Home + // Next: None + await click('#company-link'); + // At Company + // Previous: About + // Next: None + await click('#back-link'); + // At About + // Previous: Home + // Next: Company + await click('#forward-link'); + // At Company + // Previous: About + // Next: None + + expect(await firstValueFrom(routerHistory.currentUrl$)).toBe('/company'); + expect(await firstValueFrom(routerHistory.previousUrl$)).toBe('/about'); + expect(await firstValueFrom(routerHistory.nextUrl$)).toBe(undefined); }); }); diff --git a/packages/router-component-store/src/lib/router-history-store/router-history.store.ts b/packages/router-component-store/src/lib/router-history-store/router-history.store.ts index b161743..ee09b15 100644 --- a/packages/router-component-store/src/lib/router-history-store/router-history.store.ts +++ b/packages/router-component-store/src/lib/router-history-store/router-history.store.ts @@ -127,6 +127,38 @@ export class RouterHistoryStore extends ComponentStore { this.#latestRouterNavigatedSequence$, ([, navigationEnd]) => navigationEnd.urlAfterRedirects ); + /** + * The next URL when taking `popstate` events into account. + * + * `undefined` is emitted when the current navigation is the last in the + * navigation history. + */ + nextUrl$: Observable = this.select( + this.#history$, + this.#maxNavigatedId$, + (history, maxNavigatedId) => { + if (maxNavigatedId === 1) { + return undefined; + } + + const [sourceNavigationStart] = this.#findSourceNavigatedSequence( + maxNavigatedId, + history + ); + + if (sourceNavigationStart.id === maxNavigatedId) { + return undefined; + } + + const nextNavigationId = sourceNavigationStart.id + 1; + const [, nextNavigationEnd] = this.#findSourceNavigatedSequence( + nextNavigationId, + history + ); + + return nextNavigationEnd.urlAfterRedirects; + } + ); /** * The previous URL when taking `popstate` events into account. *