Skip to content

Instantly share code, notes, and snippets.

@remolueoend
Last active February 10, 2021 11:48
Show Gist options
  • Save remolueoend/c13560f2bfc33ef38d2fcd8dbe04008c to your computer and use it in GitHub Desktop.
Save remolueoend/c13560f2bfc33ef38d2fcd8dbe04008c to your computer and use it in GitHub Desktop.
// returns the final URL a request was redirected to
const getFinalUrl = resp => {
const redirs = resp.allRequestResponses
return redirs[redirs.length - 1]["Request URL"]
}
// Assumption: If we have been authorized before, Auth0 redirected us directly to the app.
// Therefore, the final URL does not contain the Auth0 domain:
const isAuthenticated = resp => {
return !getFinalUrl(resp).includes(Cypress.env("AUTH_DOMAIN"))
}
Cypress.Commands.add("login", () => {
Cypress.log({
name: "loginViaAuth0",
})
// we try to access the app and validate afterwards if we have been redirected to the login form:
cy.request({
method: "GET",
url: "/",
}).then(resp => {
// we may have already been authorized. If so, exit here:
if (isAuthenticated(resp)) return
// get the state from the redirected URL query
const finalUrl = getFinalUrl(resp)
const state = queryString.parseUrl(finalUrl).query.state
// send the login form data including the state
return cy.request({
method: "POST",
url: queryString.stringifyUrl({
url: `${Cypress.env("AUTH_DOMAIN")}/u/login`,
query: { state },
}),
form: true,
body: {
username: Cypress.env("AUTH_USERNAME"),
password: Cypress.env("AUTH_PASSWORD"),
state,
},
})
// if everything went well, we've got redirected to the app and are authenticated
// via session cookie
})
})
type Immutable<T> = {
readonly [K in keyof T]: T[K] extends Array<infer U>
? ReadonlyArray<Immutable<U>>
: T[K] extends {}
? Immutable<T[K]>
: T[K]
}
// Usage:
type Obj = {
field1: number
field2: string
field3: {
field31: boolean
}
field4: Array<{field41: number}>
}
type ImmutableObj = Immutable<Obj>
const test = (obj: ImmutableObj) => {
obj.field1 = 1 // throws
obj.field3.field31 = true // throws on nested properties
obj.field4[0] = {field41: 5} // throws on array mutation
const elem = obj.field4[0]
elem.field41 = 1 // throws on nested array items mutation
}
// Try it out at:
// https://www.typescriptlang.org/play/#src=type%20Immutable%3CT%3E%20%3D%20%7B%0D%0A%20%20%20%20readonly%20%5BK%20in%20keyof%20T%5D%3A%20T%5BK%5D%20extends%20Array%3Cinfer%20U%3E%0D%0A%20%20%20%20%3F%20ReadonlyArray%3CImmutable%3CU%3E%3E%0D%0A%20%20%20%20%3A%20T%5BK%5D%20extends%20%7B%7D%0D%0A%20%20%20%20%3F%20Immutable%3CT%5BK%5D%3E%0D%0A%20%20%20%20%3A%20T%5BK%5D%0D%0A%7D%0D%0A%0D%0Atype%20Obj%20%3D%20%7B%0D%0A%20%20%20%20field1%3A%20number%0D%0A%20%20%20%20field2%3A%20string%0D%0A%20%20%20%20field3%3A%20%7B%0D%0A%20%20%20%20%20%20%20%20field31%3A%20boolean%0D%0A%20%20%20%20%7D%0D%0A%20%20%20%20field4%3A%20Array%3C%7Bfield41%3A%20number%7D%3E%0D%0A%7D%0D%0A%0D%0Atype%20ImmutableObj%20%3D%20Immutable%3CObj%3E%0D%0A%0D%0Aconst%20test%20%3D%20(obj%3A%20ImmutableObj)%20%3D%3E%20%7B%0D%0A%20%20%20%20obj.field1%20%3D%201%0D%0A%20%20%20%20obj.field3.field31%20%3D%20true%0D%0A%20%20%20%20obj.field4%5B0%5D%20%3D%20%7Bfield41%3A%205%7D%0D%0A%20%20%20%20const%20elem%20%3D%20obj.field4%5B0%5D%0D%0A%20%20%20%20elem.field41%20%3D%201%0D%0A%7D
class PointerList extends Array {
constructor(...params) {
super(...params);
}
remove (condition) {
let i = this.length;
while (i--) {
if (condition(this[i], i)) {
this.splice(i, 1);
}
}
}
}
class Index {
constructor (keyAccessor) {
this.key = keyAccessor;
this.pointers = new PointerList();
}
filter (filter, key) {
return this.pointers[filter](i => i.key === key);
}
getAll (key) {
const res = this.filter('filter', key);
return res && res.map(i => i.pointer) || [];
}
get (key) {
const res = this.filter('find', key);
return res && res.pointer || null;
}
add (cacheItem) {
const key = this.key(cacheItem);
this.pointers.push({
key: this.key(cacheItem),
pointer: cacheItem
});
}
remove (cacheItem) {
const key = this.key(cacheItem);
this.pointers.remove(p => p.key === key);
}
}
class Cache {
constructor () {
this.indices = {};
}
registerIndex (name, keyAccessor) {
return this.indices[name] = new Index(keyAccessor);
}
add (cacheItem) {
this.getIndices()
.forEach(index => index.add(cacheItem));
}
remove (cacheItem) {
this.getIndices()
.forEach(index => index.remove(cacheItem));
}
getIndex (indexName) {
return this.indices[indexName];
}
getIndices () {
return Object.keys(this.indices)
.map(indexName => this.getIndex(indexName));
}
getAll (indexName, key) {
return this.getIndex(indexName).getAll(key);
}
get (indexName, key) {
return this.getIndex(indexName).get(key);
}
static create () {
return new Proxy(new Cache (), {
get: (cache, prop) => {
if (prop in cache) {
return cache[prop];
}
else {
return cache.getIndex(prop);
}
},
set: (cache, prop, val) => {
if (prop in cache) {
return cache[prop] = val;
}
else {
return cache.registerIndex(prop, val);
}
}
});
}
}
export default Cache;
const cache = Cache.create();
cache.country = p => p.country;
cache.first = p => p.first;
cache.last = p => p.last;
cache.add({ first: 'remo', last: 'zumsteg', country: 'CH' });
cache.add({ first: 'brian', last: 'mcalister', country: 'CH' });
cache.add({ first: 'remo', last: 'zumsteg', country: 'USA' });
cache.add({ first: 'hans', last: 'zumsteg', country: 'DE' });
console.log(cache.country.getAll('CH'));
// see: https://www.youtube.com/watch?v=3VQ382QG-y4
// combinators
let I = x => x
let K = x => y => x
let KI = x => y => y
let C = f => x => y => f(y)(x)
let B = f => g => x => f(g(x))
let B1 = f => g => a => b => f(g(a)(b))
let Th = a => f => f(a)
// boolean true/false
let t = x => y => x
let f = x => y => y
let n0 = f // alias for numeral 0
// boolean operators
let not = p => p(f)(t)
let and = p => q => p(q)(p)
let or = p => q => p(p)(q)
or(f)(not(f)) // t
// pair data structure and helpers
let pair = x => y => f => f(x)(y)
let fst = p => p(K)
let snd = p => p(KI)
let phi = p => pair(snd(p))(succ(snd(p))) // (m, n) -> (n, n+1)
// numeric helpers
let succ = n => f => B(f)(n(f))
let pred = n => B(fst)(n(phi))(pair(n0)(n0))
let is_zero = n => n(K(f))(t)
is_zero(n0) // t
// some numerals:
let n1 = I
let n2 = succ(n1)
let n3 = succ(n2)
// numeric JS helpers
const num = n => n(x => x + 1)(0)
const num_pair = a => b => [num(a), num(b)]
// numeric operators
let add = m => n => n(succ)(m)
let mul = B // m -> n -> B(m)(n)
let pow = Th // b -> e -> e(b)
let sub = m => n => n(pred)(m)
add(n2)(n3) // 5
mul(n2)(n3) // 6
pow(n2)(n3) // 8
sub(n3)(n2) // 1
// comparators
let leq = m => n => is_zero(sub(m)(n))
let gt = B1(not)(leq)
let eq = m => n => and(leq(m)(n))(leq(n)(m))
eq(n2)(n2) // t
gt(n2)(n3) // f
/**
* Binary tree described as nested triples:
*/
type TreeNode<T> = [] | [T, TreeNode<T>, TreeNode<T>]
/**
* helpers
*/
const iff = <R>(c1: boolean, t1: () => R, d: () => R) => c1 ? t1() : d()
const iff2 = <R>(c1: boolean, t1: () => R, c2: boolean, t2: () => R, d: () => R) => c1 ? t1() : c2 ? t2() : d()
/**
* some common tree operations
*/
const insert = <T>([v, left, right]: TreeNode<T>, value: T): TreeNode<T> => iff(
v == undefined, () => [value, [], []], // tree empty, return new root
() => iff2(
v > value, () => [v, insert(left, value), right], // insert left
v < value, () => [v, left, insert(right, value)], // insert right
() => [v, left, right] // value already present, return given node
),
)
const contains = <T>(value: T, [v, left, right]: TreeNode<T>): boolean => iff2(
value == v, () => true, // found it!
value < v, () => contains(value, left), // search left
() => contains(value, right) // search right
)
let tree: TreeNode<number> = [5, [], []]
tree = insert(tree, 6)
tree = insert(tree, 4)
tree = insert(tree, 3)
console.log(tree, contains(3, tree))
/**
* describes a non empty list as a recursive tuple
*/
type NonEmptyList<T> = [T, List<T>]
/**
* Describes a general list as a recursive tuple
*/
type List<T> = [] | NonEmptyList<T>
/**
* helper functions for logging, stringifying, etc
*/
const log = <T>(message: string) => (console.log(`LOG:${message}`) as unknown) as T
const remove_last = (i: number, str: string) => str.substring(0, str.length - i)
const to_str = <T>(del: string, list: List<T>) =>
remove_last(del.length, reduce((acc, i) => `${i}${del}${acc}`, "")(list))
/**
* accepts two handlers for matching empty and non-empty lists
*/
const match = <T, R>(e: (l: []) => R, f: (l: NonEmptyList<T>) => R) => (list: List<T>) => iff(notEmpty(list),
() => f(list as NonEmptyList<T>),
() => e(list as []),
)
/**
* helpers returning either the head or tail of a list
*/
const car = <T>([h]: List<T>) => h
const cdr = <T>([_, t]: List<T>) => t
/**
* returns true iff the given list is not empty
*/
const notEmpty = <T>(list: List<T>): list is NonEmptyList<T> => list[0] != undefined;
/**
* appends the given value to a list
*/
const append = <T>(value: T, list: List<T>): List<T> => [value, list]
/**
* maps over the given list using the provided mapping function and returns a new list
* containing the mapped values in the same order
*/
const map = <T, U>(fn: (e: T) => U): (l: List<T>) => List<U> => match<T, List<U>>(
([]) => [],
([h, t]) => [fn(h), map(fn)(t)]
)
/**
* Calls the provided reducer function for each element element in a list and returns the final value
* returned by the reducer
*/
const reduce = <T, U>(fn: (acc: U, e: T) => U, def_value: U): (l: List<T>) => U => match<T, U>(
([]) => def_value,
([h, t]) => reduce(fn, fn(def_value, h))(t)
)
// some testing:
let list: List<number> = [];
list = append(1, list);
list = append(2, list);
list = append(3, list);
const double = map((n: number) => n * 2)
const doubleList = double(list)
console.log(to_str(", ", doubleList))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment