Skip to content

Instantly share code, notes, and snippets.

@webbower
Last active March 4, 2024 01:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save webbower/668179303546eb53faba70951d30c68c to your computer and use it in GitHub Desktop.
Save webbower/668179303546eb53faba70951d30c68c to your computer and use it in GitHub Desktop.
Interviewing utilities
const { isEqual } = require('lodash');
const { bold, yellow, red, green } = require('chalk');
/**
* 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
*/
/**
* Perform strict equals assertion for primitive values or deep equals assertion for object values
*
* @param {AssertFnConfig} config
* @returns {void}
*/
const assert = ({ given, should, actual, expected }) => {
if (!(given && should && actual && expected)) {
throw new TypeError('`assert()` expects first argument to have all 4 keys: given, should, actual, expected');
}
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();
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;
}
// 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));
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