Up till now we've introduced exercises by giving examples of what the function is supposed to do. As our functions get more complex, we need to specify in more detail what right and wrong behavior for each function looks like. One good way to do this is by writing test cases.
Testing is an extremely important part of software engineering. When you test your code, you make sure that it works. Testing also gives you a permanent record of what correct behavior for your code looks like. In the future, if someone changes your code and breaks it, your tests will reject their changes. This way, tests can help ensure that your code will keep working forever.
Developers have lots of tools they can use to make testing easy and reliable. In this section we'll introduce one of these tools, called Jest. Starting with this lesson, we'll describe exercises by showing you test cases that your solution should pass. Then in JS4, you'll learn how to write your own test cases.
Follow these steps to create and run a test file that you can use to test your solutions to the Preflight exercises.
Create a new folder (or use an existing one)
We will be using the jest framework to run our tests, so we need to install
it.
npm i jest
We need to setup your folder for using jest . To do that, initialize your
project
npm init -y
Create a file for your solutions to this section. Call it preflight.js.
Create a file for the tests. Call it preflight.test.js. The word test in
the filename is important; Jest will only run files with test in the
filename.
Don't worry about what to put into this file just yet, the examples will guide you through what to put in.
First tell your test file to include the file that will have your solutions:
// fn will be an object with all your preflight solutionsconst fn = require('./preflight.js')
We're now ready to start writing functions. We'll do the first one step-by-step as an example.
Write a function named removeCharX that removes the character at the given index from a string.
removeCharX("We're in the endgame now.", 1) // "W're in the endgame now."
Testing this - In the following 5 steps, step 1 is most important (examples). These examples will be your tests and the following instructions will show you how to create tests and run them. After that, put your examples into the test and run it.
In the preflight.js file, set up the object that will be exported to the
test file and write your solution. You'll learn more about objects in JS3,
but for now you just need to know that allFuns is an object.
const allFuns = {}const removeCharX = ( ... ) => {// Your code goes here}allFuns.removeCharX = removeCharX // You need this line for every function// you write
Add this line to preflight.js. As you solve more exercises, just keep this
line at the end.
module.exports = allFuns
Open the Tests toggle and paste the code into your preflight.test.js file.
describe('removeCharX function', () => {it('should remove the first character', () => {const str = "We're in the endgame now."const result = fn.removeCharX(str, 0)expect(result).toEqual("e're in the endgame now.")})it('return the original string', () => {const str = 'a'const result = fn.removeCharX(str, 3)expect(result).toEqual('a')})it('return the original string', () => {const str = 'abc'const result = fn.removeCharX(str, -3)expect(result).toEqual('abc')})})
Function Shape
const removeCharX = (str, idx) => {}
Explanation
i and it starts from 0.
We need another variable result to collect all the new letters.i goes to the end of the string, we return the result.i is not equal to idx, we add letter to the resultCode
const removeCharX = (str, idx, i = 0, result = '') => {if (i === str.length) {return result}if (i !== idx) {result = result + str[i]}return removeCharX(str, idx, i + 1, result)}
jest preflight.test.js.Run it directly from the library: node_modules/jest/bin/jest.js
If that doesn't work:
npm i -g jest, and run it again: jestEACCES error, then you need to run it with sudo:
sudo npm i -g jestTo have your tests automatically re-run as you edit the code or add new
tests, run jest with the --watch option in one terminal and leave it
running. As you change your code in your editor (VS Code, vim, etc.) your
tests will run automatically. Press q to exit when you're done.
jest --watch preflight.test.js
For each new exercise, click the Tests toggle and add the tests to your
preflight.test.js file. Then add your solution to the preflight.js file and
re-run the tests (or leave them running with --watch).
Write a function named less3Diff that determines whether two strings have
fewer than 3 different characters. Both arguments will have the same length.
less3Diff('spiderman', 'spyderman') // true because only character at index 2// is differentless3Diff('spiderman', 'mary jane') // false
Tests
describe('less3Diff function', () => {it('should return false on strings with >= 3 letters different', () => {const result = fn.less3Diff('spiderman', 'mary jane')expect(result).toEqual(false)})it('should return true if < 3 letters different', () => {const result = fn.less3Diff('spiderman', 'spyderman')expect(result).toEqual(true)})it('should always return true on strings < 3 characters', () => {const result = fn.less3Diff('jk', 'lm')expect(result).toEqual(true)})it('should return false on strings equal to 3 and that are different', () => {const result = fn.less3Diff('cho', 'tis')expect(result).toEqual(false)})})
Shape
const less3Diff = (str1, str2) => {}
Explanation
str1 and string str2.i) which starts
at 0.differences ) which starts at 0 as well.i equals the length of str1,differences is greater than 2,str1 does not equal first character of
second string str2,differences by incrementing it by 1.Code
const less3Diff = (str1, str2, i = 0, differences = 0) => {if (differences > 2) {return false}if (i === str1.length) {return true}if (str1[i] !== str2[i]) {differences = differences + 1}return less3Diff(str1, str2, i + 1, differences)}
Write a function named reverso that takes in a string, and returns a new
string with the input string reversed.
reverso('sneakers') // srekaens
Tests
From here on you'll notice that some of our test suites include tests on
unlikely input. After all, most people who use your reverso function
probably won't try to reverse an empty string. But your code should ready to
handle situations that test the limits of the data types it works with.
Engineers call these edge cases, and you'll see them pop up in many of
our tests!
describe('reverso function', () => {it('should reverse "sneakers"', () => {const result = fn.reverso('sneakers')expect(result).toEqual('srekaens')})it('should reverse strings of one letter', () => {const result = fn.reverso('s')expect(result).toEqual('s')})it('should return an empty string when given an empty string', () => {const result = fn.reverso('')expect(result).toEqual('')})})
Shape
const reverso = (str, i = 0, result = '') => {}
Explanation
str.i) which starts at 0.result ) which starts at empty string "".result equals to the length of string str,result.Code
const reverso = (str, i = 0, result = '') => {if (result.length === str.length) {return result}return reverso(str, i + 1, str[i] + result)}
Write a function named delayAndCall that takes in 2 parameters, a number
and a function, and returns a function
When the returned function is called, the input function will execute after the input number of milliseconds.
const fun = delayAndCall(1000, () => {console.log('I am groot')})fun() // after 1 second, 'I am groot' will be loggedfun() // after 1 second, 'I am groot' will be logged
Tests
// Don't worry about understanding all the functions used to test// this function. But they use natural language, so you might// be able to figure them out!describe('delayAndCall function', () => {jest.useFakeTimers()it('should run function after 1000 milliseconds', () => {const callback = jest.fn()const fun = fn.delayAndCall(1000, callback)fun()expect(callback).not.toBeCalled() // It shouldn't run right awayjest.advanceTimersByTime(1000)expect(callback).toBeCalled() // But after 1000ms// it should have run})it('should run callback repeatedly', () => {const callback = jest.fn()const fun = fn.delayAndCall(1000, callback)fun()expect(callback).not.toBeCalled() // It shouldn't run right awayjest.runAllTimers()expect(callback).toHaveBeenCalledTimes(1) // Should have been// called oncefun()jest.runAllTimers()expect(callback).toHaveBeenCalledTimes(2) // Now it should have// been called twice})})
Shape
const delayAndCall = (time, fn) => {}
Explanation
time and a function fn.setTimeout with given function fn and number time.Code
const delayAndCall = (time, fn) => {return () => {setTimeout(fn, time)}}
Write a function named primeMachine that takes in a number and returns a
function.
const getPrime = primeMachine(10)getPrime() // 11getPrime() // 13getPrime() // 17getPrime() // 19
I'm going to start writing a few examples (x3) to make sure I understand the problem.
// example 1: I intentionally picked a negative numberconst getPrime1 = primeMachine(-2)getPrime1() // Should return 2? Interviewer: YesgetPrime1() // Should return 3? Interviewer: Yes// example 2: I intentionally picked a prime number to startconst getPrime2 = primeMachine(5)getPrime2() // Should return 7? Interviewer: YesgetPrime2() // Should return 11? Interviewer: Yes// example 3: Just used the example I was givenconst getPrime3 = primeMachine(10)getPrime3() // Should return 11? Interviewer: YesgetPrime3() // Should return 13? Interviewer: YesgetPrime3() // Should return 17? Interviewer: Yes
Just so I don't have to think about this much, I'll just write out the shape of the function because it is obvious and get it out of the way. (Interviewer: OK)
I know that the primeMachine function takes in a parameter, and returns a
function.
const primeMachine = start => {return () => {}}
Okay, so we probably need a few helper functions. I need a function to check to
see if a number is prime. And then I also need another function to get the next
prime number. I'll assume I have these 2 functions that I need and will just
focus on solving the primeMachine function first. Then I'll come back to them.
When the primeMachine function is called, we need to check for out-of-range
input (like < 2) and change them to 1. Also, if the input is a prime number
itself, we should add 1 to it so we get the next one.
When the returned function is called, it will return the result of calling the
getNextPrime function. After getting the result, we need to increment the
input number by 1.
In this step, our main task is to write the codes for primeMachine function.
We will write the codes for isPrime and getNetPrime functions in step 5 if
requested by the interviewer.
const isPrime = num => {// Returns true or false. Don't actually write anything more beyond this.// we will write the codes in step 5 if requested}const getNextPrime = num => {// Returns the next prime number which is >= than the num input.// Don't write anything beyond this.// we will write the codes in step 5 if requested}const primeMachine = start => {if (start < 2) {start = 1 // Any number smaller than 2 is not a prime.}if (isPrime(start)) {start = start + 1}return () => {const result = getNextPrime(start)start = start + 1return result}}
I went through the original 3 examples from step 1 to make sure the result was correct step by step.
Then I asked the interviewer: Do you want me to implement (write) the isPrime
and getNextPrime functions?
If you could not figure out this function yourself, follow the 5 steps above and
solve for isPrime and getNextPrime functions. Answers are included in the
answer toggle for this problem.
Tests
describe('primeMachine function', () => {it('should return next 2 primes starting with a negative number', () => {const getPrime1 = fn.primeMachine(-2)expect(getPrime1()).toEqual(2)expect(getPrime1()).toEqual(3)})it('should not return starting number if prime', () => {const getPrime2 = fn.primeMachine(5)expect(getPrime2()).toEqual(7)expect(getPrime2()).toEqual(11)})it('should return next 3 primes starting at 10', () => {const getPrime3 = fn.primeMachine(10)expect(getPrime3()).toEqual(11)expect(getPrime3()).toEqual(13)expect(getPrime3()).toEqual(17)})})
Shape
const isPrime = num => {}const getNextPrime = () => {}const primeMachine = () => {}
Explanation
num .isPrime to determine which number is a prime.getNextPrime to get the next prime
number starting from the given number num.primeMachine function,getNextPrime function as a
parameter.getNextPrime function on start to
be passed in as a parameter.start is less than 2,start to 1.start is prime,start by 1.result) that stores the
result of running getNextPrime function.start as argument inside this getNextPrime function.start equals result plus 1.resultCode
const isPrime = (num, start = 2) => {if (num < start) {return false}if (num === start) {return true}if (num % start === 0) {return false}return isPrime(num, start + 1)}const getNextPrime = start => {if (isPrime(start)) {return start}return getNextPrime(start + 1)}const primeMachine = start => {if (start < 2) {start = 1 // any number smaller than 2 is not a prime.}if (isPrime(start)) {start = start + 1}return () => {const result = getNextPrime(start)start = result + 1return result}}
In the last lesson, you got started with HTML and learned more about functions. If you had any issues solving any of the Preflight questions, please review the previous lesson by doing the exercises and challenges again until you're comfortable. This lesson will rely heavily on the foundations covered in the previous lesson in two parts:
The first part will build on your existing HTML knowledge and introduce you to more element properties.
The second part will teach you what you need to know about arrays, a powerful non-primitive data type in JavaScript.
Like in the previous lesson, make sure you follow along by running the example code yourself. If the concept is new to you, do not copy and paste. You will learn better if you write out the code. Ready? Let's get started!
In JS1 you learned about:
tags, elements, and attributesdocument.queryselector and alertonclick and valueIn this section we will learn a new element and a new property that can make your websites more interactive. To follow along, first launch VS Code, create a new .html file and view/open it in your browser.
The <div> tag is one of the most important HTML tags in modern web design.
This very simple tag <div> defines a container or section that is used to
group other elements together inside it. All HTML elements inside the section
are called child elements of the <div>. For example:
<div><input type="text" class="username" /><input type="password" class="password" /><button>Log in</button></div>
In the example above, the two input elements and the button element are all
children of the div element. They are also sibling elements of each
other.
Real websites have many thousands of elements, and developers use divs to
group them into logical parts like “header,” “sidebar,” “body,” or “chat
window.” Each div can often be further broken into more divs. Therefore, a
typical web page has a hierarchy of several divs deep.
We already learned how to change elements like input boxes by setting their
values. By setting an element's innerHTML property, you can dynamically add
any other elements to the page. For example, you can change your page behavior
as follows:
<div class="container"></div><script>const box = document.querySelector('.container')box.innerHTML = ``</script>
In the example above, we are using JavaScript to dynamically add three HTML tags
inside the div tag. The 3 lines of HTML after box.innerHTML = might look a
little out of place, but they're actually one long string! Recall from JS0 how
we can use ", ', or ` to create a string. We chose ` here
because it allows us to easily include line breaks inside the string, and
because the HTML is already using quotation marks. If we had used " to begin
the string, the first attribute would have ended it and broken our code.
If you want to get a reference of an element that is inserted by JavaScript, pay attention and make sure you write the document.querySelector to search the element after adding the element tag!
Good
This will work because we are getting the .submitButton element after setting
innerHTML.
<div class="container"></div><button class="showButton">Show</button><script>const box = document.querySelector('.container')const button = document.querySelector('.showButton')button.onclick = () => {box.innerHTML = `<input type="text" class="username" /><input type="password" class="password" /><button class="submitButton">submit</button>`const submitButton = document.querySelector('.submitButton')submitButton.onclick = ...}</script>
Bad
The browser will throw an error because it is trying to get the .submitButton
element before it exists.
<div class="container"></div><button class="showButton">Show</button><script>const box = document.querySelector('.container')const button = document.querySelector('.showButton')const submitButton = document.querySelector('.submitButton')submitButton.onclick = ...button.onclick = () => {box.innerHTML = `<input type="text" class="username" /><input type="password" class="password" /><button class="submitButton">submit</button>`}</script>
Be careful when setting the innerHTML property
Elements lose their properties when they're being replaced by other elements. In
the example below, we bind an onclick function to the showButton button.
When the showButton button is clicked, the element inside the <div> tag is
totally replaced with an <h1> tag and a new button element. The original
button element has been replaced although the new button has exactly the same
class name and text. It's technically a brand new element. Replace the current
contents of your page with the below code to see what happens:
<input class="inputText" type="text" /><div class="container"><button class="showButton">Show</button></div><script>const box = document.querySelector('.container')const input = document.querySelector('.inputText')let button = document.querySelector('.showButton')button.onclick = () => {box.innerHTML = ``// When you click the button, its onclick function will replace// button element inside the box with the new innerHtml property.// Now if you click the button again, nothing happens.}</script>
Keep in mind that when you set the innerHTML property, whatever was there
previously is gone. In other words, after setting the innerHTML of an
element, all variables for the children elements are no longer usable.
In the above example, whenever the button is clicked, the clicked button is to
be replaced with an identical new button. So in the current onclick function,
after we set the container div's innerHtml, we need to select this new button
and give it an onclick function that sets the container's innerHtml including
a new button, selects that button and gives it an onclick function that... See
the problem here? We need to create a function that will run every time the
button is clicked, and it needs to assign itself as the button's onclick
function. Replacing our current button.onclick function with the following
code will make it work as many times as the button is clicked:
const prepareTheButton = () => {box.innerHTML = `<h1>${input.value}</h1><button class="showButton">Show</button>`button = document.querySelector('.showButton')button.onclick = prepareTheButton}button.onclick = prepareTheButton
If you do not wish to create new HTML elements and simply wish to change the
text inside an element, you should use the innerText property. The innerText
property will treat your strings as literal strings instead of html tags.
<h1 class="heading"></h1><script>const h1Element = document.querySelector('.heading')h1Element.innerText = "The hardest choices require the strongest wills."</script>
Some of these UI exercises may seem intimidating. When you feel overwhelmed, take a deep breath, think about what you know, and make sure to start with that. When you work as a software engineer, you will have to handle projects that seem really intimidating. In these situations, it is important to think about what you know and understand, then slowly branch out from there piece by piece.
Good engineers spend the majority of their time thinking, not coding. Whether you are working on these exercises, interviewing for a software engineering job, or working on projects, make sure you spend the first half of your allotted time thinking through the problem before actually starting to code.
Debugging - If your page is not doing what you want it to do, you can look
at the console. This is where the browser will try to tell you what the error
is if you've written your code in a way it can't understand.
Read this to learn how to open
the browser's console.
For each of the problems below, if you are struggling through the answers, please walk through each step with your mentor (in person) or in the chatroom to make sure you have each step in the answer written correctly.
Change Me, an input box, and a submit button. When the submit button is
clicked, the h1 element is changed to whatever is inside the input box.startApp )startApp function.<div> element named container. Within the <div> element, add
an input box that only allows numerical value to be entered and a button
named start. You also need a h1 Tag (outside the container).h1 and container div elements.solution that takes in a number as a parameter and
returns a function. Let's name the number as a.5 + )solution .result .clear button is clicked, replace the elements inside the container
with an input box element and a start button element. Clear h1 element,
then run step 4 (to add onclick function for the start button).result function and pass in the value (convert to number). Set
h1 element's inner text. (Example: 5 + 244 = 249 )Hello repeated that many times is displayed inside an
<h1> element.String Breaker. Split the input string
into single characters and add the individual characters into their own
<h1> element. For example, breaking Batman will generate:
<h1>B</h1><h1>a</h1><h1>t</h1><h1>m</h1><h1>a</h1><h1>n</h1>
Create an input box, generate button and a container div.
Write a function named solution that takes in a string and returns a
string. Example:
solution('hello')// returns "<h1>h</h1><h1>e</h1><h1>l</h1><h1>l</h1><h1>o</h1>"
When the generate button is clicked, run the solution function and pass the inputbox value as argument. Set container div's innerHTML to the returned value.
Create a helper function named isPrime that takes in a number as a
parameter and returns a boolean: if the number is prime.
Create another helper function named printAllPrimes that takes in a number
as a parameter and returns a string of all prime numbers from 2 to the
number:
printAllPrimes(7)// returns "2...3...5...7..."
In your HTML code, create an Input box, start button and h1 tag.
Finally, when the user clicks on the start button, run the second helper
function printAllPrimes with the input box value (turn it into a number) as
argument
Set the returned value into h1 element's innerText.
Now would be a good time to git add and git commit your changes so that if
you mess up, you can undo quickly and restart from here.
If you did not initialize your repository yet, run git init and now you will
be able to use git in that folder!
Simply change the printAllPrimes function to return a different string:
printAllPrimes(7)// returns "<h1>2</h1><h1>3</h1><h1>5</h1><h1>7</h1>"
Change the h1 element to a div element instead
Change innerText to innerHTML
Create an h1 input, and button tag. Select them.
Write a function that takes in 1 arguments: a string.
type('hello')// Will put into h1 element:/*...200ms...h...200ms...he...200ms...hel...200ms...hell...200ms...hello*/
When user clicks on the button, now you can run your amazing function! Run
the type function (above) and pass in the input value as an argument
button.onclick = () => {type(input.value)}
Part 2: Add the new text that was typed to the top of the page. (type a few times)
If you are stuck and want to solve this by yourself without looking at the solution, try doing the Chat Input exercise under Optional Additional Exercises below.
You need two display elements: an h1 element to display the typing, and a
div element to hold all of the typed strings.
When the typing is done (which should be your base case), you must clear the
h1 element and then set the div element's innerHTML to its existing value
plus the string (enclosed inside a h1 tag) that just completed in the
timeouts.
if (i === str.length) {// str is the user's inputh1Element.innerText = ''allMessages.innerHTML = `${allMessages.innerHTML} <h1>${str}</h1>`return}
Add a h1 tag, get the element
At the end of the type function above, add the string into the h1 element.
// allMessages is the h1 element from the previous steptype = (str, i=0) => {if (i === str.length) {...allMessages.innerHTML = allMessages.innerHTML + '<h1>' + str + '</h1>'return}....}
Stopwatch (every 10 milliseconds)
The JavaScript Math.floor() method rounds a number DOWNWARDS to the nearest integer, and returns the result. For example:
let num = Math.floor(3 / 2)// num is 1, because 3 divided by 2 is 1.5.// Rounding down will give you 1.num = Math.floor(7 / 2) // num is 3num = Math.floor(9 / 4) // num is 2// if the number is negative,// rounding down will give you the next smaller negative integernum = Math.floor(-2.1) // num is -3
We need an h1 and a button element. Select them.
We need a variable timeVal to keep track of how much time has passed. The
initial value of timeVal is 0.
We need a variable isRunning to keep track of whether the timer has started
or not. The initial value of isRunning is false.
We need a function (named display) that takes in a number and returns a string that displays the second and millisecond. There are 1000ms in a second.
display(2000) // return "02:00"display(28000) // return "28:00"display(28290) // return "28:29"
If you are struggling to write the display function, try to break it down:
28290 , you may need to get out 290 .% remainder.28290 % 1000 will give you 29029 from 290 (hundredths of a second)We need a helper function setTimer that will call setTimeout with 2
arguments, a function and 100 (because we want to update the clock every 100
ms). The callback function should:
timeVal with timeVal + 10display function and pass in timeVal as argument.h1 element's innerText to the result of the display function.setTimerTo test this step, run setTimer function and refresh your page. You should
see the timer increase second by second.
When you click on start button, the onclick event function should do this:
isRunning is true. If it is, return.isRunning to truesetTimer function.Make sure you test each button to check that it is working before moving on!
Add 2 button (Pause & Stop) elements and select them.
Pause button — the onclick event function will:
if isRunning is true, set isRunning to false and set button text to
Unpause.
Otherwise, set isRunning to true and button text to Pause and run the
setTimer function
Stop button — the onclick event function will:
Set isRunning to false and timeVal to 0
setTimer function — Add codes:
if we are stopped or paused, (isRunning is false), return from the function
and don't do anything.
How did you do? Stopwatch was definitely the most complex program we've built so far, so don't worry if you had to use mulitple hints! Good software engineers don't hesitate to ask for help when they need it.
As you might have expected, the basic structure for the JavaScript on this page
revolves around the onClick event functions of each of the three buttons:
Start, Pause/Unpause, and Stop. But one key variable that ties them all together
is the isRunning flag. This has to be defined early on in the script and
checked whenever a button is clicked so the setTimeout functions don't go out of
control! If you want to see time fly by, remove the isRunning checks from your
solution and press Start a few times.
Possible variants
The more complex the problem, the more ways there are to solve it. For example,
you might have kept two separate counters, one for seconds and one for
hundredths of a second, and thus avoided using Math.floor, or you might have
wrapped the isRunning and isPaused flags into one. These variants are fine
as long as your stopwatch functions exactly the same as the solution page.
blink . This function will run:p element has innerText value, reset it to ''p element to display the text input's valueblink again.blink functionIf you struggled with any of the above exercises, make sure you practice them until you can do them on your own. A few of these problems have shown up during interviews.
During your interviews or at work, you are expected to come up with these steps yourself. Before moving on to the next lesson (JS3), make sure you review these exercises over and over again until you are able to come up with the answer steps yourself.
Below are more exercises that you can do for more practice:
Optional Additional Exercises
countDown that takes in a number (x) as parameter
andx is smaller than or equal to 0, return.innerText to xcountDown again with
x-1 as argumentcountDown function.div
innerHTML property. Make sure to wrap your input value around h1 elements:
<h1> string </h1>Write a function named createFilter that takes in a string and returns a
function. It should work like the following:
// Example 1:let fn = createFilter('i')fn('finding') // should return "fndng"// Example 2:fn = createFilter('b')fn('breaking bad') // should return 'reaking ad'
Add a h1 tag (for the title), div tag (to hold the input and button
tags), and h1 tag (for displaying result) and select them.
Add a variable called filterFunction. We need this to store the result from
createFilter
Create a function called startApp that:
div element's innerHTML with input tag and a button tag.divcreateFilter function and pass in input value as argument.
Store the result in filterFunction<whatever value inside input>div element's innerHTML with input tag and 2 button tags.startApp functionfilterFunction, and store the result in result h1 element's
innerText property.input element's value to "" to clear itRun startApp function
counter and set its value to zero.onclick event to do :counter .innerText property to value of counterisShowing and set its value to false.onclick event function):isShowingh1 tag will display blank ( "")isShowing variable to falseisShowing to trueh1 tag will display "Hello World"