Last active
June 20, 2024 21:28
-
-
Save webbower/668179303546eb53faba70951d30c68c to your computer and use it in GitHub Desktop.
Interviewing utilities
This file contains hidden or 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
// START: DENO STD colors | |
// https://jsr.io/@std/fmt/doc | |
/** | |
* Builds color code | |
* @param open | |
* @param close | |
*/ | |
const code = (open, close) => ({ | |
open: `\x1b[${open.join(';')}m`, | |
close: `\x1b[${close}m`, | |
regexp: new RegExp(`\\x1b\\[${close}m`, 'g'), | |
}); | |
const run = (str, code) => `${code.open}${str.replace(code.regexp, code.open)}${code.close}`; | |
const bold = str => run(str, code([1], 22)); | |
const yellow = str => run(str, code([33], 39)); | |
const red = str => run(str, code([31], 39)); | |
const green = str => run(str, code([32], 39)); | |
// END: DENO STD colors | |
// START: Deno STD Deep equality comparison | |
function isKeyedCollection(x) { | |
return [Symbol.iterator, 'size'].every(k => k in x); | |
} | |
function constructorsEqual(a, b) { | |
return ( | |
a.constructor === b.constructor || | |
(a.constructor === Object && !b.constructor) || | |
(!a.constructor && b.constructor === Object) | |
); | |
} | |
/** | |
* Deep equality comparison used in assertions | |
* | |
* @param {unknown} c The actual value | |
* @param {unknown} d The expected value | |
* @returns {boolean} `true` if the values are deeply equal, `false` otherwise | |
* | |
* @example Usage | |
* ```ts | |
* import { equal } from "@std/assert/equal"; | |
* | |
* equal({ foo: "bar" }, { foo: "bar" }); // Returns `true` | |
* equal({ foo: "bar" }, { foo: "baz" }); // Returns `false | |
* ``` | |
* | |
* @see https://jsr.io/@std/assert/0.226.0/equal.ts | |
*/ | |
function isEqual(c, d) { | |
const seen = new Map(); | |
return (function compare(a, b) { | |
// Have to render RegExp & Date for string comparison | |
// unless it's mistreated as object | |
if ( | |
a && | |
b && | |
((a instanceof RegExp && b instanceof RegExp) || (a instanceof URL && b instanceof URL)) | |
) { | |
return String(a) === String(b); | |
} | |
if (a instanceof Date && b instanceof Date) { | |
const aTime = a.getTime(); | |
const bTime = b.getTime(); | |
// Check for NaN equality manually since NaN is not | |
// equal to itself. | |
if (Number.isNaN(aTime) && Number.isNaN(bTime)) { | |
return true; | |
} | |
return aTime === bTime; | |
} | |
if (typeof a === 'number' && typeof b === 'number') { | |
return (Number.isNaN(a) && Number.isNaN(b)) || a === b; | |
} | |
if (Object.is(a, b)) { | |
return true; | |
} | |
if (a && typeof a === 'object' && b && typeof b === 'object') { | |
if (a && b && !constructorsEqual(a, b)) { | |
return false; | |
} | |
if (a instanceof WeakMap || b instanceof WeakMap) { | |
if (!(a instanceof WeakMap && b instanceof WeakMap)) return false; | |
throw new TypeError('cannot compare WeakMap instances'); | |
} | |
if (a instanceof WeakSet || b instanceof WeakSet) { | |
if (!(a instanceof WeakSet && b instanceof WeakSet)) return false; | |
throw new TypeError('cannot compare WeakSet instances'); | |
} | |
if (a instanceof WeakRef || b instanceof WeakRef) { | |
if (!(a instanceof WeakRef && b instanceof WeakRef)) return false; | |
return compare(a.deref(), b.deref()); | |
} | |
if (seen.get(a) === b) { | |
return true; | |
} | |
if (Object.keys(a).length !== Object.keys(b).length) { | |
return false; | |
} | |
seen.set(a, b); | |
if (isKeyedCollection(a) && isKeyedCollection(b)) { | |
if (a.size !== b.size) { | |
return false; | |
} | |
let unmatchedEntries = a.size; | |
for (const [aKey, aValue] of a.entries()) { | |
for (const [bKey, bValue] of b.entries()) { | |
/* Given that Map keys can be references, we need | |
* to ensure that they are also deeply equal */ | |
if ( | |
(aKey === aValue && bKey === bValue && compare(aKey, bKey)) || | |
(compare(aKey, bKey) && compare(aValue, bValue)) | |
) { | |
unmatchedEntries--; | |
break; | |
} | |
} | |
} | |
return unmatchedEntries === 0; | |
} | |
const merged = { ...a, ...b }; | |
for (const key of [ | |
...Object.getOwnPropertyNames(merged), | |
...Object.getOwnPropertySymbols(merged), | |
]) { | |
if (!compare(a && a[key], b && b[key])) { | |
return false; | |
} | |
if ((key in a && !(key in b)) || (key in b && !(key in a))) { | |
return false; | |
} | |
} | |
return true; | |
} | |
return false; | |
})(c, d); | |
} | |
// END: Deno STD Deep equality comparison | |
/** | |
* List of test outcomes | |
* @type {[('PASS' | 'FAIL'), string][]} | |
*/ | |
const outcomes = []; | |
/** | |
* Write the test outcomes to the console | |
*/ | |
const writeTestOutcomes = () => { | |
outcomes.forEach(([status, message], i) => { | |
if (status === 'PASS') { | |
console.info(green(`# ${i + 1} ok: ${message}`)); | |
} else { | |
console.error(red(`# ${i + 1} not ok: ${message}`)); | |
} | |
}); | |
}; | |
/** | |
* @template {T} | |
* @typedef {Object} AssertFnConfig The config object arg for the `assert()` function | |
* @prop {string} given A description of the context for the test(s) | |
* @prop {string} should A description of the expected outcome | |
* @prop {T} actual The result of the executed test case | |
* @prop {T} expected The expected result of the executed test case | |
*/ | |
const requiredKeys = ['given', 'should', 'actual', 'expected']; | |
/** | |
* Perform strict equals assertion for primitive values or deep equals assertion for object values | |
* | |
* @param {AssertFnConfig} config | |
* @returns {void} | |
*/ | |
const assert = config => { | |
const missingKeys = requiredKeys.filter(rk => !Object.hasOwn(config, rk)); | |
if (missingKeys.length) { | |
throw new TypeError( | |
`\`assert()\` first argument is missing the following keys: ${missingKeys.join(', ')}` | |
); | |
} | |
const { given, should, actual, expected } = config; | |
const message = `given ${given}; should ${should}`; | |
outcomes.push([isEqual(actual, expected) ? 'PASS' : 'FAIL', message]); | |
}; | |
/** | |
* Define a test block | |
* | |
* @param {string} title The title of the test block | |
* @param {(assert: AssertFnConfig) => void} testBlock The function wrapper in which to perform assertions | |
* @returns {void} | |
*/ | |
const describe = (title, testBlock) => { | |
console.info(bold(yellow(`### ${title}`))); | |
try { | |
testBlock(assert); | |
} catch (error) { | |
console.error(red(`There was an unexpected error: ${error.message}`)); | |
console.error(red(error.stack)); | |
} | |
}; | |
////// START TESTS | |
////// END TESTS | |
writeTestOutcomes(); |
This file contains hidden or 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
html { | |
box-sizing: border-box; | |
} | |
*, | |
*:before, | |
*:after { | |
box-sizing: inherit; | |
} | |
/* Button reset adapted from https://css-tricks.com/overriding-default-button-styles/ */ | |
a[class^="button"], | |
button { | |
border: 0; | |
font-family: system-ui, sans-serif; | |
font-size: 1rem; | |
line-height: 1.0; | |
white-space: nowrap; | |
text-decoration: none; | |
margin: 0; | |
cursor: pointer; | |
} | |
button { | |
background: transparent; | |
padding: 0; | |
} | |
a[class^="button"] { | |
display: inline-block; | |
} | |
.button-primary { | |
border-radius: 0.25rem; | |
background: #1E88E5; | |
color: white; | |
padding: 0.25rem 0.5rem; | |
} | |
/* | |
* Hide only visually, but have it available for screen readers: | |
* https://snook.ca/archives/html_and_css/hiding-content-for-accessibility | |
* | |
* 1. For long content, line feeds are not interpreted as spaces and small width | |
* causes content to wrap 1 word per line: | |
* https://medium.com/@jessebeach/beware-smushed-off-screen-accessible-text-5952a4c2cbfe | |
*/ | |
.vh { | |
border: 0; | |
clip: rect(0, 0, 0, 0); | |
height: 1px; | |
margin: -1px; | |
overflow: hidden; | |
padding: 0; | |
position: absolute; | |
white-space: nowrap; | |
width: 1px; | |
/* 1 */ | |
} | |
/* | |
* Extends the .vh class to allow the element | |
* to be focusable when navigated to via the keyboard: | |
* https://www.drupal.org/node/897638 | |
*/ | |
.vh.focusable:active, | |
.vh.focusable:focus { | |
clip: auto; | |
height: auto; | |
margin: 0; | |
overflow: visible; | |
position: static; | |
white-space: inherit; | |
width: auto; | |
} |
This file contains hidden or 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
// https://medium.com/javascript-scene/abstract-data-types-and-the-software-crisis-671ea7fc72e7 | |
// With modifications | |
const assert = ({ given, should, actual, expected }) => { | |
const stringify = (value) => | |
Array.isArray(value) | |
? `[${value.map(stringify).join(",")}]` | |
: `${JSON.stringify(value)}`; | |
const actualString = stringify(actual); | |
const expectedString = stringify(expected); | |
if (actualString === expectedString) { | |
console.log(`OK: | |
given: ${given} | |
should: ${should} | |
actual: ${actualString} | |
expected: ${expectedString} | |
`); | |
} else { | |
throw new Error(`NOT OK: | |
given ${given} | |
should ${should} | |
actual: ${actualString} | |
expected: ${expectedString} | |
`); | |
} | |
}; | |
const compose = (...fns) => x => fns.reduceRight((memo, fn) => fn(memo), x); | |
const pipe = (...fns) => x => fns.reduce((memo, fn) => fn(memo), x); | |
const curry = (f, arr = []) => (...args) => (a => a.length === f.length ? f(...a) : curry(f, a))([...arr, ...args]); | |
const identity = x => x; | |
const noop = () => {}; | |
const prop = curry((key, obj) => obj[key]); | |
const map = curry((fn, mappable) => mappable.map(fn)); | |
const filter = curry((pred, filterable) => filterable.filter(pred)); |
This file contains hidden or 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
const inc = (x) => x + 1; | |
const double = (x) => x * 2; | |
const isString = (x) => typeof x === "string"; | |
console.log("compose(inc, double)(2)", compose(inc, double)(2), "=== 5"); | |
console.log("pipe(inc, double)(2)", pipe(inc, double)(2), " === 6"); | |
console.log("identity('a')", identity("a"), "=== a"); | |
console.log("noop()", noop(1), "=== undefined"); | |
console.log("prop('a', {a: 'a'})", prop("a", { a: "a" }), "=== a"); | |
console.log("prop('a')({a: 'a'})", prop("a")({ a: "a" }), "=== a"); | |
console.log("map(inc, [1, 2, 3])", map(inc, [1, 2, 3]), "=== [2,3,4]"); | |
console.log("map(inc)([1, 2, 3])", map(inc)([1, 2, 3]), "=== [2,3,4]"); | |
console.log("filter(isString, [1, 'a', 2, 'b', 3])", filter(isString, [1, "a", 2, "b", 3]), "=== [a, b]"); | |
console.log("filter(isString)([1, 'a', 2, 'b', 3])", filter(isString)([1, "a", 2, "b", 3]), "=== [a, b]"); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment