I have a map of routes with react router path as keys, eg:
const routes = [
{
page: "mySettings",
label: "pages.mySettings",
path: "/professionels/mes-reglages.html",
exact: true
},
{
page: "viewUser",
label: "pages.viewUser",
path: "/users/:id/view.html",
exact: true
}
];
I want from a location retrieved with useHistory().location.pathname, to match all the path that match the key in react-router terms, eg:
(pathname) => get(routesMap, "/professionels/mes-reglages.html") => should match routesMap.get('/professionels/mes-reglages.html')
(pathname) => get(routesMap, "/users/11/view.html") => should match routesMap.get('/users/:id/view.html')
and all react-router paths so this should work too:
(pathname) => get(routesMap, "/users/11/settings/10/items/24/view.html") => should match routesMap.get('/users/:userId/settings/:settingId/items/:id/view.html')
I have started here, any idea how I can do that with a regexp?
https://codesandbox.io/s/youthful-wing-fjgm1
Based on your comments i adjusted the code a bit and wrote a rapper function for your lookup.
The following rules you have to watch out for when creating the urls:
The last id always gets replaced by {id}
All other ids get replaced by url part to id without plural and "Id" attached ("/users/111" -> "/users/{userId}")
This would be the function:
const getRouteFromPath = (map, url) => {
if (url.match(/\/\d+\//g).length > 1) {
let allowedUrlPart = getAllowedIdQualifier(map);
let urlParts = url.match(/(?<=\/)\w+(?=\/\d+\/)/g);
urlParts.forEach(val => {
if (!allowedUrlPart.includes(val)) {
urlParts = urlParts.slice(urlParts.indexOf(val), 1);
}
});
urlParts.forEach((val, key, arr) => {
if (key === arr.length - 1) {
let regex = new RegExp("(?<=/" + val + "/)\\d+", "g");
let replacement = ":id";
url = url.replace(regex, replacement);
} else {
let regex = new RegExp("(?<=/" + val + "/)\\d+", "g");
let replacement = ":" + val.slice(0, -1) + "Id";
url = url.replace(regex, replacement);
}
});
return map.get(url);
} else {
url = url.replace(/\/\d+\//g, "/:id/");
return map.get(url);
}
};
const getAllowedIdQualifier = map => {
let allowedQualifiers = [];
map.forEach(val => {
let allowed = val.path.match(/(?<=\/)\w+(?=\/:)/g);
allowed.forEach(e => {
if (!allowedQualifiers.includes(e)) {
allowedQualifiers.push(e);
}
});
});
return allowedQualifiers;
};
export default getRouteFromPath;
As parameter you pass in the url to match as first parameter and the map of routes as the second paramter and call the function getRoute() instead of the direct map.get() call you where using before.
Here is the example with the urls adjusted to follow the rules, since you need some rules to be able to apply RegEx.
EDIT:
I adjusted the script, so that it reads the map first and determines the allowed paths which accept a id and then check the possible ids from an actual url against it.
https://codesandbox.io/s/kind-moon-9oyj9?fontsize=14&hidenavigation=1&theme=dark
Related
In our tool we create a url which quiet a few parameters with values. And I want Cypress to check the contents of this url.
The example url is:
http://someUrl.com/sap/?action=create&type=sw¬ifno=70432&repby=TRL&repres=ABC&geo=017&startloc=12345¬iftp=2021-06-15T08:06:42.379Z&scen=1.0&refno=1234567&awsrt=-&vrst=&sbst=&objtp=art&objtxt=&objfc=&tel=084123456&prio=4 Niet urgent&priost=&prioen=&wbi=&facts=&bgeb=AB-CD&bequi=
I have stored the url in 'href' variable but how i can now check all the attr and their values? I really don't have a clue.
I'd parse it into an object and then use .wrap(), .its(), and .should() commands:
const url = "http://someUrl.com/sap/?action=create&type=sw¬ifno=70432&repby=TRL&repres=ABC&geo=017&startloc=12345¬iftp=2021-06-15T08:06:42.379Z&scen=1.0&refno=1234567&awsrt=-&vrst=&sbst=&objtp=art&objtxt=&objfc=&tel=084123456&prio=4 Niet urgent&priost=&prioen=&wbi=&facts=&bgeb=AB-CD&bequi=";
const arr = url.split('/?')[1].split('&');
const paramObj = {};
arr.forEach(param => {
const [ key, value ] = param.split('=');
paramObj[key] = value;
});
cy
.wrap(paramObj)
.its('tel')
.should('eq', '084123456');
or if you want to assert more properties:
cy
.wrap(paramObj)
.then(obj => {
expect(obj.notifno).to.eq('70432');
expect(obj.tel).to.eq('084123456');
});
My colleague came with this solution, now the Cucumber line included:
Given('I expect the parameter {string} of the SAP-link on dossier {string} to equal {string}',(parameter:string, dossier:string, value:string) => {
cy.get('selector').each(ele => {
if(ele.text().trim().indexOf(dossier) == 0) {
cy.get('selector')
.parents('selector')
.find('selector').should('have.attr', 'href').then((sapUrl: JQuery<HTMLElement>) => {
cy.log(sapUrl.toString());
const queryParamString: string = sapUrl.toString().split('?')[1];
cy.log(queryParamString);
const queryParamArray: string[] = queryParamString.split('&');
var params: {} = {};
queryParamArray.forEach((keyValueString: string) => {
const currentParamArray: string[] = keyValueString.split('=');
params[currentParamArray[0]] = currentParamArray[1];
});
// Actual param check
expect(params[parameter]).to.equal(value);
});
}
});
});
Let's say I have an object that contains the values:
const pathParams = { schoolId :'12', classroomId: 'j3'}
and I have a path: school/:schoolId/classroom/:classroomId
I would like to extract: 'schoolId' and 'classroomId' from the path so that I can later replace them with their corresponding values from the pathParams object. Rather than iterating from the pathParam object keys, I want to do the other way around, check what keys the path needs and then check their values in the object.
I currently have this:
function addPathParams(path, paramsMap) {
const pathParamsRegex = /(:[a-zA-Z]+)/g;
const params = path.match(pathParamsRegex); // eg. school/:schoolId/classroom/:classroomId -> [':schoolId', ':classroomId']
console.log('--params--', params)
let url = path;
params.forEach((param) => {
const paramKey = param.substring(1); // remove ':'
url = addPathParam(url, param, paramsMap[paramKey]);
});
return url;
}
function addPathParam(path, param, value) {
return path.replace(`${param}`, value);
}
Does this look robust enough to you? Is there any other case I should be considering?
Here's a couple of tests I did:
Result:
The standard URL object can be used to calculate an absolute URL from a relative URL and a base URL as follows.
const base = 'http://example.com/'
const relative = '/foo/bar?quux=123'
const absolute = new URL(relative, base).href
console.assert(absolute === 'http://example.com/foo/bar?quux=123')
However, I could not figure out how to use the URL object to do the reverse.
const base = 'http://example.com/'
const absolute = 'http://example.com/foo/bar?quux=123'
const relative = '???'
console.assert(relative === '/foo/bar?quux=123')
Do the browser APIs provide a standardised way for constructing relative URLs or do I need to use a 3rd party solution?
Do the browser APIs provide a standardised way for constructing
relative URLs?
Yes, they do. You already used it, URL
Alternatively, you can create a temporary <a>-element and get the values from that. A freshly created <a>-element or URL both implement location, so you can extract location-properties:
// use <a href ...>
const absolute = `http://example.com/foo/bar?quux=123`;
const hrefTmp = document.createElement(`a`);
hrefTmp.href = absolute;
console.log(`Absolute from <a>: ${hrefTmp.href}`);
console.log(`Relative from <a>: ${hrefTmp.pathname}${hrefTmp.search}`);
// using URL
const url = new URL(absolute);
console.log(`Absolute from url: ${url.href}`);
console.log(`Relative from url: ${url.pathname}${url.search}`);
// using URL with a different base path
const baseOther = `http://somewhere.eu`;
const urlOther = new URL(`${url.pathname}${url.search}`, baseOther );
console.log(`Absolute from urlOther: ${urlOther.href}`);
console.log(`Relative from urlOther: ${urlOther.pathname}${urlOther.search}`);
.as-console-wrapper { top: 0; max-height: 100% !important; }
I ended up doing the following.
const base = 'http://example.com/'
const absolute = 'http://example.com/foo/bar?quux=123'
const relative = ((temp) => {
return absolute.startsWith(base) ? temp.pathname.concat(temp.search) : temp.href
})(new URL(absolute, base))
console.assert(relative === '/foo/bar?quux=123')
There's an npm module called relateurl that works well but its dependency on url (note lower-case) causes mild trouble in the latest Webpack and React. I published another called relativize-url that uses URL (shouty-caps), which is supported everywhere. It's pretty minimal so you can install it or just steal the code from index.js.
const components = [
{name: 'protocol', write: u => u.protocol },
{name: 'hostname', write: u => '//' + u.hostname },
{name: 'port', write: u => u.port === '' ? '' : (':' + u.port) },
{name: 'pathname', write: (u, frm, relativize) => {
if (!relativize) return u.pathname;
const f = frm.pathname.split('/').slice(1);
const t = u.pathname.split('/').slice(1);
const maxDepth = Math.max(f.length, t.length);
let start = 0;
while(start < maxDepth && f[start] === t[start]) ++start;
const rel = f.slice(start+1).map(c => '..')
.concat(t.slice(start)).join('/');
return rel.length <= u.pathname.length ? rel : u.pathname
}},
{name: 'search', write: u => u.search },
{name: 'hash', write: u => u.hash},
];
function relativize (rel, base, opts = {}) { // opts not yet used
const from = new URL(base);
const to = new URL(rel, from);
let ret = '';
for (let component of components) {
if (ret) { // force abs path if e.g. host was diffferent
ret += component.write(to, from, false);
} else if (from[component.name] !== to[component.name]) {
ret = component.write(to, from, true);
}
}
return ret;
}
The pathname handler has extra code in it to give you nice minimal relative paths. Give it some exercise:
const base = 'http://a.example/b/e/f?g=h#i'
const target = 'http://a.example/b/c/d?j=k#l'
console.log(relativize(target, base))
// got '../c/d'; let's check it:
console.log(new URL('../c/d', base).href === target)
// true
console.log(relativize('http://a.example/b?a=b','http://a.example/b?c=d'))
// ?a=b
console.log(relativize('http://a.example/b#asdf', 'http://a.example/b'))
// #asdf
console.log(relativize('http://a.example/b', 'http://c.example/d'))
// //a.example/b
Please report bugs in https://github.com/ericprud/relativize-url/issues .
I would like to extract a specific link (zip file) between two tags as below:
<script>
AAA:'https://url/*.zip'
BBB:'https://url/*.avi'
CCC:'https://url/*.mp4'
</script>
I've tried with that but it doesn't work:
var links = Array.from(document.querySelectorAll("script[AAA:'https://url/*.zip']"); alert(links);
Normally I use querySelectorAll and that works with all other tags, but not here.. So i need here to extract https://url/*.zip link and show it with an standard alert() box, Thanks!
Extract the values from the script element's text just like you would do it from a string:
function extractor(names) {
const r = new RegExp(names.map(n => `\\b(${n}):\\s*'(.*?)'`).join('|'), 'g');
for (const script of document.getElementsByTagName('script')) {
const text = script.textContent;
let match = r.exec(text);
if (match) {
const vars = {};
do {
const [/*source*/, name, value] = match;
vars[name] = value;
match = r.exec(text);
} while (match);
return vars;
}
}
}
When using in an extension popup to extract from the web page we need a helper function that runs the code as a content script:
function extractPageScriptVars(names) {
return new Promise(resolve => {
chrome.tabs.executeScript({
code: '(() => (' + extractor + ')(' + JSON.stringify(names) + '))()',
}, ([vars]) => resolve(vars));
});
}
Usage:
(async () => {
const vars = await extractPageScriptVars(['AAA', 'BBB']);
alert(JSON.stringify(vars, null, ' '));
})();
In case the script contains a valid JSON/JS object you can find its start/end indexes and extract the entire declaration string, then parse it with JSON.parse that produces an object/array, which you can use directly.
Trying to filter only the sub-routes based on index path.
In the following example, everything works fine but we don't expect any routes belonging to /ab in the output.
function getRoutes(path: string, routerData) {
return Object.keys(routerData).filter(routePath => {
console.log('routePath', routePath.indexOf(path));
return routePath.indexOf(path) === 0 && routePath !== path;
});
}
routerData = {
['/a']: {},
['/a/1]: {},
['/a/2]: {},
['/b/1]: {},
['/b/2]: {},
['/ab/2/1]: {},
}
Expecting result
const result = ['/a/1', '/a/2'];
expect(getRoutes('/a', routerData).map(item => item.path)).toEqual(result);
Expected value to equal:
["/a/1", "/a/2"]
Received:
["/a/1", "/a/2", "/ab/2/1"]
Your function matches all routes that start with /a and are not limited to /a. So you get /a1 too.
To avoid that, you need to check all routes that start with /a/ instead:
function getRoutes(path: string, routerData) {
if (path[path.length-1] !== '/')
path += '/'; // Add a '/' to exclude incomplete paths
return Object.keys(routerData).filter(routePath => {
console.log('routePath', routePath.indexOf(path));
return routePath.indexOf(path) === 0; // This becomes useless: && routePath !== path;
});
}