Skip to content

Instantly share code, notes, and snippets.

@donavon
Created April 1, 2020 12:53
Show Gist options
  • Save donavon/450e1f0978bed97fba2291ebe3bd4a64 to your computer and use it in GitHub Desktop.
Save donavon/450e1f0978bed97fba2291ebe3bd4a64 to your computer and use it in GitHub Desktop.
buildUrl - constructs a URL from an href and a search query object, e.g. {foo: 'bar'}
/*
buildUrl constructs a URL given on a URL and a search query object.
Search query keys are sorted alphabetically. The URL may contain an
existing search query, in which case it will be sorted.
Example:
```js
const url = buildUrl('http://example.com/?x=y&a=b', {
foo: 'bar'
});
```
This creates the URL string http://example.com/?a=b&foo=bar&x=y
*/
const comparePairs = ([a], [b]) => {
if (a < b) {
return -1;
} else if (a > b) {
return 1;
}
return 0;
};
export const buildUrl = (href, queryObj = {}) => {
const url = new URL(href);
const clonedObj = url.search
? { ...Object.fromEntries(url.searchParams.entries()), ...queryObj }
: queryObj;
url.search = '';
Object.entries(clonedObj)
.sort(comparePairs)
.forEach(pair => url.searchParams.set(...pair));
return url.href;
};
// copped this off of the interwebs. it seems fine for now
export const shuffle = array => {
let currentIndex = array.length;
while (0 !== currentIndex) {
const randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
const temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
};
import { buildUrl } from '../utils';
import './polyfills/ObjectFromEntries'; // If running on node 10 or <
const domain = 'http://example.com/';
describe('buildUrl', () => {
it('can accept a path only', () => {
const url = buildUrl(domain);
expect(url).toBe(domain);
});
it('accepts both a path and a query object', () => {
const url = buildUrl(domain, { foo: 'foo', bar: 'bar' });
expect(url).toBe(`${domain}?bar=bar&foo=foo`);
});
it('accepts a path with an existing query string and a query object', () => {
const url = buildUrl(`${domain}?x=y&a=b`, { foo: 'foo', bar: 'bar' });
expect(url).toBe(`${domain}?a=b&bar=bar&foo=foo&x=y`);
});
it('accepts a path with an existing query string and a query object', () => {
const url = buildUrl(`${domain}?a=b`, { foo: 'foo', bar: 'bar' });
expect(url).toBe(`${domain}?a=b&bar=bar&foo=foo`);
});
it('accepts a path with an existing query string and a query object', () => {
const url = buildUrl(`${domain}?a=b`, { foo: 'foo', a: 'c', bar: 'bar' });
expect(url).toBe(`${domain}?a=c&bar=bar&foo=foo`);
});
it('url encodes the query string', () => {
const url = buildUrl(domain, { foo: 'f=o&o', bar: 'b?a%r' });
expect(url).toBe(`${domain}?bar=b%3Fa%25r&foo=f%3Do%26o`);
});
});
// https://github.com/tc39/proposal-object-from-entries/blob/master/polyfill.js
if (!Object.fromEntries) {
Object.fromEntries = function ObjectFromEntries(iter) {
const obj = {};
for (const pair of iter) {
if (Object(pair) !== pair) {
throw new TypeError('iterable for fromEntries should yield objects');
}
// Consistency with Map: contract is that entry has "0" and "1" keys, not
// that it is an array or iterable.
const { '0': key, '1': val } = pair;
Object.defineProperty(obj, key, {
configurable: true,
enumerable: true,
writable: true,
value: val,
});
}
return obj;
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment