mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
0460a9dfaf
Migrate formatting to prettier for common from clang-format PR Close #54150
1840 lines
77 KiB
TypeScript
1840 lines
77 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
|
|
import {
|
|
ExperimentalNavigationInterceptOptions,
|
|
FakeNavigateEvent,
|
|
FakeNavigation,
|
|
FakeNavigationCurrentEntryChangeEvent,
|
|
} from '../../testing/src/navigation/fake_navigation';
|
|
|
|
interface Locals {
|
|
navigation: FakeNavigation;
|
|
navigateEvents: FakeNavigateEvent[];
|
|
navigationCurrentEntryChangeEvents: FakeNavigationCurrentEntryChangeEvent[];
|
|
popStateEvents: PopStateEvent[];
|
|
pendingInterceptOptions: ExperimentalNavigationInterceptOptions[];
|
|
nextNavigateEvent: () => Promise<FakeNavigateEvent>;
|
|
setExtraNavigateCallback: (callback: (event: FakeNavigateEvent) => void) => void;
|
|
}
|
|
|
|
describe('navigation', () => {
|
|
let locals: Locals;
|
|
|
|
const popStateListener = (event: Event) => {
|
|
const popStateEvent = event as PopStateEvent;
|
|
locals.popStateEvents.push(popStateEvent);
|
|
};
|
|
|
|
beforeEach(() => {
|
|
window.addEventListener('popstate', popStateListener);
|
|
});
|
|
|
|
afterEach(() => {
|
|
window.removeEventListener('popstate', popStateListener);
|
|
});
|
|
|
|
beforeEach(() => {
|
|
const navigation = new FakeNavigation(window, 'https://test.com');
|
|
const navigateEvents: FakeNavigateEvent[] = [];
|
|
let nextNavigateEventResolve!: (value: FakeNavigateEvent) => void;
|
|
let nextNavigateEventPromise = new Promise<FakeNavigateEvent>((resolve) => {
|
|
nextNavigateEventResolve = resolve;
|
|
});
|
|
const navigationCurrentEntryChangeEvents: FakeNavigationCurrentEntryChangeEvent[] = [];
|
|
const popStateEvents: PopStateEvent[] = [];
|
|
const pendingInterceptOptions: ExperimentalNavigationInterceptOptions[] = [];
|
|
let extraNavigateCallback: ((event: FakeNavigateEvent) => void) | undefined = undefined;
|
|
|
|
navigation.addEventListener('navigate', (event: Event) => {
|
|
const navigateEvent = event as FakeNavigateEvent;
|
|
nextNavigateEventResolve(navigateEvent);
|
|
nextNavigateEventPromise = new Promise<FakeNavigateEvent>((resolve) => {
|
|
nextNavigateEventResolve = resolve;
|
|
});
|
|
locals.navigateEvents.push(navigateEvent);
|
|
const interceptOptions = pendingInterceptOptions.shift();
|
|
if (interceptOptions) {
|
|
navigateEvent.intercept(interceptOptions);
|
|
}
|
|
extraNavigateCallback?.(navigateEvent);
|
|
});
|
|
navigation.addEventListener('currententrychange', (event: Event) => {
|
|
const currentNavigationEntryChangeEvent = event as FakeNavigationCurrentEntryChangeEvent;
|
|
locals.navigationCurrentEntryChangeEvents.push(currentNavigationEntryChangeEvent);
|
|
});
|
|
locals = {
|
|
navigation,
|
|
navigateEvents,
|
|
navigationCurrentEntryChangeEvents,
|
|
popStateEvents,
|
|
pendingInterceptOptions,
|
|
nextNavigateEvent() {
|
|
return nextNavigateEventPromise;
|
|
},
|
|
setExtraNavigateCallback(callback: (event: FakeNavigateEvent) => void) {
|
|
extraNavigateCallback = callback;
|
|
},
|
|
};
|
|
});
|
|
|
|
const setUpEntries = async ({hash = false} = {}) => {
|
|
locals.pendingInterceptOptions.push({});
|
|
const pathPrefix = hash ? '#' : '/';
|
|
const firstPageEntry = await locals.navigation.navigate(`${pathPrefix}page1`, {
|
|
state: {page1: true},
|
|
}).finished;
|
|
locals.pendingInterceptOptions.push({});
|
|
const secondPageEntry = await locals.navigation.navigate(`${pathPrefix}page2`, {
|
|
state: {page2: true},
|
|
}).finished;
|
|
locals.pendingInterceptOptions.push({});
|
|
const thirdPageEntry = await locals.navigation.navigate(`${pathPrefix}page3`, {
|
|
state: {page3: true},
|
|
}).finished;
|
|
locals.navigateEvents.length = 0;
|
|
locals.navigationCurrentEntryChangeEvents.length = 0;
|
|
locals.popStateEvents.length = 0;
|
|
return [firstPageEntry, secondPageEntry, thirdPageEntry];
|
|
};
|
|
|
|
const setUpEntriesWithHistory = ({hash = false} = {}) => {
|
|
const pathPrefix = hash ? '#' : '/';
|
|
locals.navigation.pushState({state: {page1: true}}, '', `${pathPrefix}page1`);
|
|
const firstPageEntry = locals.navigation.currentEntry;
|
|
locals.navigation.pushState({state: {page2: true}}, '', `${pathPrefix}page2`);
|
|
const secondPageEntry = locals.navigation.currentEntry;
|
|
locals.navigation.pushState({state: {page3: true}}, '', `${pathPrefix}page3`);
|
|
const thirdPageEntry = locals.navigation.currentEntry;
|
|
locals.navigateEvents.length = 0;
|
|
locals.navigationCurrentEntryChangeEvents.length = 0;
|
|
locals.popStateEvents.length = 0;
|
|
return [firstPageEntry, secondPageEntry, thirdPageEntry];
|
|
};
|
|
|
|
it('disposes', async () => {
|
|
expect(locals.navigation.isDisposed()).toBeFalse();
|
|
const navigateEvents: Event[] = [];
|
|
locals.navigation.addEventListener('navigate', (event: Event) => {
|
|
navigateEvents.push(event);
|
|
});
|
|
const navigationCurrentEntryChangeEvents: Event[] = [];
|
|
locals.navigation.addEventListener('currententrychange', (event: Event) => {
|
|
navigationCurrentEntryChangeEvents.push(event);
|
|
});
|
|
|
|
await locals.navigation.navigate('#page1').finished;
|
|
expect(navigateEvents.length).toBe(1);
|
|
expect(navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
locals.navigation.dispose();
|
|
// After a dispose, a different singleton.
|
|
expect(locals.navigation.isDisposed()).toBeTrue();
|
|
await locals.navigation.navigate('#page2').finished;
|
|
// Listeners are disposed.
|
|
expect(navigateEvents.length).toBe(1);
|
|
expect(navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
});
|
|
|
|
describe('navigate', () => {
|
|
it('push URL', async () => {
|
|
const initialEntry = locals.navigation.currentEntry;
|
|
locals.pendingInterceptOptions.push({});
|
|
const {committed, finished} = locals.navigation.navigate('/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
canIntercept: true,
|
|
hashChange: false,
|
|
info: undefined,
|
|
navigationType: 'push',
|
|
userInitiated: false,
|
|
signal: jasmine.any(AbortSignal),
|
|
destination: jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: null,
|
|
id: null,
|
|
index: -1,
|
|
sameDocument: false,
|
|
}),
|
|
}),
|
|
);
|
|
expect(navigateEvent.destination.getState()).toBeUndefined();
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toEqual(
|
|
jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: '1',
|
|
id: '1',
|
|
index: 1,
|
|
sameDocument: true,
|
|
}),
|
|
);
|
|
expect(committedEntry.getState()).toBeUndefined();
|
|
expect(locals.navigation.currentEntry).toBe(committedEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
const currentEntryChangeEvent = locals.navigationCurrentEntryChangeEvents[0];
|
|
expect(currentEntryChangeEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'push',
|
|
from: jasmine.objectContaining({
|
|
url: initialEntry.url,
|
|
key: initialEntry.key,
|
|
id: initialEntry.id,
|
|
index: initialEntry.index,
|
|
sameDocument: initialEntry.sameDocument,
|
|
}),
|
|
}),
|
|
);
|
|
expect(currentEntryChangeEvent.from.getState()).toBe(initialEntry.getState());
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const popStateEvent = locals.popStateEvents[0];
|
|
expect(popStateEvent.state).toBeNull();
|
|
const finishedEntry = await finished;
|
|
expect(committedEntry).toBe(finishedEntry);
|
|
expect(locals.navigation.currentEntry).toBe(finishedEntry);
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
});
|
|
|
|
it('push URL relative', async () => {
|
|
locals.pendingInterceptOptions.push({});
|
|
await locals.navigation.navigate('/a/b/c').finished;
|
|
expect(locals.navigation.currentEntry.url).toBe('https://test.com/a/b/c');
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => Promise.resolve(),
|
|
});
|
|
await locals.navigation.navigate('../').finished;
|
|
expect(locals.navigation.currentEntry.url).toBe('https://test.com/a/');
|
|
});
|
|
|
|
it('replace URL', async () => {
|
|
const initialEntry = locals.navigation.currentEntry;
|
|
locals.pendingInterceptOptions.push({});
|
|
const {committed, finished} = locals.navigation.navigate('/test', {
|
|
history: 'replace',
|
|
});
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
canIntercept: true,
|
|
hashChange: false,
|
|
info: undefined,
|
|
navigationType: 'replace',
|
|
userInitiated: false,
|
|
signal: jasmine.any(AbortSignal),
|
|
destination: jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: null,
|
|
id: null,
|
|
index: -1,
|
|
sameDocument: false,
|
|
}),
|
|
}),
|
|
);
|
|
expect(navigateEvent.destination.getState()).toBeUndefined();
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toEqual(
|
|
jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: '0',
|
|
id: '1',
|
|
index: 0,
|
|
sameDocument: true,
|
|
}),
|
|
);
|
|
expect(committedEntry.getState()).toBeUndefined();
|
|
expect(locals.navigation.currentEntry).toBe(committedEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
const currentEntryChangeEvent = locals.navigationCurrentEntryChangeEvents[0];
|
|
expect(currentEntryChangeEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'replace',
|
|
from: jasmine.objectContaining({
|
|
url: initialEntry.url,
|
|
key: initialEntry.key,
|
|
id: initialEntry.id,
|
|
index: initialEntry.index,
|
|
sameDocument: initialEntry.sameDocument,
|
|
}),
|
|
}),
|
|
);
|
|
expect(currentEntryChangeEvent.from.getState()).toBe(initialEntry.getState());
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const popStateEvent = locals.popStateEvents[0];
|
|
expect(popStateEvent.state).toBeNull();
|
|
const finishedEntry = await finished;
|
|
expect(committedEntry).toBe(finishedEntry);
|
|
expect(locals.navigation.currentEntry).toBe(finishedEntry);
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
});
|
|
|
|
it('push URL with state', async () => {
|
|
locals.pendingInterceptOptions.push({});
|
|
const state = {test: true};
|
|
const {committed, finished} = locals.navigation.navigate('/test', {
|
|
state,
|
|
});
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent.destination.getState()).toEqual(state);
|
|
const committedEntry = await committed;
|
|
expect(committedEntry.getState()).toEqual(state);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const finishedEntry = await finished;
|
|
expect(committedEntry).toBe(finishedEntry);
|
|
});
|
|
|
|
it('replace URL with state', async () => {
|
|
locals.pendingInterceptOptions.push({});
|
|
const state = {test: true};
|
|
const {committed, finished} = locals.navigation.navigate('/test', {
|
|
state,
|
|
history: 'replace',
|
|
});
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent.destination.getState()).toEqual(state);
|
|
const committedEntry = await committed;
|
|
expect(committedEntry.getState()).toEqual(state);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const finishedEntry = await finished;
|
|
expect(committedEntry).toBe(finishedEntry);
|
|
});
|
|
|
|
it('push URL with hashchange', async () => {
|
|
const {committed, finished} = locals.navigation.navigate('#test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent.destination.url).toBe('https://test.com/#test');
|
|
expect(navigateEvent.hashChange).toBeTrue();
|
|
const committedEntry = await committed;
|
|
expect(committedEntry.url).toBe('https://test.com/#test');
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const finishedEntry = await finished;
|
|
expect(committedEntry).toBe(finishedEntry);
|
|
});
|
|
|
|
it('replace URL with hashchange', async () => {
|
|
const {committed, finished} = locals.navigation.navigate('#test', {
|
|
history: 'replace',
|
|
});
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent.destination.url).toBe('https://test.com/#test');
|
|
expect(navigateEvent.hashChange).toBeTrue();
|
|
const committedEntry = await committed;
|
|
expect(committedEntry.url).toBe('https://test.com/#test');
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const finishedEntry = await finished;
|
|
expect(committedEntry).toBe(finishedEntry);
|
|
});
|
|
|
|
it('push URL with info', async () => {
|
|
locals.pendingInterceptOptions.push({});
|
|
const info = {test: true};
|
|
const {finished, committed} = locals.navigation.navigate('/test', {info});
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent.info).toBe(info);
|
|
await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await finished;
|
|
});
|
|
|
|
it('replace URL with info', async () => {
|
|
locals.pendingInterceptOptions.push({});
|
|
const info = {test: true};
|
|
const {finished, committed} = locals.navigation.navigate('/test', {
|
|
info,
|
|
history: 'replace',
|
|
});
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent.info).toBe(info);
|
|
await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await finished;
|
|
});
|
|
|
|
it('push URL with handler', async () => {
|
|
let handlerFinishedResolve!: (value: Promise<undefined> | undefined) => void;
|
|
const handlerFinished = new Promise<undefined>((resolve) => {
|
|
handlerFinishedResolve = resolve;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerFinished,
|
|
});
|
|
const {committed, finished} = locals.navigation.navigate('/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toEqual(
|
|
jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: '1',
|
|
id: '1',
|
|
index: 1,
|
|
sameDocument: true,
|
|
}),
|
|
);
|
|
expect(committedEntry.getState()).toBeUndefined();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await expectAsync(finished).toBePending();
|
|
handlerFinishedResolve(undefined);
|
|
await expectAsync(finished).toBeResolvedTo(committedEntry);
|
|
});
|
|
|
|
it('replace URL with handler', async () => {
|
|
let handlerFinishedResolve!: (value: Promise<undefined> | undefined) => void;
|
|
const handlerFinished = new Promise<undefined>((resolve) => {
|
|
handlerFinishedResolve = resolve;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerFinished,
|
|
});
|
|
const {committed, finished} = locals.navigation.navigate('/test', {
|
|
history: 'replace',
|
|
});
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toEqual(
|
|
jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: '0',
|
|
id: '1',
|
|
index: 0,
|
|
sameDocument: true,
|
|
}),
|
|
);
|
|
expect(committedEntry.getState()).toBeUndefined();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await expectAsync(finished).toBePending();
|
|
handlerFinishedResolve(undefined);
|
|
await expectAsync(finished).toBeResolvedTo(committedEntry);
|
|
});
|
|
|
|
it('deferred commit', async () => {
|
|
let handlerFinishedResolve!: (value: Promise<undefined> | undefined) => void;
|
|
const handlerFinished = new Promise<undefined>((resolve) => {
|
|
handlerFinishedResolve = resolve;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerFinished,
|
|
commit: 'after-transition',
|
|
});
|
|
const {committed, finished} = locals.navigation.navigate('/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
await expectAsync(committed).toBePending();
|
|
expect(locals.navigation.currentEntry.url).toBe('https://test.com/');
|
|
locals.navigateEvents[0].commit();
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toEqual(
|
|
jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: '1',
|
|
id: '1',
|
|
index: 1,
|
|
sameDocument: true,
|
|
}),
|
|
);
|
|
expect(committedEntry.getState()).toBeUndefined();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await expectAsync(finished).toBePending();
|
|
handlerFinishedResolve(undefined);
|
|
await expectAsync(finished).toBeResolvedTo(committedEntry);
|
|
});
|
|
|
|
it('deferred commit early resolve', async () => {
|
|
let handlerFinishedResolve!: (value: Promise<undefined> | undefined) => void;
|
|
const handlerFinished = new Promise<undefined>((resolve) => {
|
|
handlerFinishedResolve = resolve;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerFinished,
|
|
commit: 'after-transition',
|
|
});
|
|
const {committed, finished} = locals.navigation.navigate('/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
await expectAsync(committed).toBePending();
|
|
expect(locals.navigation.currentEntry.url).toBe('https://test.com/');
|
|
handlerFinishedResolve(undefined);
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toEqual(
|
|
jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: '1',
|
|
id: '1',
|
|
index: 1,
|
|
sameDocument: true,
|
|
}),
|
|
);
|
|
expect(committedEntry.getState()).toBeUndefined();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await expectAsync(finished).toBeResolvedTo(committedEntry);
|
|
});
|
|
|
|
it('deferred commit during dispatch throws', async () => {
|
|
locals.pendingInterceptOptions.push({
|
|
commit: 'after-transition',
|
|
});
|
|
let called = false;
|
|
locals.setExtraNavigateCallback((event: FakeNavigateEvent) => {
|
|
expect(() => {
|
|
called = true;
|
|
event.commit();
|
|
}).toThrowError(DOMException);
|
|
});
|
|
const {finished} = locals.navigation.navigate('/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
navigateEvent.commit();
|
|
await finished;
|
|
expect(called).toBeTrue();
|
|
});
|
|
|
|
it('deferred commit with immediate throws', async () => {
|
|
locals.pendingInterceptOptions.push({});
|
|
const {finished} = locals.navigation.navigate('/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(() => {
|
|
navigateEvent.commit();
|
|
}).toThrowError(DOMException);
|
|
await finished;
|
|
});
|
|
|
|
it('deferred commit twice throws', async () => {
|
|
let handlerFinishedResolve!: (value: Promise<undefined> | undefined) => void;
|
|
const handlerFinished = new Promise<undefined>((resolve) => {
|
|
handlerFinishedResolve = resolve;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerFinished,
|
|
commit: 'after-transition',
|
|
});
|
|
const {committed, finished} = locals.navigation.navigate('/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
await expectAsync(committed).toBePending();
|
|
expect(locals.navigation.currentEntry.url).toBe('https://test.com/');
|
|
navigateEvent.commit();
|
|
expect(() => {
|
|
navigateEvent.commit();
|
|
}).toThrowError(DOMException);
|
|
handlerFinishedResolve(undefined);
|
|
await finished;
|
|
});
|
|
|
|
it('deferred commit resolves on finished', async () => {
|
|
let handlerFinishedResolve!: (value: Promise<undefined> | undefined) => void;
|
|
const handlerFinished = new Promise<undefined>((resolve) => {
|
|
handlerFinishedResolve = resolve;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerFinished,
|
|
commit: 'after-transition',
|
|
});
|
|
const {committed, finished} = locals.navigation.navigate('/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
await expectAsync(committed).toBePending();
|
|
expect(locals.navigation.currentEntry.url).toBe('https://test.com/');
|
|
handlerFinishedResolve(undefined);
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toEqual(
|
|
jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: '1',
|
|
id: '1',
|
|
index: 1,
|
|
sameDocument: true,
|
|
}),
|
|
);
|
|
expect(committedEntry.getState()).toBeUndefined();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await expectAsync(finished).toBeResolvedTo(committedEntry);
|
|
});
|
|
|
|
it('push with interruption', async () => {
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => new Promise(() => {}),
|
|
});
|
|
|
|
const {committed, finished} = locals.navigation.navigate('/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await expectAsync(finished).toBePending();
|
|
locals.pendingInterceptOptions.push({});
|
|
const interruptResult = locals.navigation.navigate('/interrupt');
|
|
await expectAsync(finished).toBeRejectedWithError(DOMException);
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
await interruptResult.committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
await interruptResult.finished;
|
|
});
|
|
|
|
it('replace with interruption', async () => {
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => new Promise(() => {}),
|
|
});
|
|
|
|
const {committed, finished} = locals.navigation.navigate('/test', {
|
|
history: 'replace',
|
|
});
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await expectAsync(finished).toBePending();
|
|
locals.pendingInterceptOptions.push({});
|
|
const interruptResult = locals.navigation.navigate('/interrupt');
|
|
await expectAsync(finished).toBeRejectedWithError(DOMException);
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
await interruptResult.committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
await interruptResult.finished;
|
|
});
|
|
|
|
it('push with handler reject', async () => {
|
|
let handlerFinishedReject!: (reason: unknown) => void;
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () =>
|
|
new Promise<undefined>((resolve, reject) => {
|
|
handlerFinishedReject = reject;
|
|
}),
|
|
});
|
|
|
|
const {committed, finished} = locals.navigation.navigate('/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await expectAsync(finished).toBePending();
|
|
const error = new Error('rejected');
|
|
handlerFinishedReject(error);
|
|
await expectAsync(finished).toBeRejectedWith(error);
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
});
|
|
|
|
it('replace with reject', async () => {
|
|
let handlerFinishedReject!: (reason: unknown) => void;
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () =>
|
|
new Promise<undefined>((resolve, reject) => {
|
|
handlerFinishedReject = reject;
|
|
}),
|
|
});
|
|
|
|
const {committed, finished} = locals.navigation.navigate('/test', {
|
|
history: 'replace',
|
|
});
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await expectAsync(finished).toBePending();
|
|
const error = new Error('rejected');
|
|
handlerFinishedReject(error);
|
|
await expectAsync(finished).toBeRejectedWith(error);
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('traversal', () => {
|
|
it('traverses back', async () => {
|
|
expect(locals.navigation.canGoBack).toBeFalse();
|
|
expect(locals.navigation.canGoForward).toBeFalse();
|
|
const [firstPageEntry, , thirdPageEntry] = await setUpEntries();
|
|
|
|
expect(locals.navigation.canGoBack).toBeTrue();
|
|
expect(locals.navigation.canGoForward).toBeFalse();
|
|
const {committed, finished} = locals.navigation.traverseTo(firstPageEntry.key);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
canIntercept: true,
|
|
hashChange: false,
|
|
info: undefined,
|
|
navigationType: 'traverse',
|
|
signal: jasmine.any(AbortSignal),
|
|
userInitiated: false,
|
|
destination: jasmine.objectContaining({
|
|
url: firstPageEntry.url!,
|
|
key: firstPageEntry.key,
|
|
id: firstPageEntry.id,
|
|
index: firstPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(navigateEvent.destination.getState()).toEqual(firstPageEntry.getState());
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toBe(firstPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
const currentEntryChangeEvent = locals.navigationCurrentEntryChangeEvents[0];
|
|
expect(currentEntryChangeEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'traverse',
|
|
from: jasmine.objectContaining({
|
|
url: thirdPageEntry.url!,
|
|
key: thirdPageEntry.key,
|
|
id: thirdPageEntry.id,
|
|
index: thirdPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(currentEntryChangeEvent.from.getState()).toEqual(thirdPageEntry.getState());
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const popStateEvent = locals.popStateEvents[0];
|
|
expect(popStateEvent.state).toBeNull();
|
|
expect(locals.navigation.canGoBack).toBeTrue();
|
|
expect(locals.navigation.canGoForward).toBeTrue();
|
|
const finishedEntry = await finished;
|
|
expect(finishedEntry).toBe(firstPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
});
|
|
|
|
it('traverses forward', async () => {
|
|
expect(locals.navigation.canGoBack).toBeFalse();
|
|
expect(locals.navigation.canGoForward).toBeFalse();
|
|
const [firstPageEntry, , thirdPageEntry] = await setUpEntries();
|
|
expect(locals.navigation.canGoBack).toBeTrue();
|
|
expect(locals.navigation.canGoForward).toBeFalse();
|
|
await locals.navigation.traverseTo(firstPageEntry.key).finished;
|
|
locals.navigateEvents.length = 0;
|
|
locals.navigationCurrentEntryChangeEvents.length = 0;
|
|
locals.popStateEvents.length = 0;
|
|
expect(locals.navigation.canGoBack).toBeTrue();
|
|
expect(locals.navigation.canGoForward).toBeTrue();
|
|
|
|
const {committed, finished} = locals.navigation.traverseTo(thirdPageEntry.key);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
canIntercept: true,
|
|
hashChange: false,
|
|
info: undefined,
|
|
navigationType: 'traverse',
|
|
signal: jasmine.any(AbortSignal),
|
|
userInitiated: false,
|
|
destination: jasmine.objectContaining({
|
|
url: thirdPageEntry.url!,
|
|
key: thirdPageEntry.key,
|
|
id: thirdPageEntry.id,
|
|
index: thirdPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(navigateEvent.destination.getState()).toEqual(thirdPageEntry.getState());
|
|
const committedEntry = await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
const currentEntryChangeEvent = locals.navigationCurrentEntryChangeEvents[0];
|
|
expect(currentEntryChangeEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'traverse',
|
|
from: jasmine.objectContaining({
|
|
url: firstPageEntry.url!,
|
|
key: firstPageEntry.key,
|
|
id: firstPageEntry.id,
|
|
index: firstPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(currentEntryChangeEvent.from.getState()).toEqual(firstPageEntry.getState());
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const popStateEvent = locals.popStateEvents[0];
|
|
expect(popStateEvent.state).toBeNull();
|
|
expect(committedEntry).toBe(thirdPageEntry);
|
|
const finishedEntry = await finished;
|
|
expect(finishedEntry).toBe(thirdPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(thirdPageEntry);
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
expect(locals.navigation.canGoBack).toBeTrue();
|
|
expect(locals.navigation.canGoForward).toBeFalse();
|
|
});
|
|
|
|
it('traverses back with hashchange', async () => {
|
|
const [firstPageEntry] = await setUpEntries({hash: true});
|
|
|
|
const {finished, committed} = locals.navigation.traverseTo(firstPageEntry.key);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent.hashChange).toBeTrue();
|
|
await committed;
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await finished;
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
});
|
|
|
|
it('traverses forward with hashchange', async () => {
|
|
const [firstPageEntry, thirdPageEntry] = await setUpEntries({hash: true});
|
|
await locals.navigation.traverseTo(firstPageEntry.key).finished;
|
|
locals.navigateEvents.length = 0;
|
|
locals.navigationCurrentEntryChangeEvents.length = 0;
|
|
locals.popStateEvents.length = 0;
|
|
|
|
const {finished, committed} = locals.navigation.traverseTo(thirdPageEntry.key);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent.hashChange).toBeTrue();
|
|
await committed;
|
|
expect(locals.navigation.currentEntry).toBe(thirdPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await finished;
|
|
expect(locals.navigation.currentEntry).toBe(thirdPageEntry);
|
|
});
|
|
|
|
it('traverses with info', async () => {
|
|
const [firstPageEntry] = await setUpEntries();
|
|
const info = {test: true};
|
|
const {finished, committed} = locals.navigation.traverseTo(firstPageEntry.key, {info});
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent.info).toBe(info);
|
|
await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await finished;
|
|
});
|
|
|
|
it('traverses with history state', async () => {
|
|
const [firstPageEntry] = setUpEntriesWithHistory();
|
|
|
|
const {finished, committed} = locals.navigation.traverseTo(firstPageEntry.key);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent.destination.getHistoryState()).toEqual(firstPageEntry.getHistoryState());
|
|
await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const popStateEvent = locals.popStateEvents[0];
|
|
expect(popStateEvent.state).toEqual(firstPageEntry.getHistoryState());
|
|
await finished;
|
|
});
|
|
|
|
it('traverses with handler', async () => {
|
|
const [firstPageEntry] = await setUpEntries();
|
|
let handlerFinishedResolve!: (value: Promise<undefined> | undefined) => void;
|
|
const handlerFinished = new Promise<undefined>((resolve) => {
|
|
handlerFinishedResolve = resolve;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerFinished,
|
|
});
|
|
const {committed, finished} = locals.navigation.traverseTo(firstPageEntry.key);
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toBe(firstPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await expectAsync(finished).toBePending();
|
|
handlerFinishedResolve(undefined);
|
|
await expectAsync(finished).toBeResolvedTo(firstPageEntry);
|
|
});
|
|
|
|
it('traverses with interruption', async () => {
|
|
const [firstPageEntry] = await setUpEntries();
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => new Promise(() => {}),
|
|
});
|
|
const {committed, finished} = locals.navigation.traverseTo(firstPageEntry.key);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
await committed;
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
expect(navigateEvent.signal.aborted).toBeFalse();
|
|
await expectAsync(finished).toBePending();
|
|
locals.pendingInterceptOptions.push({});
|
|
const interruptResult = locals.navigation.navigate('/interrupt');
|
|
await expectAsync(finished).toBeRejectedWithError(DOMException);
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
await interruptResult.committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
await interruptResult.finished;
|
|
});
|
|
|
|
it('traverses with reject', async () => {
|
|
const [firstPageEntry] = await setUpEntries();
|
|
let handlerFinishedReject!: (reason: unknown) => void;
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () =>
|
|
new Promise<undefined>((resolve, reject) => {
|
|
handlerFinishedReject = reject;
|
|
}),
|
|
});
|
|
|
|
const {committed, finished} = locals.navigation.traverseTo(firstPageEntry.key);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
await committed;
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
expect(navigateEvent.signal.aborted).toBeFalse();
|
|
await expectAsync(finished).toBePending();
|
|
const error = new Error('rejected');
|
|
handlerFinishedReject(error);
|
|
await expectAsync(finished).toBeRejectedWith(error);
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
});
|
|
|
|
it('traverses to non-existent', async () => {
|
|
const {committed, finished} = locals.navigation.traverseTo('non-existent');
|
|
await expectAsync(committed).toBeRejectedWithError(DOMException);
|
|
await expectAsync(finished).toBeRejectedWithError(DOMException);
|
|
expect(locals.navigateEvents.length).toBe(0);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(0);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
});
|
|
|
|
it('back', async () => {
|
|
const [, secondPageEntry, thirdPageEntry] = await setUpEntries();
|
|
const {committed, finished} = locals.navigation.back();
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
canIntercept: true,
|
|
hashChange: false,
|
|
info: undefined,
|
|
navigationType: 'traverse',
|
|
signal: jasmine.any(AbortSignal),
|
|
userInitiated: false,
|
|
destination: jasmine.objectContaining({
|
|
url: secondPageEntry.url!,
|
|
key: secondPageEntry.key,
|
|
id: secondPageEntry.id,
|
|
index: secondPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(navigateEvent.destination.getState()).toEqual(secondPageEntry.getState());
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toBe(secondPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
const currentEntryChangeEvent = locals.navigationCurrentEntryChangeEvents[0];
|
|
expect(currentEntryChangeEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'traverse',
|
|
from: jasmine.objectContaining({
|
|
url: thirdPageEntry.url!,
|
|
key: thirdPageEntry.key,
|
|
id: thirdPageEntry.id,
|
|
index: thirdPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(currentEntryChangeEvent.from.getState()).toEqual(thirdPageEntry.getState());
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const popStateEvent = locals.popStateEvents[0];
|
|
expect(popStateEvent.state).toBeNull();
|
|
const finishedEntry = await finished;
|
|
expect(finishedEntry).toBe(secondPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
});
|
|
|
|
it('back with info', async () => {
|
|
await setUpEntries();
|
|
const info = {test: true};
|
|
const {committed, finished} = locals.navigation.back({info});
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent.info).toBe(info);
|
|
await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await finished;
|
|
});
|
|
|
|
it('back out of bounds', async () => {
|
|
const {committed, finished} = locals.navigation.back();
|
|
await expectAsync(committed).toBeRejectedWithError(DOMException);
|
|
await expectAsync(finished).toBeRejectedWithError(DOMException);
|
|
expect(locals.navigateEvents.length).toBe(0);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(0);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
});
|
|
|
|
it('forward', async () => {
|
|
const [firstPageEntry, secondPageEntry] = await setUpEntries();
|
|
await locals.navigation.traverseTo(firstPageEntry.key).finished;
|
|
locals.navigateEvents.length = 0;
|
|
locals.navigationCurrentEntryChangeEvents.length = 0;
|
|
locals.popStateEvents.length = 0;
|
|
|
|
const {committed, finished} = locals.navigation.forward();
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
canIntercept: true,
|
|
hashChange: false,
|
|
info: undefined,
|
|
navigationType: 'traverse',
|
|
signal: jasmine.any(AbortSignal),
|
|
userInitiated: false,
|
|
destination: jasmine.objectContaining({
|
|
url: secondPageEntry.url!,
|
|
key: secondPageEntry.key,
|
|
id: secondPageEntry.id,
|
|
index: secondPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(navigateEvent.destination.getState()).toEqual(secondPageEntry.getState());
|
|
const committedEntry = await committed;
|
|
expect(committedEntry).toBe(secondPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
const currentEntryChangeEvent = locals.navigationCurrentEntryChangeEvents[0];
|
|
expect(currentEntryChangeEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'traverse',
|
|
from: jasmine.objectContaining({
|
|
url: firstPageEntry.url!,
|
|
key: firstPageEntry.key,
|
|
id: firstPageEntry.id,
|
|
index: firstPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(currentEntryChangeEvent.from.getState()).toEqual(firstPageEntry.getState());
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const popStateEvent = locals.popStateEvents[0];
|
|
expect(popStateEvent.state).toBeNull();
|
|
const finishedEntry = await finished;
|
|
expect(finishedEntry).toBe(secondPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
});
|
|
|
|
it('forward with info', async () => {
|
|
const [firstPageEntry] = await setUpEntries();
|
|
await locals.navigation.traverseTo(firstPageEntry.key).finished;
|
|
locals.navigateEvents.length = 0;
|
|
locals.navigationCurrentEntryChangeEvents.length = 0;
|
|
locals.popStateEvents.length = 0;
|
|
|
|
const info = {test: true};
|
|
const {committed, finished} = locals.navigation.forward({info});
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent.info).toBe(info);
|
|
await committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await finished;
|
|
});
|
|
|
|
it('forward out of bounds', async () => {
|
|
const {committed, finished} = locals.navigation.forward();
|
|
await expectAsync(committed).toBeRejectedWithError(DOMException);
|
|
await expectAsync(finished).toBeRejectedWithError(DOMException);
|
|
expect(locals.navigateEvents.length).toBe(0);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(0);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
});
|
|
|
|
it('traverse synchronously', async () => {
|
|
const [, secondPageEntry] = await setUpEntries();
|
|
locals.navigation.setSynchronousTraversalsForTesting(true);
|
|
|
|
const {committed, finished} = locals.navigation.back();
|
|
// Synchronously navigates.
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
await expectAsync(committed).toBeResolvedTo(secondPageEntry);
|
|
await expectAsync(finished).toBeResolvedTo(secondPageEntry);
|
|
});
|
|
|
|
it('traversal current entry', async () => {
|
|
const {committed, finished} = locals.navigation.traverseTo(
|
|
locals.navigation.currentEntry.key,
|
|
);
|
|
await expectAsync(committed).toBeResolvedTo(locals.navigation.currentEntry);
|
|
await expectAsync(finished).toBeResolvedTo(locals.navigation.currentEntry);
|
|
expect(locals.navigateEvents.length).toBe(0);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(0);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
});
|
|
|
|
it('second traversal to same entry', async () => {
|
|
const [firstPageEntry] = await setUpEntries();
|
|
const traverseResult = locals.navigation.traverseTo(firstPageEntry.key);
|
|
const duplicateTraverseResult = locals.navigation.traverseTo(firstPageEntry.key);
|
|
expect(traverseResult.committed).toBe(duplicateTraverseResult.committed);
|
|
expect(traverseResult.finished).toBe(duplicateTraverseResult.finished);
|
|
await Promise.all([traverseResult.committed, duplicateTraverseResult.committed]);
|
|
// Only one NavigationCurrentEntryChangeEvent for duplicate traversals
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await Promise.all([traverseResult.finished, duplicateTraverseResult.finished]);
|
|
// Only one NavigateEvent for duplicate traversals.
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
});
|
|
|
|
it('queues traverses', async () => {
|
|
const [firstPageEntry, secondPageEntry] = await setUpEntries();
|
|
|
|
const firstTraverseResult = locals.navigation.traverseTo(firstPageEntry.key);
|
|
const secondTraverseResult = locals.navigation.traverseTo(secondPageEntry.key);
|
|
|
|
const firstTraverseCommittedEntry = await firstTraverseResult.committed;
|
|
expect(firstTraverseCommittedEntry).toBe(firstPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const firstNavigateEvent = locals.navigateEvents[0];
|
|
expect(firstNavigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'traverse',
|
|
destination: jasmine.objectContaining({
|
|
key: firstPageEntry.key,
|
|
}),
|
|
}),
|
|
);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const firstTraverseFinishedEntry = await firstTraverseResult.finished;
|
|
expect(firstTraverseFinishedEntry).toBe(firstPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
|
|
const secondTraverseCommittedEntry = await secondTraverseResult.committed;
|
|
expect(secondTraverseCommittedEntry).toBe(secondPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
expect(locals.navigateEvents.length).toBe(2);
|
|
const secondNavigateEvent = locals.navigateEvents[1];
|
|
expect(secondNavigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'traverse',
|
|
destination: jasmine.objectContaining({
|
|
key: secondPageEntry.key,
|
|
}),
|
|
}),
|
|
);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
const secondTraverseFinishedEntry = await secondTraverseResult.finished;
|
|
expect(secondTraverseFinishedEntry).toBe(secondPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
});
|
|
});
|
|
|
|
describe('integration', () => {
|
|
it('queues traverses after navigate', async () => {
|
|
const [firstPageEntry, secondPageEntry] = await setUpEntries();
|
|
|
|
const firstTraverseResult = locals.navigation.traverseTo(firstPageEntry.key);
|
|
const secondTraverseResult = locals.navigation.traverseTo(secondPageEntry.key);
|
|
locals.pendingInterceptOptions.push({});
|
|
const navigateResult = locals.navigation.navigate('/page4', {
|
|
state: {page4: true},
|
|
});
|
|
|
|
const navigateResultCommittedEntry = await navigateResult.committed;
|
|
expect(navigateResultCommittedEntry.url).toBe('https://test.com/page4');
|
|
expect(locals.navigation.currentEntry).toBe(navigateResultCommittedEntry);
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const firstNavigateEvent = locals.navigateEvents[0];
|
|
expect(firstNavigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'push',
|
|
destination: jasmine.objectContaining({
|
|
url: 'https://test.com/page4',
|
|
}),
|
|
}),
|
|
);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const navigateResultFinishedEntry = await navigateResult.finished;
|
|
expect(navigateResultFinishedEntry).toBe(navigateResultCommittedEntry);
|
|
expect(locals.navigation.currentEntry).toBe(navigateResultCommittedEntry);
|
|
|
|
const firstTraverseCommittedEntry = await firstTraverseResult.committed;
|
|
expect(firstTraverseCommittedEntry).toBe(firstPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigateEvents.length).toBe(2);
|
|
const secondNavigateEvent = locals.navigateEvents[1];
|
|
expect(secondNavigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'traverse',
|
|
destination: jasmine.objectContaining({
|
|
key: firstPageEntry.key,
|
|
}),
|
|
}),
|
|
);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
const firstTraverseFinishedEntry = await firstTraverseResult.finished;
|
|
expect(firstTraverseFinishedEntry).toBe(firstPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
|
|
const secondTraverseCommittedEntry = await secondTraverseResult.committed;
|
|
expect(secondTraverseCommittedEntry).toBe(secondPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
expect(locals.navigateEvents.length).toBe(3);
|
|
const thirdNavigateEvent = locals.navigateEvents[2];
|
|
expect(thirdNavigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'traverse',
|
|
destination: jasmine.objectContaining({
|
|
key: secondPageEntry.key,
|
|
}),
|
|
}),
|
|
);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(3);
|
|
expect(locals.popStateEvents.length).toBe(3);
|
|
const secondTraverseFinishedEntry = await secondTraverseResult.finished;
|
|
expect(secondTraverseFinishedEntry).toBe(secondPageEntry);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
});
|
|
});
|
|
|
|
describe('history API', () => {
|
|
describe('push and replace', () => {
|
|
it('push URL', async () => {
|
|
const initialEntry = locals.navigation.currentEntry;
|
|
|
|
locals.navigation.pushState(undefined, '', '/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
canIntercept: true,
|
|
hashChange: false,
|
|
info: undefined,
|
|
navigationType: 'push',
|
|
userInitiated: false,
|
|
signal: jasmine.any(AbortSignal),
|
|
destination: jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: null,
|
|
id: null,
|
|
index: -1,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(navigateEvent.destination.getState()).toBeUndefined();
|
|
expect(navigateEvent.destination.getHistoryState()).toBeUndefined();
|
|
const currentEntry = locals.navigation.currentEntry;
|
|
expect(currentEntry).toEqual(
|
|
jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: '1',
|
|
id: '1',
|
|
index: 1,
|
|
sameDocument: true,
|
|
}),
|
|
);
|
|
expect(currentEntry.getState()).toBeUndefined();
|
|
expect(currentEntry.getHistoryState()).toBeUndefined();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
const currentEntryChangeEvent = locals.navigationCurrentEntryChangeEvents[0];
|
|
expect(currentEntryChangeEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'push',
|
|
from: jasmine.objectContaining({
|
|
url: initialEntry.url,
|
|
key: initialEntry.key,
|
|
id: initialEntry.id,
|
|
index: initialEntry.index,
|
|
sameDocument: initialEntry.sameDocument,
|
|
}),
|
|
}),
|
|
);
|
|
expect(currentEntryChangeEvent.from.getState()).toBe(initialEntry.getState());
|
|
expect(currentEntryChangeEvent.from.getHistoryState()).toBeNull();
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
});
|
|
|
|
it('replace URL', async () => {
|
|
const initialEntry = locals.navigation.currentEntry;
|
|
|
|
locals.navigation.replaceState(null, '', '/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
canIntercept: true,
|
|
hashChange: false,
|
|
info: undefined,
|
|
navigationType: 'replace',
|
|
userInitiated: false,
|
|
signal: jasmine.any(AbortSignal),
|
|
destination: jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: null,
|
|
id: null,
|
|
index: -1,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(navigateEvent.destination.getState()).toBeUndefined();
|
|
expect(navigateEvent.destination.getHistoryState()).toBeNull();
|
|
const currentEntry = locals.navigation.currentEntry;
|
|
expect(currentEntry).toEqual(
|
|
jasmine.objectContaining({
|
|
url: 'https://test.com/test',
|
|
key: '0',
|
|
id: '1',
|
|
index: 0,
|
|
sameDocument: true,
|
|
}),
|
|
);
|
|
expect(currentEntry.getState()).toBeUndefined();
|
|
expect(currentEntry.getHistoryState()).toBeNull();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
const currentEntryChangeEvent = locals.navigationCurrentEntryChangeEvents[0];
|
|
expect(currentEntryChangeEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'replace',
|
|
from: jasmine.objectContaining({
|
|
url: initialEntry.url,
|
|
key: initialEntry.key,
|
|
id: initialEntry.id,
|
|
index: initialEntry.index,
|
|
sameDocument: initialEntry.sameDocument,
|
|
}),
|
|
}),
|
|
);
|
|
expect(currentEntryChangeEvent.from.getState()).toBe(initialEntry.getState());
|
|
expect(currentEntryChangeEvent.from.getHistoryState()).toBeNull();
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
});
|
|
|
|
it('push URL with history state', async () => {
|
|
locals.pendingInterceptOptions.push({});
|
|
const state = {test: true};
|
|
locals.navigation.pushState(state, '', '/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent.destination.getHistoryState()).toEqual(state);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.navigation.currentEntry.getHistoryState()).toEqual(state);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
});
|
|
|
|
it('replace URL with history state', async () => {
|
|
locals.pendingInterceptOptions.push({});
|
|
const state = {test: true};
|
|
locals.navigation.replaceState(state, '', '/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent.destination.getHistoryState()).toEqual(state);
|
|
expect(locals.navigation.currentEntry.getHistoryState()).toEqual(state);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
});
|
|
|
|
it('push URL with hashchange', async () => {
|
|
locals.navigation.pushState(null, '', '#test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent.destination.url).toBe('https://test.com/#test');
|
|
expect(navigateEvent.hashChange).toBeTrue();
|
|
expect(locals.navigation.currentEntry.url).toBe('https://test.com/#test');
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
});
|
|
|
|
it('replace URL with hashchange', async () => {
|
|
locals.navigation.replaceState(null, '', '#test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(navigateEvent.destination.url).toBe('https://test.com/#test');
|
|
expect(navigateEvent.hashChange).toBeTrue();
|
|
expect(locals.navigation.currentEntry.url).toBe('https://test.com/#test');
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
});
|
|
|
|
it('push URL with handler', async () => {
|
|
let handlerFinishedResolve!: (value: Promise<undefined> | undefined) => void;
|
|
const handlerFinished = new Promise<undefined>((resolve) => {
|
|
handlerFinishedResolve = resolve;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerFinished,
|
|
});
|
|
locals.navigation.pushState(null, '', '/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const currentEntry = locals.navigation.currentEntry;
|
|
expect(currentEntry.url).toBe('https://test.com/test');
|
|
expect(currentEntry.key).toBe('1');
|
|
expect(currentEntry.id).toBe('1');
|
|
expect(currentEntry.index).toBe(1);
|
|
expect(currentEntry.sameDocument).toBeTrue();
|
|
expect(currentEntry.getState()).toBeUndefined();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
handlerFinishedResolve(undefined);
|
|
expect(locals.navigation.currentEntry).toBe(currentEntry);
|
|
});
|
|
|
|
it('replace URL with handler', async () => {
|
|
let handlerFinishedResolve!: (value: Promise<undefined> | undefined) => void;
|
|
const handlerFinished = new Promise<undefined>((resolve) => {
|
|
handlerFinishedResolve = resolve;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerFinished,
|
|
});
|
|
locals.navigation.replaceState(null, '', '/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const currentEntry = locals.navigation.currentEntry;
|
|
expect(currentEntry.url).toBe('https://test.com/test');
|
|
expect(currentEntry.key).toBe('0');
|
|
expect(currentEntry.id).toBe('1');
|
|
expect(currentEntry.index).toBe(0);
|
|
expect(currentEntry.sameDocument).toBeTrue();
|
|
expect(currentEntry.getState()).toBeUndefined();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
handlerFinishedResolve(undefined);
|
|
expect(locals.navigation.currentEntry).toBe(currentEntry);
|
|
});
|
|
|
|
it('push with interruption', async () => {
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => new Promise(() => {}),
|
|
});
|
|
|
|
locals.navigation.pushState(null, '', '/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
locals.pendingInterceptOptions.push({});
|
|
const interruptResult = locals.navigation.navigate('/interrupt');
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
await interruptResult.committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await interruptResult.finished;
|
|
});
|
|
|
|
it('replace with interruption', async () => {
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => new Promise(() => {}),
|
|
});
|
|
|
|
locals.navigation.replaceState(null, '', '/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
locals.pendingInterceptOptions.push({});
|
|
const interruptResult = locals.navigation.navigate('/interrupt');
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
await interruptResult.committed;
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
await interruptResult.finished;
|
|
});
|
|
|
|
it('push with handler reject', async () => {
|
|
let handlerFinishedReject!: (reason: unknown) => void;
|
|
const handlerPromise = new Promise<undefined>((resolve, reject) => {
|
|
handlerFinishedReject = reject;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerPromise,
|
|
});
|
|
|
|
locals.navigation.pushState(null, '', '/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
const error = new Error('rejected');
|
|
handlerFinishedReject(error);
|
|
await expectAsync(handlerPromise).toBeRejectedWith(error);
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
});
|
|
|
|
it('replace with reject', async () => {
|
|
let handlerFinishedReject!: (reason: unknown) => void;
|
|
const handlerPromise = new Promise<undefined>((resolve, reject) => {
|
|
handlerFinishedReject = reject;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerPromise,
|
|
});
|
|
|
|
locals.navigation.replaceState(null, '', '/test');
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
const navigateEvent = locals.navigateEvents[0];
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(0);
|
|
const error = new Error('rejected');
|
|
handlerFinishedReject(error);
|
|
await expectAsync(handlerPromise).toBeRejectedWith(error);
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
});
|
|
});
|
|
|
|
describe('traversal', () => {
|
|
it('go back', async () => {
|
|
expect(locals.navigation.canGoBack).toBeFalse();
|
|
expect(locals.navigation.canGoForward).toBeFalse();
|
|
const [firstPageEntry, , thirdPageEntry] = await setUpEntries();
|
|
expect(locals.navigation.canGoBack).toBeTrue();
|
|
expect(locals.navigation.canGoForward).toBeFalse();
|
|
locals.navigation.go(-2);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
canIntercept: true,
|
|
hashChange: false,
|
|
info: undefined,
|
|
navigationType: 'traverse',
|
|
signal: jasmine.any(AbortSignal),
|
|
userInitiated: false,
|
|
destination: jasmine.objectContaining({
|
|
url: firstPageEntry.url!,
|
|
key: firstPageEntry.key,
|
|
id: firstPageEntry.id,
|
|
index: firstPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(navigateEvent.destination.getState()).toEqual(firstPageEntry.getState());
|
|
expect(navigateEvent.destination.getHistoryState()).toEqual(
|
|
firstPageEntry.getHistoryState(),
|
|
);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
const currentEntryChangeEvent = locals.navigationCurrentEntryChangeEvents[0];
|
|
expect(currentEntryChangeEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'traverse',
|
|
from: jasmine.objectContaining({
|
|
url: thirdPageEntry.url!,
|
|
key: thirdPageEntry.key,
|
|
id: thirdPageEntry.id,
|
|
index: thirdPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(currentEntryChangeEvent.from.getState()).toEqual(thirdPageEntry.getState());
|
|
expect(currentEntryChangeEvent.from.getHistoryState()).toEqual(
|
|
thirdPageEntry.getHistoryState(),
|
|
);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const popStateEvent = locals.popStateEvents[0];
|
|
expect(popStateEvent.state).toBeNull();
|
|
expect(locals.navigation.canGoBack).toBeTrue();
|
|
expect(locals.navigation.canGoForward).toBeTrue();
|
|
});
|
|
|
|
it('go forward', async () => {
|
|
expect(locals.navigation.canGoBack).toBeFalse();
|
|
expect(locals.navigation.canGoForward).toBeFalse();
|
|
const [firstPageEntry, , thirdPageEntry] = await setUpEntries();
|
|
await locals.navigation.traverseTo(firstPageEntry.key).finished;
|
|
locals.navigateEvents.length = 0;
|
|
locals.navigationCurrentEntryChangeEvents.length = 0;
|
|
locals.popStateEvents.length = 0;
|
|
expect(locals.navigation.canGoBack).toBeTrue();
|
|
expect(locals.navigation.canGoForward).toBeTrue();
|
|
|
|
locals.navigation.go(2);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
canIntercept: true,
|
|
hashChange: false,
|
|
info: undefined,
|
|
navigationType: 'traverse',
|
|
signal: jasmine.any(AbortSignal),
|
|
userInitiated: false,
|
|
destination: jasmine.objectContaining({
|
|
url: thirdPageEntry.url!,
|
|
key: thirdPageEntry.key,
|
|
id: thirdPageEntry.id,
|
|
index: thirdPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(navigateEvent.destination.getState()).toEqual(thirdPageEntry.getState());
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
const currentEntryChangeEvent = locals.navigationCurrentEntryChangeEvents[0];
|
|
expect(currentEntryChangeEvent).toEqual(
|
|
jasmine.objectContaining({
|
|
navigationType: 'traverse',
|
|
from: jasmine.objectContaining({
|
|
url: firstPageEntry.url!,
|
|
key: firstPageEntry.key,
|
|
id: firstPageEntry.id,
|
|
index: firstPageEntry.index,
|
|
sameDocument: true,
|
|
}),
|
|
}),
|
|
);
|
|
expect(currentEntryChangeEvent.from.getState()).toEqual(firstPageEntry.getState());
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const popStateEvent = locals.popStateEvents[0];
|
|
expect(popStateEvent.state).toBeNull();
|
|
expect(locals.navigation.currentEntry).toBe(thirdPageEntry);
|
|
expect(locals.navigateEvents.length).toBe(1);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
expect(locals.navigation.canGoBack).toBeTrue();
|
|
expect(locals.navigation.canGoForward).toBeFalse();
|
|
});
|
|
|
|
it('go back with hashchange', async () => {
|
|
await setUpEntries({hash: true});
|
|
|
|
locals.navigation.go(-2);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent.hashChange).toBeTrue();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
});
|
|
|
|
it('go back with history state', async () => {
|
|
const [firstPageEntry] = setUpEntriesWithHistory();
|
|
|
|
locals.navigation.go(-2);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent.destination.getHistoryState()).toEqual(
|
|
firstPageEntry.getHistoryState(),
|
|
);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const popStateEvent = locals.popStateEvents[0];
|
|
expect(popStateEvent.state).toEqual(firstPageEntry.getHistoryState());
|
|
});
|
|
|
|
it('go forward with hashchange', async () => {
|
|
const [firstPageEntry] = await setUpEntries({hash: true});
|
|
await locals.navigation.traverseTo(firstPageEntry.key).finished;
|
|
locals.navigateEvents.length = 0;
|
|
locals.navigationCurrentEntryChangeEvents.length = 0;
|
|
locals.popStateEvents.length = 0;
|
|
|
|
locals.navigation.go(2);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(navigateEvent.hashChange).toBeTrue();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
});
|
|
|
|
it('go with handler', async () => {
|
|
const [firstPageEntry] = await setUpEntries();
|
|
let handlerFinishedResolve!: (value: Promise<undefined> | undefined) => void;
|
|
const handlerFinished = new Promise<undefined>((resolve) => {
|
|
handlerFinishedResolve = resolve;
|
|
});
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => handlerFinished,
|
|
});
|
|
locals.navigation.go(-2);
|
|
await locals.nextNavigateEvent();
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
handlerFinishedResolve(undefined);
|
|
});
|
|
|
|
it('go with interruption', async () => {
|
|
const [firstPageEntry] = await setUpEntries();
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () => new Promise(() => {}),
|
|
});
|
|
locals.navigation.go(-2);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
expect(navigateEvent.signal.aborted).toBeFalse();
|
|
locals.pendingInterceptOptions.push({});
|
|
const interruptResult = locals.navigation.navigate('/interrupt');
|
|
await interruptResult.committed;
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
await interruptResult.finished;
|
|
});
|
|
|
|
it('go with reject', async () => {
|
|
const [firstPageEntry] = await setUpEntries();
|
|
let handlerFinishedReject!: (reason: unknown) => void;
|
|
locals.pendingInterceptOptions.push({
|
|
handler: () =>
|
|
new Promise<undefined>((resolve, reject) => {
|
|
handlerFinishedReject = reject;
|
|
}),
|
|
});
|
|
|
|
locals.navigation.go(-2);
|
|
const navigateEvent = await locals.nextNavigateEvent();
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
expect(navigateEvent.signal.aborted).toBeFalse();
|
|
const error = new Error('rejected');
|
|
handlerFinishedReject(error);
|
|
await new Promise((resolve) => {
|
|
navigateEvent.signal.addEventListener('abort', resolve);
|
|
});
|
|
expect(navigateEvent.signal.aborted).toBeTrue();
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
});
|
|
|
|
it('go synchronously', async () => {
|
|
const [, secondPageEntry] = await setUpEntries();
|
|
locals.navigation.setSynchronousTraversalsForTesting(true);
|
|
|
|
locals.navigation.go(-1);
|
|
// Synchronously navigates.
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
await expectAsync(locals.nextNavigateEvent()).toBePending();
|
|
});
|
|
|
|
it('go out of bounds', async () => {
|
|
locals.navigation.go(-1);
|
|
await expectAsync(locals.nextNavigateEvent()).toBePending();
|
|
locals.navigation.go(1);
|
|
await expectAsync(locals.nextNavigateEvent()).toBePending();
|
|
});
|
|
|
|
it('go queues', async () => {
|
|
const [firstPageEntry, secondPageEntry] = await setUpEntries();
|
|
|
|
locals.navigation.go(-1);
|
|
locals.navigation.go(-1);
|
|
const firstNavigateEvent = await locals.nextNavigateEvent();
|
|
expect(firstNavigateEvent.destination.key).toBe(secondPageEntry.key);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const secondNavigateEvent = await locals.nextNavigateEvent();
|
|
expect(secondNavigateEvent.destination.key).toBe(firstPageEntry.key);
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
});
|
|
|
|
it('go queues both directions', async () => {
|
|
const [firstPageEntry, secondPageEntry] = await setUpEntries();
|
|
|
|
locals.navigation.go(-2);
|
|
locals.navigation.go(1);
|
|
const firstNavigateEvent = await locals.nextNavigateEvent();
|
|
expect(firstNavigateEvent.destination.key).toBe(firstPageEntry.key);
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const secondNavigateEvent = await locals.nextNavigateEvent();
|
|
expect(secondNavigateEvent.destination.key).toBe(secondPageEntry.key);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
});
|
|
|
|
it('go queues with back', async () => {
|
|
const [firstPageEntry, secondPageEntry] = await setUpEntries();
|
|
|
|
locals.navigation.back();
|
|
locals.navigation.go(-1);
|
|
const firstNavigateEvent = await locals.nextNavigateEvent();
|
|
expect(firstNavigateEvent.destination.key).toBe(secondPageEntry.key);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const secondNavigateEvent = await locals.nextNavigateEvent();
|
|
expect(secondNavigateEvent.destination.key).toBe(firstPageEntry.key);
|
|
expect(locals.navigation.currentEntry).toBe(firstPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
});
|
|
|
|
it('go queues with forward', async () => {
|
|
const [, secondPageEntry, thirdPageEntry] = await setUpEntries();
|
|
await locals.navigation.back().finished;
|
|
locals.navigateEvents.length = 0;
|
|
locals.navigationCurrentEntryChangeEvents.length = 0;
|
|
locals.popStateEvents.length = 0;
|
|
|
|
locals.navigation.forward();
|
|
locals.navigation.go(-1);
|
|
const firstNavigateEvent = await locals.nextNavigateEvent();
|
|
expect(firstNavigateEvent.destination.key).toBe(thirdPageEntry.key);
|
|
expect(locals.navigation.currentEntry).toBe(thirdPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const secondNavigateEvent = await locals.nextNavigateEvent();
|
|
expect(secondNavigateEvent.destination.key).toBe(secondPageEntry.key);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
});
|
|
|
|
it('go after synchronous navigate', async () => {
|
|
const [, secondPageEntry, thirdPageEntry] = await setUpEntries();
|
|
|
|
// Back to /page2
|
|
locals.navigation.go(-1);
|
|
// Push /interrupt on top of current /page3
|
|
locals.pendingInterceptOptions.push({});
|
|
const interruptResult = locals.navigation.navigate('/interrupt');
|
|
// Back from /interrupt to /page3.
|
|
locals.navigation.go(-1);
|
|
const interruptEntry = await interruptResult.finished;
|
|
expect(locals.navigation.currentEntry).toBe(interruptEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(1);
|
|
expect(locals.popStateEvents.length).toBe(1);
|
|
const firstNavigateEvent = await locals.nextNavigateEvent();
|
|
expect(firstNavigateEvent.destination.key).toBe(secondPageEntry.key);
|
|
expect(locals.navigation.currentEntry).toBe(secondPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(2);
|
|
expect(locals.popStateEvents.length).toBe(2);
|
|
const secondNavigateEvent = await locals.nextNavigateEvent();
|
|
expect(secondNavigateEvent.destination.key).toBe(thirdPageEntry.key);
|
|
expect(locals.navigation.currentEntry).toBe(thirdPageEntry);
|
|
expect(locals.navigationCurrentEntryChangeEvents.length).toBe(3);
|
|
expect(locals.popStateEvents.length).toBe(3);
|
|
});
|
|
});
|
|
});
|
|
});
|