diff --git a/core/examples/luigi-sample-angular/e2e/tests/luigi-client-features.spec.js b/core/examples/luigi-sample-angular/e2e/tests/luigi-client-features.spec.js
index 2a0fc95e42..0fdbb9df3e 100644
--- a/core/examples/luigi-sample-angular/e2e/tests/luigi-client-features.spec.js
+++ b/core/examples/luigi-sample-angular/e2e/tests/luigi-client-features.spec.js
@@ -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);
diff --git a/core/examples/luigi-sample-angular/src/app/project/project.component.html b/core/examples/luigi-sample-angular/src/app/project/project.component.html
index 78929db696..619bb6ce76 100644
--- a/core/examples/luigi-sample-angular/src/app/project/project.component.html
+++ b/core/examples/luigi-sample-angular/src/app/project/project.component.html
@@ -1,76 +1,99 @@
-
-
-
LuigiClient uxManager methods:
-
-
-
-
-
-
+
+
+
LuigiClient uxManager methods:
+
+
+
+
+
+
-
-
-
Context received from linkManager().goBack():
-
{{ preservedViewCallbackContext | json }}
-
-
+
+
+
Context received from linkManager().goBack():
+
{{ preservedViewCallbackContext | json }}
+
+
-
-
LuigiClient linkManager methods:
-
-
+
+
LuigiClient linkManager methods:
+
+
-
+
+
+
LuigiClient - wrong paths in linkManager.navigate():
+
+
+
+
+
\ No newline at end of file
diff --git a/core/package.json b/core/package.json
index fd2ec32ef0..e72da3b443 100644
--- a/core/package.json
+++ b/core/package.json
@@ -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"
},
diff --git a/core/src/App.html b/core/src/App.html
index a51f59f209..dd6813c87e 100644
--- a/core/src/App.html
+++ b/core/src/App.html
@@ -1,4 +1,10 @@
+ {#if alert && alert.message}
+
+
+ {alert.message} {alert.link}
+
+ {/if}
@@ -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 = {}) => {
@@ -296,6 +304,7 @@
diff --git a/core/src/services/routing.js b/core/src/services/routing.js
index 101701935a..50234b8ccb 100644
--- a/core/src/services/routing.js
+++ b/core/src/services/routing.js
@@ -3,6 +3,7 @@ import { LuigiConfig } from './config';
import {
getPathWithoutHash,
getUrlWithoutHash,
+ containsAllSegments,
isIE,
getConfigValueFromObject
} from '../utilities/helpers';
@@ -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,
@@ -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;
diff --git a/core/src/utilities/helpers.js b/core/src/utilities/helpers.js
index eeb4f44b33..28fb769d33 100644
--- a/core/src/utilities/helpers.js
+++ b/core/src/utilities/helpers.js
@@ -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.
*/
diff --git a/core/test/config.spec.js b/core/test/services/config.spec.js
similarity index 95%
rename from core/test/config.spec.js
rename to core/test/services/config.spec.js
index d33a807327..1e8e46f587 100644
--- a/core/test/config.spec.js
+++ b/core/test/services/config.spec.js
@@ -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()', () => {
diff --git a/core/test/navigation.spec.js b/core/test/services/navigation.spec.js
similarity index 98%
rename from core/test/navigation.spec.js
rename to core/test/services/navigation.spec.js
index cde45d9dde..96d92c99fd 100644
--- a/core/test/navigation.spec.js
+++ b/core/test/services/navigation.spec.js
@@ -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 = () => {
diff --git a/core/test/routing.spec.js b/core/test/services/routing.spec.js
similarity index 98%
rename from core/test/routing.spec.js
rename to core/test/services/routing.spec.js
index 7c3c9927e4..ebee0aef0a 100644
--- a/core/test/routing.spec.js
+++ b/core/test/services/routing.spec.js
@@ -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;
@@ -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 {
diff --git a/core/test/utilities/helpers.spec.js b/core/test/utilities/helpers.spec.js
new file mode 100644
index 0000000000..2eaff926e9
--- /dev/null
+++ b/core/test/utilities/helpers.spec.js
@@ -0,0 +1,106 @@
+const chai = require('chai');
+const assert = chai.assert;
+import { containsAllSegments } from '../../src/utilities/helpers';
+
+describe('Helpers()', () => {
+ describe('#containsAllSegments()', () => {
+ it('should return true when proper data provided', async () => {
+ const sourceUrl = 'mas/ko/pa/tol/';
+ const targetPathSegments = [
+ {
+ //doesn't matter, it's omitted anyway
+ },
+ {
+ pathSegment: 'mas'
+ },
+ {
+ pathSegment: 'ko'
+ },
+ {
+ pathSegment: 'pa'
+ },
+ {
+ pathSegment: 'tol'
+ }
+ ];
+ assert.equal(containsAllSegments(sourceUrl, targetPathSegments), true);
+ });
+
+ it('should return false when wrong data provided', async () => {
+ const differentSourceUrl = 'mas/ko/pa/tol';
+ const similarSourceUrl = 'luigi/is/os/awesome';
+ const targetPathSegments = [
+ {
+ //doesn't matter, it's omitted anyway
+ },
+ {
+ pathSegment: 'luigi'
+ },
+ {
+ pathSegment: 'is'
+ },
+ {
+ pathSegment: 'so'
+ },
+ {
+ pathSegment: 'awesome'
+ }
+ ];
+ assert.equal(
+ containsAllSegments(differentSourceUrl, targetPathSegments),
+ false
+ );
+ assert.equal(
+ containsAllSegments(similarSourceUrl, targetPathSegments),
+ false
+ );
+ });
+
+ it("should return false when pathSegments numbers don't match", async () => {
+ const tooShortSourceUrl = 'one/two';
+ const tooLongSourceUrl = 'three/four/five/six';
+ const targetPathSegments = [
+ {
+ //doesn't matter, it's omitted anyway
+ },
+ {
+ pathSegment: 'one'
+ },
+ {
+ pathSegment: 'two'
+ },
+ {
+ pathSegment: 'three'
+ }
+ ];
+ assert.equal(
+ containsAllSegments(tooShortSourceUrl, targetPathSegments),
+ false
+ );
+ assert.equal(
+ containsAllSegments(tooLongSourceUrl, targetPathSegments),
+ false
+ );
+ });
+
+ it('should ignore GET parameters', async () => {
+ const sourceUrl = 'one/two/three?masko=patol&four=five';
+
+ const targetPathSegments = [
+ {
+ //doesn't matter, it's omitted anyway
+ },
+ {
+ pathSegment: 'one'
+ },
+ {
+ pathSegment: 'two'
+ },
+ {
+ pathSegment: 'three'
+ }
+ ];
+ assert.equal(containsAllSegments(sourceUrl, targetPathSegments), true);
+ });
+ });
+});