Created
March 23, 2018 11:48
-
-
Save joshycube/83a9e62e884997c474cf40ba28fb3306 to your computer and use it in GitHub Desktop.
Coding Challenge JS Developer Code Challenge // source https://jsbin.com/pafafek
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
<html> | |
<head> | |
<meta name="description" content="JS Developer Code Challenge" /><html> | |
<head> | |
<script src="https://cdn.rawgit.com/lodash/lodash/4.5.1/dist/lodash.min.js"></script> | |
<script src="https://cdn.rawgit.com/lodash/lodash/4.5.1/dist/lodash.fp.min.js"></script> | |
<title>Coding Challenge</title> | |
</head> | |
<body> | |
<h1>Terms of the Exercise</h1> | |
<ul> | |
<li>Try and stay away from libraries if possible</li> | |
<li>You can take as long as you like to complete the exercise, but for an indicative timescale we expect a senior developer can accomplish this in an hour.</li> | |
<li>You may use online resources to assist you with specific techniques, syntax etc. but please do not just copy code.</li> | |
<li>Please don't share this exercise with any third party</li> | |
</ul> | |
<h1>The Challenge</h1> | |
<p> | |
<ul> | |
<li>Fill in the "assertEquals" function such that it will correctly compare the passed "expected" vs "actual" parameters.</li> | |
<li>You may add more functions.</li> | |
<li>Credit will be given for approach, correctly identifying "failed" assertEquals, "clean" code and coding style.</li> | |
</ul> | |
<h1>Expected Result</h1> | |
The following tests should "fail": <strong>02, 03, 04, 07, 08 and 09</strong> - and the failures should be reported using the provided mechanism.<br/> | |
Ideally the failure messages should report further information: | |
<ul class="expected"> | |
<li>Test 02: Expected "abcdef" found "abc"</li> | |
<li>Test 03: Expected type Array but found Object</li> | |
<li>Test 04: Expected array length 2 but found 3</li> | |
<li>Test 07: Expected propB.propA[1].propB "b" but found "c"</li> | |
<li>Test 08: Expected propB.propC but was not found</li> | |
<li>Test 09: Expected type null but found type Object</li> | |
</ul> | |
<h1>Output</h1> | |
<ul class="output" id="messages"></ul> | |
<script src="https://cdn.jsdelivr.net/lodash/4/lodash.min.js"></script> | |
<script id="jsbin-javascript"> | |
/** | |
* @param {String} message The message that will be thrown | |
*/ | |
"use strict"; | |
function AssertFailEx(message) { | |
this.message = message; | |
this.name = "AssertFailEx"; | |
} | |
/** | |
* @param {*} subject The item needs to be type checked | |
*/ | |
function getType(subject) { | |
return Object.prototype.toString.call(subject).slice(8, -1); | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function deepEqualObject(expected, actual) { | |
var path = []; | |
var expectedVal = []; | |
function changes(expected, actual) { | |
return _.transform(expected, function (result, value, key) { | |
if (!_.isEqual(value, actual[key])) { | |
path.push(key); | |
result[key] = _.isObject(value) && _.isObject(actual[key]) ? changes(value, actual[key]) : expectedVal.push(value) && value; | |
} | |
}); | |
} | |
changes(expected, actual); | |
return { path: path, expectedVal: expectedVal }; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function shallowEqualArray(expected, actual) { | |
for (var _item in expected) { | |
if (!actual[_item]) { | |
return _item; | |
} | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function checkArray(expected, actual) { | |
var expectedType = getType(expected); | |
var expectedLength = expected.length; | |
var actualType = getType(actual); | |
var actualLength = actual.length; | |
if (expectedType !== actualType) { | |
return "Expected type " + expectedType + " but found " + actualType; | |
} else if (expectedLength !== actualLength) { | |
return "Expected length " + expectedLength + " but found " + actualLength; | |
} else { | |
var shallow = shallowEqualArray(expected, actual); | |
return shallow === true ? shallow : "Expected item in array " + item + " but found undefined"; | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function checkObject(expected, actual) { | |
var expectedType = getType(expected); | |
var actualType = getType(actual); | |
if (expectedType !== actualType) { | |
return "Expected type " + expectedType + " but found type " + actualType; | |
} else { | |
var deep = deepEqualObject(expected, actual); | |
return deep.path.length === 0 ? true : "Expected " + deep.path.join('.') + " \"" + deep.expectedVal + "\"\n but found \"" + _.get(actual, deep.path) + "\""; | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function checkOtherTypes(expected, actual) { | |
var expectedType = getType(expected); | |
var actualType = getType(actual); | |
if (expectedType !== actualType) { | |
return "Expected type " + expectedType + " but found type " + actualType; | |
} else if (expected !== actual) { | |
return "Expected \"" + expected + "\" but found \"" + actual + "\""; | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
* @param {String} type The returned type Array, Null, Object, String, Undefined, Number | |
*/ | |
function assertTypes(expected, actual, type) { | |
switch (type) { | |
case "Object": | |
return checkObject(expected, actual); | |
break; | |
case "Array": | |
return checkArray(expected, actual); | |
break; | |
default: | |
return checkOtherTypes(expected, actual); | |
} | |
} | |
/** | |
* Asserts "expected" versus "actual", | |
* 'failing' the assertion (via Error) if a difference is found. | |
* | |
* @param {String} message The comparison message passed by the user | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function assertEquals(expected, actual) { | |
var type = getType(expected); | |
var asserted = assertTypes(expected, actual, type); | |
if (asserted !== true) { | |
throw new AssertFailEx(asserted); | |
} | |
} | |
/* -- Test running code: --- */ | |
/** | |
* Runs a "assertEquals" test. | |
* | |
* @param {String} message The initial message to pass | |
* @param {Array} assertionFailures List of messages that will be displayed on the UI for evaluation | |
* @param {*} expected Expected item | |
* @param {*} actual The actual item | |
*/ | |
function runTest(message, assertionFailures, expected, actual) { | |
try { | |
assertEquals(expected, actual); | |
} catch (failure) { | |
assertionFailures.push(message + " " + failure.message); | |
} | |
} | |
function runAll() { | |
var complexObject1 = { | |
propA: 1, | |
propB: { | |
propA: [1, { propA: "a", propB: "b" }, 3], | |
propB: 1, | |
propC: 2 | |
} | |
}; | |
var complexObject1Copy = { | |
propA: 1, | |
propB: { | |
propA: [1, { propA: "a", propB: "b" }, 3], | |
propB: 1, | |
propC: 2 | |
} | |
}; | |
var complexObject2 = { | |
propA: 1, | |
propB: { | |
propB: 1, | |
propA: [1, { propA: "a", propB: "c" }, 3], | |
propC: 2 | |
} | |
}; | |
var complexObject3 = { | |
propA: 1, | |
propB: { | |
propA: [1, { propA: "a", propB: "b" }, 3], | |
propB: 1 | |
} | |
}; | |
// Run the tests | |
var assertionFailures = []; | |
runTest("Test 01: ", assertionFailures, "abc", "abc"); | |
runTest("Test 02: ", assertionFailures, "abcdef", "abc"); | |
runTest("Test 03: ", assertionFailures, ["a"], { 0: "a" }); | |
runTest("Test 04: ", assertionFailures, ["a", "b"], ["a", "b", "c"]); | |
runTest("Test 05: ", assertionFailures, ["a", "b", "c"], ["a", "b", "c"]); | |
runTest("Test 06: ", assertionFailures, complexObject1, complexObject1Copy); | |
runTest("Test 07: ", assertionFailures, complexObject1, complexObject2); | |
runTest("Test 08: ", assertionFailures, complexObject1, complexObject3); | |
runTest("Test 09: ", assertionFailures, null, {}); | |
// Output the results | |
var messagesEl = document.getElementById("messages"); | |
var newListEl; | |
var i, ii; | |
for (i = 0, ii = assertionFailures.length; i < ii; i++) { | |
newListEl = document.createElement("li"); | |
newListEl.innerHTML = assertionFailures[i]; | |
messagesEl.appendChild(newListEl); | |
} | |
} | |
runAll(); | |
</script> | |
<script id="jsbin-source-javascript" type="text/javascript">/** | |
* @param {String} message The message that will be thrown | |
*/ | |
function AssertFailEx(message) { | |
this.message = message; | |
this.name = "AssertFailEx"; | |
} | |
/** | |
* @param {*} subject The item needs to be type checked | |
*/ | |
function getType(subject) { | |
return Object.prototype.toString.call(subject).slice(8, -1); | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function deepEqualObject(expected, actual) { | |
const path = []; | |
const expectedVal = []; | |
function changes(expected, actual) { | |
return _.transform(expected, (result, value, key) => { | |
if (!_.isEqual(value, actual[key])) { | |
path.push(key); | |
result[key] = | |
_.isObject(value) && _.isObject(actual[key]) | |
? changes(value, actual[key]) | |
: expectedVal.push(value) && value; | |
} | |
}); | |
} | |
changes(expected, actual); | |
return { path, expectedVal }; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function shallowEqualArray(expected, actual) { | |
for (let item in expected) { | |
if (!actual[item]) { | |
return item; | |
} | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function checkArray(expected, actual) { | |
const expectedType = getType(expected); | |
const expectedLength = expected.length; | |
const actualType = getType(actual); | |
const actualLength = actual.length; | |
if (expectedType !== actualType) { | |
return `Expected type ${expectedType} but found ${actualType}`; | |
} else if (expectedLength !== actualLength) { | |
return `Expected length ${expectedLength} but found ${actualLength}`; | |
} else { | |
const shallow = shallowEqualArray(expected, actual); | |
return shallow === true | |
? shallow | |
: `Expected item in array ${item} but found undefined`; | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function checkObject(expected, actual) { | |
const expectedType = getType(expected); | |
const actualType = getType(actual); | |
if (expectedType !== actualType) { | |
return `Expected type ${expectedType} but found type ${actualType}`; | |
} else { | |
const deep = deepEqualObject(expected, actual); | |
return deep.path.length === 0 | |
? true | |
: `Expected ${deep.path.join('.')} "${deep.expectedVal}" | |
but found "${_.get(actual, deep.path)}"`; | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function checkOtherTypes(expected, actual) { | |
const expectedType = getType(expected); | |
const actualType = getType(actual); | |
if (expectedType !== actualType) { | |
return `Expected type ${expectedType} but found type ${actualType}`; | |
} else if (expected !== actual) { | |
return `Expected "${expected}" but found "${actual}"`; | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
* @param {String} type The returned type Array, Null, Object, String, Undefined, Number | |
*/ | |
function assertTypes(expected, actual, type) { | |
switch (type) { | |
case "Object": | |
return checkObject(expected, actual); | |
break; | |
case "Array": | |
return checkArray(expected, actual); | |
break; | |
default: | |
return checkOtherTypes(expected, actual); | |
} | |
} | |
/** | |
* Asserts "expected" versus "actual", | |
* 'failing' the assertion (via Error) if a difference is found. | |
* | |
* @param {String} message The comparison message passed by the user | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function assertEquals(expected, actual) { | |
const type = getType(expected); | |
const asserted = assertTypes(expected, actual, type); | |
if (asserted !== true) { | |
throw new AssertFailEx(asserted); | |
} | |
} | |
/* -- Test running code: --- */ | |
/** | |
* Runs a "assertEquals" test. | |
* | |
* @param {String} message The initial message to pass | |
* @param {Array} assertionFailures List of messages that will be displayed on the UI for evaluation | |
* @param {*} expected Expected item | |
* @param {*} actual The actual item | |
*/ | |
function runTest(message, assertionFailures, expected, actual) { | |
try { | |
assertEquals(expected, actual); | |
} catch (failure) { | |
assertionFailures.push(`${message} ${failure.message}`); | |
} | |
} | |
function runAll() { | |
var complexObject1 = { | |
propA: 1, | |
propB: { | |
propA: [1, { propA: "a", propB: "b" }, 3], | |
propB: 1, | |
propC: 2 | |
} | |
}; | |
var complexObject1Copy = { | |
propA: 1, | |
propB: { | |
propA: [1, { propA: "a", propB: "b" }, 3], | |
propB: 1, | |
propC: 2 | |
} | |
}; | |
var complexObject2 = { | |
propA: 1, | |
propB: { | |
propB: 1, | |
propA: [1, { propA: "a", propB: "c" }, 3], | |
propC: 2 | |
} | |
}; | |
var complexObject3 = { | |
propA: 1, | |
propB: { | |
propA: [1, { propA: "a", propB: "b" }, 3], | |
propB: 1 | |
} | |
}; | |
// Run the tests | |
var assertionFailures = []; | |
runTest("Test 01: ", assertionFailures, "abc", "abc"); | |
runTest("Test 02: ", assertionFailures, "abcdef", "abc"); | |
runTest("Test 03: ", assertionFailures, ["a"], { 0: "a" }); | |
runTest("Test 04: ", assertionFailures, ["a", "b"], ["a", "b", "c"]); | |
runTest("Test 05: ", assertionFailures, ["a", "b", "c"], ["a", "b", "c"]); | |
runTest("Test 06: ", assertionFailures, complexObject1, complexObject1Copy); | |
runTest("Test 07: ", assertionFailures, complexObject1, complexObject2); | |
runTest("Test 08: ", assertionFailures, complexObject1, complexObject3); | |
runTest("Test 09: ", assertionFailures, null, {}); | |
// Output the results | |
var messagesEl = document.getElementById("messages"); | |
var newListEl; | |
var i, ii; | |
for (i = 0, ii = assertionFailures.length; i < ii; i++) { | |
newListEl = document.createElement("li"); | |
newListEl.innerHTML = assertionFailures[i]; | |
messagesEl.appendChild(newListEl); | |
} | |
} | |
runAll(); | |
</script></body> | |
</html> |
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
/** | |
* @param {String} message The message that will be thrown | |
*/ | |
"use strict"; | |
function AssertFailEx(message) { | |
this.message = message; | |
this.name = "AssertFailEx"; | |
} | |
/** | |
* @param {*} subject The item needs to be type checked | |
*/ | |
function getType(subject) { | |
return Object.prototype.toString.call(subject).slice(8, -1); | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function deepEqualObject(expected, actual) { | |
var path = []; | |
var expectedVal = []; | |
function changes(expected, actual) { | |
return _.transform(expected, function (result, value, key) { | |
if (!_.isEqual(value, actual[key])) { | |
path.push(key); | |
result[key] = _.isObject(value) && _.isObject(actual[key]) ? changes(value, actual[key]) : expectedVal.push(value) && value; | |
} | |
}); | |
} | |
changes(expected, actual); | |
return { path: path, expectedVal: expectedVal }; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function shallowEqualArray(expected, actual) { | |
for (var _item in expected) { | |
if (!actual[_item]) { | |
return _item; | |
} | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function checkArray(expected, actual) { | |
var expectedType = getType(expected); | |
var expectedLength = expected.length; | |
var actualType = getType(actual); | |
var actualLength = actual.length; | |
if (expectedType !== actualType) { | |
return "Expected type " + expectedType + " but found " + actualType; | |
} else if (expectedLength !== actualLength) { | |
return "Expected length " + expectedLength + " but found " + actualLength; | |
} else { | |
var shallow = shallowEqualArray(expected, actual); | |
return shallow === true ? shallow : "Expected item in array " + item + " but found undefined"; | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function checkObject(expected, actual) { | |
var expectedType = getType(expected); | |
var actualType = getType(actual); | |
if (expectedType !== actualType) { | |
return "Expected type " + expectedType + " but found type " + actualType; | |
} else { | |
var deep = deepEqualObject(expected, actual); | |
return deep.path.length === 0 ? true : "Expected " + deep.path.join('.') + " \"" + deep.expectedVal + "\"\n but found \"" + _.get(actual, deep.path) + "\""; | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function checkOtherTypes(expected, actual) { | |
var expectedType = getType(expected); | |
var actualType = getType(actual); | |
if (expectedType !== actualType) { | |
return "Expected type " + expectedType + " but found type " + actualType; | |
} else if (expected !== actual) { | |
return "Expected \"" + expected + "\" but found \"" + actual + "\""; | |
} | |
return true; | |
} | |
/** | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
* @param {String} type The returned type Array, Null, Object, String, Undefined, Number | |
*/ | |
function assertTypes(expected, actual, type) { | |
switch (type) { | |
case "Object": | |
return checkObject(expected, actual); | |
break; | |
case "Array": | |
return checkArray(expected, actual); | |
break; | |
default: | |
return checkOtherTypes(expected, actual); | |
} | |
} | |
/** | |
* Asserts "expected" versus "actual", | |
* 'failing' the assertion (via Error) if a difference is found. | |
* | |
* @param {String} message The comparison message passed by the user | |
* @param {*} expected The expected item | |
* @param {*} actual The actual item | |
*/ | |
function assertEquals(expected, actual) { | |
var type = getType(expected); | |
var asserted = assertTypes(expected, actual, type); | |
if (asserted !== true) { | |
throw new AssertFailEx(asserted); | |
} | |
} | |
/* -- Test running code: --- */ | |
/** | |
* Runs a "assertEquals" test. | |
* | |
* @param {String} message The initial message to pass | |
* @param {Array} assertionFailures List of messages that will be displayed on the UI for evaluation | |
* @param {*} expected Expected item | |
* @param {*} actual The actual item | |
*/ | |
function runTest(message, assertionFailures, expected, actual) { | |
try { | |
assertEquals(expected, actual); | |
} catch (failure) { | |
assertionFailures.push(message + " " + failure.message); | |
} | |
} | |
function runAll() { | |
var complexObject1 = { | |
propA: 1, | |
propB: { | |
propA: [1, { propA: "a", propB: "b" }, 3], | |
propB: 1, | |
propC: 2 | |
} | |
}; | |
var complexObject1Copy = { | |
propA: 1, | |
propB: { | |
propA: [1, { propA: "a", propB: "b" }, 3], | |
propB: 1, | |
propC: 2 | |
} | |
}; | |
var complexObject2 = { | |
propA: 1, | |
propB: { | |
propB: 1, | |
propA: [1, { propA: "a", propB: "c" }, 3], | |
propC: 2 | |
} | |
}; | |
var complexObject3 = { | |
propA: 1, | |
propB: { | |
propA: [1, { propA: "a", propB: "b" }, 3], | |
propB: 1 | |
} | |
}; | |
// Run the tests | |
var assertionFailures = []; | |
runTest("Test 01: ", assertionFailures, "abc", "abc"); | |
runTest("Test 02: ", assertionFailures, "abcdef", "abc"); | |
runTest("Test 03: ", assertionFailures, ["a"], { 0: "a" }); | |
runTest("Test 04: ", assertionFailures, ["a", "b"], ["a", "b", "c"]); | |
runTest("Test 05: ", assertionFailures, ["a", "b", "c"], ["a", "b", "c"]); | |
runTest("Test 06: ", assertionFailures, complexObject1, complexObject1Copy); | |
runTest("Test 07: ", assertionFailures, complexObject1, complexObject2); | |
runTest("Test 08: ", assertionFailures, complexObject1, complexObject3); | |
runTest("Test 09: ", assertionFailures, null, {}); | |
// Output the results | |
var messagesEl = document.getElementById("messages"); | |
var newListEl; | |
var i, ii; | |
for (i = 0, ii = assertionFailures.length; i < ii; i++) { | |
newListEl = document.createElement("li"); | |
newListEl.innerHTML = assertionFailures[i]; | |
messagesEl.appendChild(newListEl); | |
} | |
} | |
runAll(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment