Skip to content
Closed
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
3 changes: 3 additions & 0 deletions src/hooks/lazyLoad.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ const lazyLoadHook: TransitionHookFn = (transition: Transition) => {

// No matching state found, so let .sync() choose the best non-state match/otherwise
router.urlService.sync();
// Abort this transition; it would supersede the transition started by sync()
// and activate the (now deregistered) future state placeholder (issue #850)
return false;
}

const promises = transition
Expand Down
52 changes: 51 additions & 1 deletion test/lazyLoadSpec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { UIRouter, TransitionService, StateService } from '../src/index';
import { UIRouter, TransitionService, StateService, RejectType, Rejection } from '../src/index';

import { StateRegistry } from '../src/state/stateRegistry';
import { UrlRouter } from '../src/url/urlRouter';
Expand Down Expand Up @@ -298,6 +298,56 @@ describe('future state', function() {
});
});

// Tests for https://github.com/ui-router/core/issues/850
describe('triggered by a URL sync that the lazy loaded states do not match', () => {
let lazyLoadCount: number;

beforeEach(() => {
lazyLoadCount = 0;
$state.defaultErrorHandler(() => {});
$registry.register({ name: 'notFound' });
router.urlService.rules.otherwise({ state: 'notFound' });
$registry.register({
name: 'A.**',
url: '/a',
lazyLoad: () => {
lazyLoadCount++;
return Promise.resolve({ states: [{ name: 'A', url: '/a/:id' }] });
},
});
});

it('should activate the otherwise() rule instead of the deregistered placeholder', (done) => {
router.urlService.url('/a/no/such/page');
router.urlService.sync();

$transitions.onSuccess({}, () => {
expect(lazyLoadCount).toBe(1);
expect($state.current.name).toBe('notFound');
expect($registry.get('A.**')).toBeFalsy();
done();
});
});

it('should abort the original transition to the deregistered placeholder', (done) => {
const errors: { targetName: string; error: Rejection }[] = [];
$transitions.onError({}, (trans) => {
errors.push({ targetName: trans.targetState().name(), error: trans.error() });
});

// Only change the url; the url listener syncs it (avoids starting a second, duplicate transition)
router.urlService.url('/a/no/such/page');

$transitions.onSuccess({}, () => {
expect(errors.length).toBe(1);
expect(errors[0].targetName).toBe('A.**');
expect(errors[0].error.type).toBe(RejectType.ABORTED);
expect($state.current.name).toBe('notFound');
done();
});
});
});

describe('that resolves to multiple states', () => {
let futureA, futureB;
const lazyA = { name: 'A', url: '/a/:id', params: { id: 'default' } };
Expand Down