Last active
February 10, 2021 11:48
-
-
Save remolueoend/c13560f2bfc33ef38d2fcd8dbe04008c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 | |
}) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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')); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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