Skip to content

Commit

Permalink
404 support for non existing paths (#162)
Browse files Browse the repository at this point in the history
Handle 404 when route is absolutely not found
  • Loading branch information
parostatkiem authored and kwiatekus committed Nov 20, 2018
1 parent c6acf88 commit 6eb2406
Show file tree
Hide file tree
Showing 10 changed files with 312 additions and 82 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,39 @@ describe('Luigi client features', () => {
});
});

describe('linkManager wrong paths navigation', () => {
let $iframeBody;
beforeEach(() => {
cy.get('iframe').then($iframe => {
$iframeBody = $iframe.contents().find('body');
cy.goToFeaturesPage($iframeBody);
});
});
it('navigate to a partly wrong link', () => {
cy.wrap($iframeBody)
.contains('Partly wrong link')
.click();
cy.location().should(loc => {
expect(loc.hash).to.eq('#/projects/pr2/miscellaneous2');
});
cy.get('.fd-alert').contains(
'Could not map the exact target node for the requested route projects/pr2/miscellaneous2/maskopatol'
);
});

it('navigate to a totally wrong link', () => {
cy.wrap($iframeBody)
.contains('Totally wrong link')
.click();
cy.location().should(loc => {
expect(loc.hash).to.eq('#/overview');
});
cy.get('.fd-alert').contains(
'Could not find the requested route maskopatol/has/a/child'
);
});
});

describe('uxManager', () => {
it('backdrop', () => {
cy.wait(500);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,76 +1,99 @@
<section class="fd-section">
<div class="fd-section__header">
<h1 class="fd-section__title">Project {{ projectId }}</h1>
</div>
<div class="fd-panel">
<p><strong>LuigiClient uxManager methods:</strong></p>
<button class="fd-button" (click)="toggleModal()">Add backdrop</button>
<p>
<app-code-snippet data="luigiClient.uxManager().addBackdrop()"></app-code-snippet>
<app-code-snippet data="luigiClient.uxManager().removeBackdrop()"></app-code-snippet>
</p>
</div>
<div class="fd-section__header">
<h1 class="fd-section__title">Project {{ projectId }}</h1>
</div>
<div class="fd-panel">
<p><strong>LuigiClient uxManager methods:</strong></p>
<button class="fd-button" (click)="toggleModal()">Add backdrop</button>
<p>
<app-code-snippet data="luigiClient.uxManager().addBackdrop()"></app-code-snippet>
<app-code-snippet data="luigiClient.uxManager().removeBackdrop()"></app-code-snippet>
</p>
</div>
</section>

<section class="fd-section" *ngIf="preservedViewCallbackContext">
<div class="fd-panel">
<div class="fd-alert" role="alert">
<span class="fd-status-label fd-status-label--available"></span> Context received from linkManager().goBack():<br />
<pre>{{ preservedViewCallbackContext | json }}</pre>
</div>
</div>
<div class="fd-panel">
<div class="fd-alert" role="alert">
<span class="fd-status-label fd-status-label--available"></span> Context received from linkManager().goBack():<br />
<pre>{{ preservedViewCallbackContext | json }}</pre>
</div>
</div>
</section>

<section class="fd-section link-manager" *ngIf="projectId && projectId !== 'overview'">
<div class="fd-panel">
<p><strong>LuigiClient linkManager methods:</strong></p>
<ul class="fd-list-group">
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().navigate('/overview')">absolute: to overview</a>
<app-code-snippet data="luigiClient.linkManager().navigate('/overview')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().navigate('users/groups/stakeholders')">relative: to stakeholders</a>
<app-code-snippet data="luigiClient.linkManager().navigate('users/groups/stakeholders')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().fromClosestContext().navigate('/users/groups/stakeholders')">closest parent: to stakeholders</a>
<app-code-snippet data="luigiClient.linkManager().fromClosestContext().navigate('/users/groups/stakeholders')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().fromContext('project').navigate('/settings')">parent by name: project to settings</a>
<app-code-snippet data="luigiClient.linkManager().fromContext('project').navigate('/settings')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().fromClosestContext().withParams({foo: 'bar'}).navigate('settings')">project to settings with params (foo=bar)</a>
<app-code-snippet data="luigiClient.linkManager().fromClosestContext().withParams({foo: 'bar'}).navigate('settings')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().fromContext('FOOMARK').navigate('/settings')">parent by name: with nonexisting context</a> (look at the console)
<app-code-snippet data="luigiClient.linkManager().fromContext('FOOMARK').navigate('/settings')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().navigate('/settings', null, true)">with preserved view: project to global settings and back</a>
<app-code-snippet data="luigiClient.linkManager().navigate('/settings', null, true)"></app-code-snippet>
</li>
<li class="fd-list-group__item check-path">
<span>Check if path exists</span>
<app-code-snippet data="luigiClient.linkManager().pathExists('{{pathExists.formValue}}')"></app-code-snippet>
<span>
<input type="text" [(ngModel)]="pathExists.formValue" (input)="resetPathExistsResult()"/>
<button class="fd-button" (click)="checkIfPathExists()">Check</button>
</span>
<p class="check-path-result">
<ng-container *ngIf="pathExists.result === true">
Path {{pathExists.formValue}} exists!
</ng-container>
<ng-container *ngIf="pathExists.result === false">
Path {{pathExists.formValue}} does not exist!
</ng-container>
</p>
</li>
</ul>
</div>
<div class="fd-panel">
<p><strong>LuigiClient linkManager methods:</strong></p>
<ul class="fd-list-group">
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().navigate('/overview')">absolute: to overview</a>
<app-code-snippet data="luigiClient.linkManager().navigate('/overview')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().navigate('users/groups/stakeholders')">relative: to stakeholders</a>
<app-code-snippet data="luigiClient.linkManager().navigate('users/groups/stakeholders')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().fromClosestContext().navigate('/users/groups/stakeholders')">
closest parent: to stakeholders</a>
<app-code-snippet data="luigiClient.linkManager().fromClosestContext().navigate('/users/groups/stakeholders')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().fromContext('project').navigate('/settings')">
parent by name: project to settings</a>
<app-code-snippet data="luigiClient.linkManager().fromContext('project').navigate('/settings')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().fromClosestContext().withParams({foo: 'bar'}).navigate('settings')">
project to settings with params (foo=bar)</a>
<app-code-snippet data="luigiClient.linkManager().fromClosestContext().withParams({foo: 'bar'}).navigate('settings')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().fromContext('FOOMARK').navigate('/settings')">
parent by name: with nonexisting context</a> (look at the console)
<app-code-snippet data="luigiClient.linkManager().fromContext('FOOMARK').navigate('/settings')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().navigate('/settings', null, true)">
with preserved view: project to global settings and back</a>
<app-code-snippet data="luigiClient.linkManager().navigate('/settings', null, true)"></app-code-snippet>
</li>
<li class="fd-list-group__item check-path">
<span>Check if path exists</span>
<app-code-snippet data="luigiClient.linkManager().pathExists('{{pathExists.formValue}}')"></app-code-snippet>
<span>
<input type="text" [(ngModel)]="pathExists.formValue" (input)="resetPathExistsResult()" />
<button class="fd-button" (click)="checkIfPathExists()">Check</button>
</span>
<p class="check-path-result">
<ng-container *ngIf="pathExists.result === true">
Path {{pathExists.formValue}} exists!
</ng-container>
<ng-container *ngIf="pathExists.result === false">
Path {{pathExists.formValue}} does not exist!
</ng-container>
</p>
</li>
</ul>
</div>
</section>

<app-modal [modalActive]="modalActive" (modalClosed)="toggleModal()"></app-modal>
<section class="fd-section" *ngIf="projectId && projectId !== 'overview'">
<div class="fd-panel">
<p><strong>LuigiClient - wrong paths in linkManager.navigate():</strong></p>
<ul class="fd-list-group">
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().navigate('/projects/pr2/miscellaneous2/maskopatol')">Partly
wrong link
</a>
<app-code-snippet data="luigiClient.linkManager().navigate('/projects/pr2/miscellaneous2/maskopatol')"></app-code-snippet>
</li>
<li class="fd-list-group__item">
<a href="javascript:void(0)" (click)="luigiClient.linkManager().navigate('/maskopatol/has/a/child')">Totally wrong link</a>
<app-code-snippet data="luigiClient.linkManager().navigate('/maskopatol/has/a/child')"></app-code-snippet>
</li>
</ul>
</div>
</section>

<app-modal [modalActive]="modalActive" (modalClosed)="toggleModal()"></app-modal>
2 changes: 1 addition & 1 deletion core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"scripts": {
"bundle": "webpack --display-error-details",
"bundle-develop": "webpack -d --watch",
"test": "./node_modules/mocha/bin/mocha --require babel-register --require babel-polyfill --require jsdom-global/register",
"test": "./node_modules/mocha/bin/mocha --require babel-register --require babel-polyfill --require jsdom-global/register --recursive test",
"bundlesize": "bundlesize",
"prepush": "npm test && npm run bundle && npm run bundlesize"
},
Expand Down
33 changes: 28 additions & 5 deletions core/src/App.html
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
<div id="app {hideNav? 'no-nav' : ''}">
{#if alert && alert.message}
<div class="fd-alert fd-alert--error fd-alert--dismissible" role="alert" id="j2ALl423">
<button on:click="set({alert:null})" class="fd-alert__close" aria-controls="j2ALl423" aria-label="Close"></button>
{alert.message} {alert.link}
</div>
{/if}
<Backdrop>
<div class="fd-page iframeContainer" use:init="context"></div>
</Backdrop>
Expand Down Expand Up @@ -122,12 +128,14 @@
};

const handleNavigation = async (component, data, config) => {
const path = buildPath(component, data.params);
const matchedPath = await Routing.matchPath(path);
if (matchedPath !== null) {
addPreserveView(component, data, config);
Routing.navigateTo(matchedPath);
let path = buildPath(component, data.params);

if (path[0] !== '/') {
path = '/' + path; //add leading slash if necessary
}

addPreserveView(component, data, config);
Routing.navigateTo(path); //navigate to the raw path. Any errors/alerts are handled later
};

const sendContextToClient = (component, config, goBackContext = {}) => {
Expand Down Expand Up @@ -296,6 +304,7 @@
<style type="text/scss">
@import 'node_modules/fundamental-ui/scss/layout/page';
@import 'node_modules/fundamental-ui/scss/components/spinner';
@import 'node_modules/fundamental-ui/scss/components/alert';
@import 'static.css';

:global(html) {
Expand Down Expand Up @@ -369,4 +378,18 @@
right: 0;
}
}

$topNavHeight: 50px;
$leftNavWidth: 320px;
.fd-alert {
position: absolute;
min-width: 20rem;
width: calc(100% - 2 * (#{$leftNavWidth} + 1rem));
top: calc(#{$topNavHeight} + 1rem);
left: calc(#{$leftNavWidth} + 1rem);
z-index: 2;
.fd-alert__close {
cursor: pointer;
}
}
</style>
26 changes: 24 additions & 2 deletions core/src/services/routing.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { LuigiConfig } from './config';
import {
getPathWithoutHash,
getUrlWithoutHash,
containsAllSegments,
isIE,
getConfigValueFromObject
} from '../utilities/helpers';
Expand Down Expand Up @@ -308,10 +309,31 @@ export const handleRouteChange = async (path, component, node, config) => {
if (routeExists) {
const defaultChildNode = getDefaultChildNode(pathData);
navigateTo(`${pathUrl ? `/${pathUrl}` : ''}/${defaultChildNode}`);
} // TODO else display 404 page
} else {
const alert = {
message: 'Could not find the requested route',
link: pathUrl
};

component.set({ alert });
navigateTo('/');
//error 404
}
return;
}

if (!containsAllSegments(pathUrl, pathData.navigationPath)) {
const matchedPath = await matchPath(pathUrl);

const alert = {
message: 'Could not map the exact target node for the requested route',
link: pathUrl
};

component.set({ alert });
navigateTo(matchedPath);
}

const previousCompData = component.get();
component.set({
hideNav,
Expand Down Expand Up @@ -392,7 +414,7 @@ export const matchPath = async path => {
@param windowElem object defaults to window
@param documentElem object defaults to document
*/
export const navigateTo = (route, windowElem = window) => {
export const navigateTo = async (route, windowElem = window) => {
if (LuigiConfig.getConfigValue('routing.useHashRouting')) {
windowElem.location.hash = route;
return;
Expand Down
23 changes: 23 additions & 0 deletions core/src/utilities/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,29 @@ export const prependOrigin = path => {
return window.location.origin;
};

export const containsAllSegments = (sourceUrl, targetPathSegments) => {
if (!sourceUrl || !targetPathSegments || !targetPathSegments.length) {
console.error(
'Ooops, seems like the developers have misconfigured something'
);
return false;
}

const pathSegmentsUrl = targetPathSegments
.slice(1)
.map(x => x.pathSegment)
.join('/');
const mandatorySegmentsUrl = removeTrailingSlash(sourceUrl.split('?')[0]);
return pathSegmentsUrl === mandatorySegmentsUrl;
};

/**
* Prepend current url to redirect_uri, if it is a relative path
* @param {str} string from which any number of trailing slashes should be removed
* @returns string string without any trailing slash
*/
export const removeTrailingSlash = str => str.replace(/\/+$/, '');

/*
* Gets value of the given property on the given object.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const chai = require('chai');
const assert = chai.assert;
import { LuigiConfig } from '../src/services/config';
import { LuigiConfig } from '../../src/services/config';

describe('Config', () => {
describe('getConfigBooleanValue()', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
const navigation = require('../src/navigation/services/navigation');
const navigation = require('../../src/navigation/services/navigation');
const chai = require('chai');
const expect = chai.expect;
const assert = chai.assert;
const sinon = require('sinon');
import { LuigiConfig } from '../src/services/config';
import { LuigiConfig } from '../../src/services/config';

const sampleNavPromise = new Promise(function(resolve) {
const lazyLoadedChildrenNodesProviderFn = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ const expect = chai.expect;
const assert = chai.assert;
const sinon = require('sinon');
const MockBrowser = require('mock-browser').mocks.MockBrowser;
const routing = require('../src/services/routing');
import { deepMerge } from '../src/utilities/helpers.js';
const routing = require('../../src/services/routing');
import { deepMerge } from '../../src/utilities/helpers.js';
import { afterEach } from 'mocha';
import { LuigiConfig } from '../src/services/config';
import { LuigiConfig } from '../../src/services/config';

describe('Routing', () => {
let component;
Expand Down Expand Up @@ -742,7 +742,7 @@ describe('Routing', () => {
});

describe('defaultChildNodes', () => {
const routing = rewire('../src/services/routing');
const routing = rewire('../../src/services/routing');
const getDefaultChildNode = routing.__get__('getDefaultChildNode');
const getPathData = function() {
return {
Expand Down
Loading

0 comments on commit 6eb2406

Please sign in to comment.