Skip to content

Commit

Permalink
fix(kit): prevent navigation to parent page if navigation occurs from…
Browse files Browse the repository at this point in the history
… dialog (#6944)
  • Loading branch information
splincode authored Mar 4, 2024
1 parent 8e5a72b commit d5bf23d
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {ChangeDetectionStrategy, Component, Inject, Injector, Self} from '@angular/core';
import {ActivatedRoute, Router, UrlSegment} from '@angular/router';
import {ActivatedRoute, Router} from '@angular/router';
import {TuiDestroyService} from '@taiga-ui/cdk';
import {TuiDialogService} from '@taiga-ui/core';
import {PolymorpheusComponent} from '@tinkoff/ng-polymorpheus';
Expand All @@ -12,6 +12,8 @@ import {takeUntil} from 'rxjs/operators';
providers: [TuiDestroyService],
})
export class TuiRoutableDialogComponent {
private readonly initialUrl = this.router.url;

constructor(
@Inject(ActivatedRoute) private readonly route: ActivatedRoute,
@Inject(Router) private readonly router: Router,
Expand All @@ -25,26 +27,26 @@ export class TuiRoutableDialogComponent {
this.route.snapshot.data['dialogOptions'],
)
.pipe(takeUntil(destroy$))
.subscribe({
complete: () => this.navigateToParent(),
});
.subscribe({complete: () => this.onDialogClosing()});
}

private navigateToParent(): void {
const isLazy = this.route.snapshot.data['isLazy'];
private get lazyLoadedBackUrl(): string {
return (this.route.parent?.snapshot.url || []).map(() => '..').join('/');
}

private onDialogClosing(): void {
if (this.initialUrl === this.router.url) {
this.navigateToParent();
}
}

const backUrl = isLazy
? this.getLazyLoadedBackUrl()
private navigateToParent(): void {
const backUrl = this.route.snapshot.data['isLazy']
? this.lazyLoadedBackUrl
: this.route.snapshot.data['backUrl'];

void this.router.navigate([backUrl], {
relativeTo: this.route,
});
}

private getLazyLoadedBackUrl(): string {
const urlSegments: UrlSegment[] = this.route.parent?.snapshot.url || [];

return urlSegments.map(() => '..').join('/');
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,10 @@ describe('TuiRoutableDialog', () => {
let tuiDialogService: TuiDialogService;
let router: Router;

function createComponent(activatedRoute?: Partial<ActivatedRoute>): void {
function createComponent(
activatedRoute?: Partial<ActivatedRoute>,
closeDialogImmediately = true,
): void {
tuiDialogService = mock(TuiDialogService);
router = mock(Router);

Expand All @@ -54,16 +57,21 @@ describe('TuiRoutableDialog', () => {
],
}).compileComponents();

when(tuiDialogService.open(anything(), anything())).thenReturn(NEVER);
when(tuiDialogService.open(anything(), anything())).thenReturn(
closeDialogImmediately ? EMPTY : NEVER,
);

fixture = TestBed.createComponent(TuiRoutableDialogComponent);
}

it('Dialog content component is passed to the dialog open method, when RoutableDialog is created', () => {
// arrange
createComponent();

// act
fixture.detectChanges();

// assert
verify(
tuiDialogService.open(
deepEqual(new PolymorpheusComponent(DialogComponent, anything())),
Expand All @@ -73,6 +81,7 @@ describe('TuiRoutableDialog', () => {
});

it('dialog options are passed to the dialog open method', () => {
// arrange
const dialogOptions = {
dismissible: true,
};
Expand All @@ -86,13 +95,16 @@ describe('TuiRoutableDialog', () => {
} as unknown as ActivatedRouteSnapshot,
});

// act
fixture.detectChanges();

// assert
verify(tuiDialogService.open(anything(), deepEqual(dialogOptions))).once();
});

it('Closing the dialog navigates back to the parent route for lazy loaded case', fakeAsync(() => {
createComponent({
// arrange
const activatedRouteMock = {
snapshot: {
data: {
dialog: DialogComponent,
Expand All @@ -114,21 +126,26 @@ describe('TuiRoutableDialog', () => {
],
} as unknown as ActivatedRouteSnapshot,
} as unknown as ActivatedRoute,
});
};

createComponent(activatedRouteMock);

when(tuiDialogService.open(anything(), anything())).thenReturn(EMPTY);
// act
fixture.detectChanges();

// assert
verify(
router.navigate(
deepEqual(['../../..']),
deepEqual({
relativeTo: DEFAULT_ACTIVATED_ROUTE_MOCK,
relativeTo: activatedRouteMock,
}) as unknown as NavigationExtras,
),
);
).once();
}));

it('Closing the dialog navigates back to the parent route for eager loaded case', fakeAsync(() => {
// arrange
createComponent({
snapshot: {
data: {
Expand All @@ -138,8 +155,35 @@ describe('TuiRoutableDialog', () => {
} as unknown as ActivatedRouteSnapshot,
});

when(tuiDialogService.open(anything(), anything())).thenReturn(EMPTY);
// act
fixture.detectChanges();

verify(router.navigate(deepEqual(['../../..']), anything()));
// assert
verify(router.navigate(deepEqual(['../../..']), anything())).once();
}));

it('if navigation occurs from a dialog, then the navigation to parent is not called', () => {
// arrange
createComponent(
{
snapshot: {
data: {
dialog: DialogComponent,
backUrl: '../../..',
} as unknown as Data,
} as unknown as ActivatedRouteSnapshot,
},
false, // will close dialog only on destroy
);

fixture.detectChanges();

when(router.url).thenReturn('new/route/after/navigation'); // means the url has changed

// act
fixture.destroy(); // should trigger dialog closing logic

// assert
verify(router.navigate(anything(), anything())).never();
});
});

0 comments on commit d5bf23d

Please sign in to comment.