From f3a2be2970eb98d8e844f78629b97a005e5f433e Mon Sep 17 00:00:00 2001 From: Andrew Smith Date: Sun, 26 Apr 2020 09:45:46 -0700 Subject: [PATCH] Add solutions. --- src/www/js/ajax/ajax.js | 6 +- src/www/js/es-syntax/syntax.test.js | 121 ++++++--------------- src/www/js/events/events.js | 6 + src/www/js/fetch/fetch.js | 5 +- src/www/js/flags/flags.js | 28 +++++ src/www/js/functional/array.test.js | 90 ++++++--------- src/www/js/functional/higher-order.test.js | 24 +++- src/www/js/oop/classes.js | 67 +++++++++--- src/www/js/oop/constructors.js | 26 +++++ src/www/js/oop/constructors.test.js | 2 + src/www/js/oop/create.test.js | 16 ++- src/www/js/this/this.test.js | 16 ++- 12 files changed, 232 insertions(+), 175 deletions(-) diff --git a/src/www/js/ajax/ajax.js b/src/www/js/ajax/ajax.js index a49e043d..d04de9cc 100644 --- a/src/www/js/ajax/ajax.js +++ b/src/www/js/ajax/ajax.js @@ -13,7 +13,11 @@ */ export const Ajax = (() => { const raw = async (url, method, data) => { - // Implement this function. + const response = await fetch( + url, + { body: JSON.stringify(data), method }, + ) + return response.json() } // HTTP GET (Fetch resource). diff --git a/src/www/js/es-syntax/syntax.test.js b/src/www/js/es-syntax/syntax.test.js index 2174cd4b..3c433ca2 100644 --- a/src/www/js/es-syntax/syntax.test.js +++ b/src/www/js/es-syntax/syntax.test.js @@ -5,14 +5,14 @@ describe('ES2015 syntax', () => { * Assume the codebase uses `let` declarations only for variables * that are reassigned. Use `const` otherwise. */ - var a = 4 - var b = [1, 2, 3] + let a = 4 + const b = [1, 2, 3] if (b.length < 4) b.push(a) expect(b).toEqual([1, 2, 3, 4]) - var a = 2 - var c = [1, a, 3] + a = 2 + const c = [1, a, 3] expect(c).toEqual([1, 2, 3]) }) }) @@ -20,7 +20,7 @@ describe('ES2015 syntax', () => { describe('loops', () => { it('rewrite the for loop to use a let variable', () => { const nums = [] - for (var i = 0; i < 3; i++) { + for (let i = 0; i < 3; i++) { nums.push(i) } expect(nums).toEqual([0, 1, 2]) @@ -31,44 +31,39 @@ describe('ES2015 syntax', () => { it('rewrite using object function shorthand', () => { const person = { name: 'Andrew', - getName: function() { return this.name } + getName() { return this.name } } expect(person.getName()).toEqual('Andrew') }) it('rewrite using object property shorthand', () => { const name = 'Andrew' - const person = { name: name } + const person = { name } expect(person.name).toEqual('Andrew') }) }) describe('functions', () => { it('rewrite the function declaration with arrow syntax', () => { - expect(foo()).toEqual('foo') + const foo = () => 'foo' - function foo() { - return 'foo' - } + expect(foo()).toEqual('foo') }) it('rewrite the function declaration, and use implicit return for anonymous function', () => { - expect(addOneToValues([1, 2, 3])).toEqual([2, 3, 4]) - expect(() => addOneToValues([])).toThrow('Values required') - - function addOneToValues(xs) { + const addOneToValues = (xs) => { if (xs.length < 1) throw new Error('Values required') // HINT: you can use an implicit return arrow function by omitting the curly brackets - return xs.map(function (x) { - return x + 1 - }) + return xs.map(x => x + 1) } + + expect(addOneToValues([1, 2, 3])).toEqual([2, 3, 4]) + expect(() => addOneToValues([])).toThrow('Values required') }) it('rewrite the logic in the function to use default parameters', () => { - const getIndexOfFoo = (str) => { - const strDefault = str || '' - return strDefault.indexOf('foo') + const getIndexOfFoo = (str = '') => { + return str.indexOf('foo') } expect(getIndexOfFoo('hello foo bar')).toEqual(6) @@ -79,9 +74,7 @@ describe('ES2015 syntax', () => { describe('array spread and destructuring', () => { it('rewrite using array destructuring', () => { const favoriteThings = ['tea', 'chocolate', 'bicycles', 'mangoes'] - const tea = favoriteThings[0] - const chocolate = favoriteThings[1] - const others = favoriteThings.slice(2) + const [tea, chocolate, ...others] = favoriteThings expect(tea).toEqual('tea') expect(chocolate).toEqual('chocolate') expect(others).toEqual(['bicycles', 'mangoes']) @@ -89,22 +82,12 @@ describe('ES2015 syntax', () => { it('rewrite to use rest parameters', () => { // takes the first number, and then adds it to each value in the remaining numbers and returns an array - const addNToNumbers = function () { - const n = arguments[0] - const nums = Array.prototype.slice.call(arguments, 1) - return nums.map(val => val + n) - } + const addNToNumbers = (n, ...nums) => nums.map(val => val + n) expect(addNToNumbers(3, 1, 2, 5)).toEqual([4, 5, 8]) }) it('rewrite using spread syntax to shallow-copy an array', () => { - const copyArray = (arr) => { - const copy = [] - for (let i = 0; i < arr.length; i++) { - copy.push(arr[i]) - } - return copy - } + const copyArray = (arr) => [...arr] const arr1 = [1, 2, 3] const copy = copyArray(arr1) @@ -114,10 +97,7 @@ describe('ES2015 syntax', () => { }) it('write a function that immutably adds a new item to an array', () => { - const concat = (arr, item) => { - arr.push(item) - return arr - } + const concat = (arr, item) => [...arr, item] const animals = ['cat', 'dog', 'bird'] const moarAnimals = concat(animals, 'alpaca') expect(animals === moarAnimals).toEqual(false) @@ -125,10 +105,7 @@ describe('ES2015 syntax', () => { }) it('write a function that immutably prepends a new item to an array', () => { - const prepend = (arr, item) => { - arr.unshift(item) - return arr - } + const prepend = (arr, item) => [item, ...arr] const animals = ['cat', 'dog', 'bird'] const moarAnimals = prepend(animals, 'alpaca') expect(moarAnimals).toEqual(['alpaca', 'cat', 'dog', 'bird']) @@ -136,34 +113,16 @@ describe('ES2015 syntax', () => { }) it('rewrite using spread syntax to duplicate the contents of an array', () => { - const duplicateArrayContents = (arr) => { - const result = [] - for (let i = 0; i < 2; i++) { - for (let j = 0; j < arr.length; j++) { - result.push(arr[j]) - } - } - return result - } + const duplicateArrayContents = (arr) => [...arr, ...arr] expect(duplicateArrayContents([1, 2, 3])).toEqual([1, 2, 3, 1, 2, 3]) }) it('CHALLENGE: rewrite using spread syntax to duplicate and reverse contents of an array', () => { // HINT: You can immutably reverse an array with: `[...array].reverse()` - const duplicateAndReverseArrayContents = (arr) => { - const result = [] - for (let i = 0; i < 2; i++) { - for (let j = 0; j < arr.length; j++) { - if (i === 0) { - result.push(arr[j]) - } else { - result.push(arr[arr.length - 1 - j]) - } - } - } - return result - } + const duplicateAndReverseArrayContents = (arr) => ( + [...arr, ...[...arr].reverse()] + ) expect(duplicateAndReverseArrayContents([1, 2, 3])).toEqual([1, 2, 3, 3, 2, 1]) }) @@ -174,9 +133,7 @@ describe('ES2018 syntax', () => { describe('object spread and destructuring', () => { it('rewrite using object destructuring', () => { const person = { id: 42, name: 'Andrew', location: 'Seattle' } - const id = person.id - const name = person.name - const location = person.location + const { id, name, location } = person expect(id).toEqual(42) expect(name).toEqual('Andrew') expect(location).toEqual('Seattle') @@ -184,10 +141,7 @@ describe('ES2018 syntax', () => { it('rewrite using object spread and destructuring', () => { const withoutKey = (keyToRemove, obj) => { - const copy = {} - for (const [key, value] of Object.entries(obj)) { - if (key !== keyToRemove) copy[key] = value - } + const { [keyToRemove]: ignore, ...copy } = obj return copy } @@ -200,15 +154,15 @@ describe('ES2018 syntax', () => { it('use object destructuring with a key rename', () => { const person = { id: 42, name: 'Andrew', location: 'Seattle' } - const personId = person.id // destructure this, but keep the variable name `personId` + const { id: personId } = person expect(personId).toEqual(42) }) it('write a function that immutably records the todo IDs that are done', () => { // todos are expected to be like { 42: true, 63: true } - const markDone = (id, todos) => { - todos[id] = true - } + const markDone = (id, todos) => ( + { ...todos, [id]: true } + ) const doneTodos = { 42: true, 63: true } const moreDoneTodos = markDone(3, doneTodos) expect(moreDoneTodos).toEqual({ 42: true, 63: true, 3: true }) @@ -218,14 +172,11 @@ describe('ES2018 syntax', () => { describe('CHALLENGE: combining array and object spreads', () => { it('write a function that immutably updates the done flag on a list of todos', () => { - // helper function to locate a todo by its ID in an array. Returns the matched todo - const findTodo = (id, todos) => todos.find(todo => todo.id === id) - - const setTodoDone = (id, todos) => { - const todo = findTodo(id, todos) - todo.done = true // :-( - return todos - } + const setTodoDone = (id, todos) => ( + todos.map(todo => ( + todo.id === id ? { ...todo, done: true } : todo + )) + ) const todos = [{ id: 1, done: false }, { id: 2, done: false }] const updatedTodos = setTodoDone(2, todos) diff --git a/src/www/js/events/events.js b/src/www/js/events/events.js index e2846b88..c9ff9c78 100644 --- a/src/www/js/events/events.js +++ b/src/www/js/events/events.js @@ -13,3 +13,9 @@ // Your code here. +document.body.addEventListener('click', (e) => { + e.preventDefault() + const display = e.target.nextElementSibling + if (display.tagName !== 'SPAN') return + display.innerText = parseInt(display.innerText) + 1 +}) diff --git a/src/www/js/fetch/fetch.js b/src/www/js/fetch/fetch.js index dcb2960f..68d81e08 100644 --- a/src/www/js/fetch/fetch.js +++ b/src/www/js/fetch/fetch.js @@ -10,7 +10,8 @@ * returning a Promise that resolves to the deserialized JSON data. */ export const getArtists = (id) => { - - // Your code goes here. + const url = '/api/artists' + (id ? `/${id}` : '') + return fetch(url) + .then(response => response.json()) } diff --git a/src/www/js/flags/flags.js b/src/www/js/flags/flags.js index 1515da34..0f55788a 100644 --- a/src/www/js/flags/flags.js +++ b/src/www/js/flags/flags.js @@ -65,3 +65,31 @@ */ // Your code here. +const bucketList = document.getElementById('bucket').getElementsByTagName('ul')[0] + +const flag1 = document.querySelector('#container .main li.foo') +bucketList.appendChild(flag1) + +const flag2Text = document.querySelector('#articles .new') + .lastElementChild // p + .lastElementChild // a + .firstElementChild // span + .firstChild // text +const flag2 = document.createElement('li') +flag2.appendChild(flag2Text) +bucketList.appendChild(flag2) + +const flag3Text = document.querySelector('.footer div div div div').firstChild +const flag3 = document.createElement('li') +flag3.appendChild(flag3Text) +bucketList.appendChild(flag3) + +const flag4Text = document.querySelector('#article-3 p span').firstChild +const flag4 = document.createElement('li') +flag4.appendChild(flag4Text) +bucketList.appendChild(flag4) + +const flag5Text = document.getElementById('article-3').lastElementChild.previousElementSibling.firstChild +const flag5 = document.createElement('li') +flag5.appendChild(flag5Text) +bucketList.appendChild(flag5) diff --git a/src/www/js/functional/array.test.js b/src/www/js/functional/array.test.js index 5c6e0a63..8bd7998c 100644 --- a/src/www/js/functional/array.test.js +++ b/src/www/js/functional/array.test.js @@ -9,9 +9,8 @@ describe('Array higher-order functions', () => { it('rewrite the for loop with forEach', () => { const mockFn = jest.fn() - for (let i = 0; i < users.length; i++) { - mockFn(users[i]) - } + // element, index, array + users.forEach(mockFn) expect(mockFn.mock.calls.length).toEqual(3) expect(mockFn.mock.calls).toEqual([ @@ -24,29 +23,13 @@ describe('Array higher-order functions', () => { describe('#filter', () => { it('rewrite the filter operation without a for loop', () => { - const usersWithFavoriteColorBlue = [] - - for (let i = 0; i < users.length; i++) { - const user = users[i] - if (user.favoriteColor === 'blue') { - usersWithFavoriteColorBlue.push(user) - } - } - + const usersWithFavoriteColorBlue = users.filter((user) => user.favoriteColor === 'blue') expect(usersWithFavoriteColorBlue).toEqual([users[0]]) }) it('write a function #reject that does the opposite of #filter and uses that method', () => { - const reject = (pred, array) => { - const result = [] - for (let i = 0; i < array.length; i++) { - if (!pred(array[i])) { - result.push(array[i]) - } - } - return result - } - + const complement = (fn) => x => !fn(x) + const reject = (pred, array) => array.filter(complement(pred)) const usersWithoutFavoriteColorblue = reject(user => user.favoriteColor === 'blue', users) expect(usersWithoutFavoriteColorblue).toEqual([users[1], users[2]]) }) @@ -55,9 +38,13 @@ describe('Array higher-order functions', () => { describe('#every', () => { it('implement the #every method', () => { // === Uncomment me and implement === - // Array.prototype.every = function(pred) { - // console.log(this) // access the array with `this` - // } + Array.prototype.every = function (pred) { + let result = true + this.forEach(el => { + if (!pred(el)) result = false + }) + return result + } expect([1, 2, 3].every(x => x > 0)).toEqual(true) expect([1, 2, 3].every(x => x > 2)).toEqual(false) }) @@ -66,9 +53,13 @@ describe('Array higher-order functions', () => { describe('#some', () => { it('implement the #some method', () => { // === Uncomment me and implement === - // Array.prototype.some = function(pred) { - // console.log(this) // access the array with `this` - // } + Array.prototype.some = function (pred) { + let result = false + this.forEach(el => { + if (pred(el)) result = true + }) + return result + } expect([1, 2, 3].some(x => x > 2)).toEqual(true) expect([1, 2, 3].some(x => x > 4)).toEqual(false) }) @@ -76,13 +67,7 @@ describe('Array higher-order functions', () => { describe('#map', () => { it('rewrite using #map', () => { - const incrementAgeOfHumans = (humans) => { - const result = [] - for (let i = 0; i < humans.length; i++) { - result.push({ ...humans[i], age: humans[i].age + 1 }) - } - return result - } + const incrementAgeOfHumans = (humans) => humans.map((human) => ({ ...human, age: human.age + 1 })) expect(incrementAgeOfHumans(users)).toEqual([ { id: 1, name: 'Andrew', age: 34, favoriteColor: 'blue' }, @@ -94,40 +79,29 @@ describe('Array higher-order functions', () => { describe('#reduce', () => { it('rewrite using reduce', () => { - const doubleSum = (vals) => { - let result = 0 - for (let i = 0; i < vals.length; i++) { - result += vals[i] * 2 - } - return result - } + const doubleSum = (vals) => ( + vals.reduce((acc, val) => acc + val * 2, 0) + ) expect(doubleSum([1, 2, 3])).toEqual(2 + 4 + 6) }) it('rewrite #flatten using reduce', () => { // TIP: Use Array#concat to join two arrays together immutably - const flatten = (tuples) => { - const result = [] - for (let i = 0; i < tuples.length; i++) { - for (let j = 0; j < tuples[i].length; j++) { - result.push(tuples[i][j]) - } - } - return result - } - + const flatten = (tuples) => tuples.reduce((acc, tuple) => acc.concat(tuple)) expect(flatten([[0, 1], [2, 3, 4], [5], []])).toEqual([0, 1, 2, 3, 4, 5]) }) it('rewrite #groupByName using reduce', () => { const groupByName = (arr) => { - const groupings = {} - for (const val of arr) { - const currentVal = groupings[val] || 0 - groupings[val] = currentVal + 1 - } - return groupings + return arr.reduce( + (acc, val) => { + const currentVal = acc[val] || 0 + acc[val] = currentVal + 1 + return acc + }, + {} + ) } const names = ['Alice', 'Bob', 'Tiff', 'Bruce', 'Alice'] expect(groupByName(names)).toEqual({ Alice: 2, Bob: 1, Tiff: 1, Bruce: 1 }) diff --git a/src/www/js/functional/higher-order.test.js b/src/www/js/functional/higher-order.test.js index 620d3734..d34df321 100644 --- a/src/www/js/functional/higher-order.test.js +++ b/src/www/js/functional/higher-order.test.js @@ -9,7 +9,12 @@ describe('Higher Order Functions', () => { // it should return a function that expects an event // it should call `#stopPropagation` on the event, and then // pass it to the event handler - const withoutPropagation = () => {} + const withoutPropagation = (cb) => { + return (ev) => { + ev.stopPropagation() + cb(ev) // ?? where does event come from? + } + } // don't edit me... // TODO test that eventWasHandled @@ -28,12 +33,23 @@ describe('Higher Order Functions', () => { // That invocation returns a function that receives other arguments to be given to the callback function // // If you're confused, look at its usage. - const repeat = () => {} + // #repeat takes a number, of how many times a callback function will be repeated + // It returns a function that will expect a count (which iteration of the repetition), along with other arguments + // It returns a function that receives other arguments to be given to the callback function + const repeat = (num) => ( + (cb) => ( + (...args) => { + for (let i = 1; i <= num; i++) { + cb(i, ...args) + } + } + ) + ) // do not edit const repeat3Times = repeat(3) - const callMockFnThreeTimesWithArgs = repeat3Times(mockFn) - callMockFnThreeTimesWithArgs('hello', 'world') + const callMock3TimesWithArgs = repeat3Times((count, ...args) => { mockFn(count, ...args) }) + callMock3TimesWithArgs('hello', 'world') expect(mockFn.mock.calls).toEqual([ [1, 'hello', 'world'], [2, 'hello', 'world'], diff --git a/src/www/js/oop/classes.js b/src/www/js/oop/classes.js index f716e8f3..c87450b7 100644 --- a/src/www/js/oop/classes.js +++ b/src/www/js/oop/classes.js @@ -11,7 +11,19 @@ * */ -export class TempTracker {} +export class TempTracker { + constructor() { + this.temp = 0 + } + + getTemp() { + return this.temp + } + + setTemp(v) { + this.temp = v + } +} /** * Create a class `AverageTempTracker` @@ -25,7 +37,21 @@ export class TempTracker {} * */ -export class AverageTempTracker {} +export class AverageTempTracker extends TempTracker { + constructor() { + super() + this.temps = [] + } + + setTemp(v) { + super.setTemp(v) + this.temps.push(v) + } + + getAverageTemp() { + return this.temps.reduce((acc, n) => acc + n) / this.temps.length + } +} /** * Create a class `BoundedTempTracker` @@ -44,20 +70,31 @@ export class AverageTempTracker {} * */ -export class BoundedTempTracker {} - -export class Counter { - /** - * Step 1: Rewrite setting the initial state without - * the `constructor` method. - */ +export class BoundedTempTracker extends TempTracker { constructor() { - this.counter = 1 + super() + this.reads = 0 + } + + setTemp(v) { + if (v < 0 || v > 100) return + super.setTemp(v) + } + + getTemp() { + this.reads += 1 + return super.getTemp() } - /** - * Step 2: rewrite `this.counter` to use a private field - * so it can't be accessed outside the class. - */ - tick() { return this.counter++ } + getTempReads() { + return this.reads + } +} + +export class Counter { + #counter = 1 + + tick() { + return this.#counter++ + } } diff --git a/src/www/js/oop/constructors.js b/src/www/js/oop/constructors.js index 5c50cf19..7e04bd9a 100644 --- a/src/www/js/oop/constructors.js +++ b/src/www/js/oop/constructors.js @@ -27,3 +27,29 @@ * // Stack is now empty. * c.get(); // returns 20 */ + +export const Calculator = function (initialValue = 0) { + this.otherValues = [] + this.value = initialValue +} + +Calculator.prototype.add = function () { + this.value = this.otherValues.reduce( + (acc, n) => acc + n, + this.value + ) + this.otherValues.length = 0 +} +Calculator.prototype.mul = function () { + this.value = this.otherValues.reduce( + (acc, n) => acc * n, + this.value + ) + this.otherValues.length = 0 +} +Calculator.prototype.get = function () { + return this.value +} +Calculator.prototype.push = function (value) { + this.otherValues.push(value) +} diff --git a/src/www/js/oop/constructors.test.js b/src/www/js/oop/constructors.test.js index c3550577..4d243e07 100644 --- a/src/www/js/oop/constructors.test.js +++ b/src/www/js/oop/constructors.test.js @@ -1,3 +1,5 @@ +import { Calculator } from './constructors' + describe('Constructor Functions Exercise', () => { it('Should provide a Calculator function', () => { expect(typeof Calculator).toEqual('function') diff --git a/src/www/js/oop/create.test.js b/src/www/js/oop/create.test.js index e7a0b7aa..a7d77de6 100644 --- a/src/www/js/oop/create.test.js +++ b/src/www/js/oop/create.test.js @@ -5,7 +5,7 @@ describe('Creating inheritance', () => { } it('make an object that has person as its prototype', () => { - const instructor = doSomething() + const instructor = Object.create(person) instructor.first = 'Andrew' instructor.last = 'Smith' expect(instructor.fullName()).toEqual('Andrew Smith') @@ -14,16 +14,24 @@ describe('Creating inheritance', () => { it('make a #makePerson function that points to `person` as prototype', () => { // makePerson should take a `first` and `last` to give to the created object - const makePerson = () => {} + const makePerson = (first, last) => { + const obj = Object.create(person) + obj.first = first + obj.last = last + return obj + } const instructor = makePerson('Andrew', 'Smith') expect(instructor.fullName()).toEqual('Andrew Smith') expect(instructor.species).toEqual('Homo sapien') }) - it('write a function that tells how many prototype hops is needed to find a prop', () => { - const baconNumber = () => {} it('CHALLENGE: write a function that tells how many prototype hops is needed to find a prop', () => { + const baconNumber = (obj, prop, hops = 0) => { + if (!obj) return -1 + if (obj.hasOwnProperty(prop)) return hops + return baconNumber(obj.__proto__, prop, hops + 1) + } const a = { color: 'green' } const b = Object.create(a) diff --git a/src/www/js/this/this.test.js b/src/www/js/this/this.test.js index aff6afd3..3ca689a8 100644 --- a/src/www/js/this/this.test.js +++ b/src/www/js/this/this.test.js @@ -1,6 +1,11 @@ describe('this', () => { it('EXERCISE 1: should give an age and an function #isAdult which checks if age is >= 18', () => { - const person = {} + const person = { + age: 17, + isAdult() { + return this.age >= 18 + } + } expect(person.isAdult()).toEqual(false) }) @@ -8,7 +13,7 @@ describe('this', () => { it('EXERCISE 2: fix this code', () => { const c = { color: 'green', - getColor: () => { + getColor() { return this.color } } @@ -20,7 +25,7 @@ describe('this', () => { const greeter = { message: 'Hello', print() { - function otherFn() { + const otherFn = () => { expect(this.message).toEqual('Hello') } @@ -39,13 +44,13 @@ describe('this', () => { } } - const getColor = a.getColor + const getColor = a.getColor.bind(a) expect(getColor()).toEqual('red') }) it('EXERCISE 5: fix this code', () => { - const getAge = () => this.age + const getAge = function() { return this.age } const p1 = { age: 42, @@ -55,4 +60,3 @@ describe('this', () => { expect(p1.getAge()).toEqual(42) }) }) -