๐Ÿฅš JS1

What even is JavaScript?

๐Ÿ”–
  1. JS1
  2. โ†™๏ธ โ†˜๏ธ Making a choice
  3. โ™ป๏ธ Reusing the function
  4. โš–๏ธ Comparing current and target output
  5. โœ… โŒ Interpreting feedback
  6. โœ๏ธ Writing an assertion
  7. โŒ Errors
  8. โ“ Conditionally executing code
  9. โžก๏ธ Arrow functions
  10. ๐Ÿƒ Installing Jest
  11. ๐ŸŽ’ Starting a project
  12. ๐ŸŽฎ Playing computer
  13. ๐Ÿ’ฌ Declarations and statements
  14. ๐Ÿ’ผ First test case
  15. ๐Ÿ“ Running scripts
  16. ๐Ÿ“ค Returning from a function
  17. ๐Ÿ“ฆ Using packages
  18. ๐Ÿ”„ Reusing variable names
  19. ๐Ÿ”Ž Interpreting errors
  20. ๐Ÿ”Ž Testing a sub-goal
  21. ๐Ÿ”ฌ Identifying missing tests
  22. ๐Ÿ”ญ Scope
  23. ๐Ÿ•› 12 vs 24 hour clock
  24. Interacting with computers
  25. ๐ŸŽ›๏ธ Jest's Application Programming Interface
  26. ๐Ÿ—๏ธ Assembling the parts
  27. ๐Ÿ—๏ธ Sub-goal #2
  28. ๐Ÿท๏ธ Anonymous functions
  29. ๐Ÿท๏ธ Parameterising a function
  30. ๐Ÿท๏ธ Saving expressions
  31. ๐Ÿ•น๏ธ Using an interface
  32. ๐Ÿ–ฅ๏ธ Terminal interface
  33. ๐Ÿ–จ๏ธ Logging
  34. ๐Ÿ–ผ๏ธ Testing frameworks
  35. ๐Ÿ—„๏ธ Classifying data
  36. ๐Ÿ—„๏ธ Generalising further
  37. ๐Ÿ—„๏ธ Generalising further
  38. ๐Ÿง  Forming sub-goals
  39. ๐Ÿง  Interpreting this error
  40. ๐Ÿงฉ Percentages
  41. ๐Ÿงฎ Ordinal numbers
  42. ๐Ÿงฐ Install a UNIX based operating system
  43. ๐Ÿงฐ Install Node with nvm
  44. ๐Ÿงถ Accessing strings
  45. ๐Ÿงน Improving the code
  46. ๐Ÿงน Refactoring repetition
  47. ๐Ÿงพ Evaluating expressions
  48. ๐Ÿช„ Declaring functions
  49. ๐Ÿช„ Functions

JS1 block viewer

This block viewer lets you flick through all the existing blocks in the JS1 folder so you can choose what parts to add to your pages and what parts you might want to create, revise, or leave out.

It's literally just an alphabetical list of whatever is in this folder.

โ†™๏ธ โ†˜๏ธ Making a choice

Learning Objectives

Our function works for morning inputs like "08:00". In this case, the function returns the target output of "08:00 am" as required. However, at the moment, the output of formatAs12HourClock("23:00") is "23:00 am".

๐Ÿ’ก We need to execute some different logic when the time is beyond midday

We can interpret this behaviour as a question:

flowchart LR A{Is the time before midday?} -- true --> B[Add am to time string] A -- false --> C[???]

We need to make two changes to our code.

  1. We need to do something different depending on whether the time is before midday. This is called running code conditionally.
  2. And then we need to know what we do if the time is after midday.

We don’t need to solve the whole problem at once. First let’s work out how to do something different depending on the time. We can worry about what we need to do differently once we’ve solved this problem.

๐Ÿ’ก tip

It’s easier to search for the solutions to smaller parts of problems than the whole problem.

Programming is all about breaking down problems into smaller pieces which we can solve.

โ™ป๏ธ Reusing the function

Learning Objectives

Our goal is for convertToPercentage to be reusable for any number. To check this goal, let’s call convertToPercentage with different arguments and check the return value each time:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
const decimalNumber = 0.5;

function convertToPercentage() {
  const percentage = `${decimalNumber * 100}%`;
  return percentage;
}

const output1 = convertToPercentage(0.5);
const output2 = convertToPercentage(0.231);

console.log(output1);
console.log(output2);

When we execute this code we want to log the target output for each input: 0.5 and 0.231:

50%
23.1%

However, given the function’s current implementation, we get the following logs:

50%
50%

๐ŸŒ Global scope

At the moment, decimalNumber is in the global scope ๐Ÿงถ ๐Ÿงถ global scope Variables declared in the global scope are available everywhere in your program. Variables declared in a { block scope } are only available within that block. Any block within your program can access variables that are defined within the global scope. . Any functions we declare can reference variables in the global scope. If a variable is in the global scope, we say that variable is a global variable.

Play computer and step through the code to check why we get the output below:

50%
50%

โš–๏ธ Comparing current and target output

Learning Objectives

Let’s start with a function declaration of formatAs12HourClock:

1
function formatAs12HourClock() {}

Whenever we call formatAs12HourClock we expect it to return a particular value, which we call the ๐ŸŽฏ target output. For example, we expect formatAs12HourClock("08:00") to return "08:00 am". "08:00 am" is the ๐ŸŽฏ target output.

However, the current output is what the function actually returns right now.

๐Ÿ‘‰๐Ÿผ Work through the exercises

Let’s start with formatAs12HourClock defined like this:

1
function formatAs12HourClock() {}

Suppose we now call the function like this:

formatAs12HourClock("05:30");

For the call above, what is the

a) the current output? b) the ๐ŸŽฏ target output?

๐Ÿ‘‰๐Ÿผ Keep going

Let’s continue with formatAs12HourClock defined like this:

1
function formatAs12HourClock() {}

Suppose we call the function like this:

formatAs12HourClock("20:10");

For the call above, what is the

a) the current output? b) the ๐ŸŽฏ target output?

๐Ÿงฉ Comparing values

We have learned how to log values to the console. We can also compare two values. We check that a function produces some target output with a comparison.

We compare the current output of formatAs12HourClock("08:00") with the target output of "08:00 am" and ask: are these two values the same? We use a comparison operator to compare two expressions and check if they evaluate to the same value. We use the strict equality operator === to check if two values are the same.

Left equals Right

formatAs12HourClock("08:00") === "8:00 am";

=== checks if the values on the left and right of the operator are the same. We can think of formatAs12HourClock("08:00") === "8:00 am" as the question: “Is the return value of formatAs12HourClock("08:00") equal to "8:00 am" ?” This leads to the question:

What will the expression formatAs12HourClock("08:00") === "8:00 am" evaluate to?

โœ… โŒ Boolean values

Does 1 equal 0? Yes or No? True or False?

Some values are best represented as strings: any piece of text, a name, address, etc will most likely be stored using the string data type. The number data type can store numbers we use in calculations.

If we’re comparing two things, there are only two different states: true or false. This leads us to the boolean datatype, which only has true or false values. Whenever we compare two values with a comparison operator, we end up with a boolean value: true or false. It’s one or the other. It’s boolean.

// using the strict equality comparison expression

console.log(42 === 10 + 32);
// logs true

console.log(10 * 5 === 60);
// logs false

๐Ÿ‘‰๐Ÿพ Now work through the exercises

Look at the code below and predict what each expression will evaluate to. Write your prediction down. Use the Node REPL to check your answers. Check out some relevant documentation if you’ve not seen a particular operator before.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
true === false;
5 == 2 + 4;
4 * 5 == "20";
3 * 2 === 6;
Math.min(3, 4, 5) === 4;
let mhairiName = "Mhairi";
typeof mhairiName === "string";
let mhairiAge = 28;
let isMhairiOldEnoughToDrive = true;
let kilometersMhairiDrivesToWork = 9.4;

๐Ÿ‘‰๐Ÿฟ Keep Going

Look at the code below and predict what each expression will evaluate to. Write your prediction down. Use the Node REPL to check your answers. Check out some relevant documentation if you’ve not seen a particular operator before

1
2
3
4
5
6
7
"hello Mhairi" === `hello ${mhairiName}`;
"${mhairiName} is 28" === `Mhairi is ${mhairiAge}`;
isMhairiOldEnoughToDrive;
isMhairiOldEnoughToDrive === true;
mhairiAge >= 18;
29 <= mhairiAge;
Math.round(kilometersMhairiDrivesToWork) === 9;

๐Ÿ‘‰๐Ÿฟ Keep Going

Checking misconceptions ๐Ÿค”

Look at the code below and then predict what the expression will evaluate to. Be sure to explain your answer too. Use the Node Repl to check your answer. Does the REPL output make sense?

Try using the Node Repl to check your answer. Does the REPL output make sense?

1
console.log(10 + 32) === 42;

โœ… โŒ Interpreting feedback

Learning Objectives

We currently have a project structure like this:

week-4-test-example
โ”œโ”€โ”€ get-ordinal-number.test.js
โ”œโ”€โ”€ package.json
โ”œโ”€โ”€ package-lock.json
โ””โ”€โ”€ node_modules

1 directory, 3 files

And get-ordinal-number.test.js looks like this

test("converts 1 to an ordinal number", function () {
  expect(getOrdinalNumber(1)).toEqual("1st");
});

After running the test above, we should get feedback indicating whether or not the test has passed.

Predict and explain

Predict and explain what the test feedback will be when the test above is executed.

๐Ÿšข Defining the function

At the moment, our test feedback gives the following:

test-reference-error

Just like we saw when the test function wasn’t defined, the test code is throwing a ReferenceError ๐Ÿงถ ๐Ÿงถ ReferenceError A ReferenceError occurs when we try to reference a variable that we’ve not defined in our code.

This means that we haven’t defined a function named getOrdinalNumber, but we’re trying to use it.

To fix this, we can declare getOrdinalNumber.

function getOrdinalNumber() {}

test("converts 1 to an ordinal number", function () {
  expect(getOrdinalNumber(1)).toEqual("1st");
});

Now we can run the tests again and check the test feedback.

Assertion errors

We now get the following feedback:

test-feedback-fail

Jest tells us 3 main things:

  1. The test case that failed
  2. The target output and the current output
  3. The line number where error occurred

Jest defines Expected and Received in the test feedback:

  • Expected: “1st”
  • Received: undefined

exercise

What are the values of Expected and Received in the test output?

How do Received and Expected match up with the target output and expected output ?

What line number did the test case fail on?

Avoiding repetition

When we wrote console.assert tests before, we ended up extracting variables because we were re-using values.

Without Jest, this assertion would probably have looked more like:

const input = 1;
const targetOutput = "1st";
const currentOutput = getOrdinalNumber(input);
console.assert(targetOutput === currentOutput, `Expected ${targetOutput} but got ${currentOutput}`);

Because Jest makes a useful error message for us telling us what the target and current outputs are, we could write this all in one line. We didn’t need a variable so we could pass "1st" both to getOrdinalNumber and into the message.

Jest helped us to avoid writing more repetitive code.

Passing getOrdinalNumber

We can now pass the test by implementing functionality for the first test case. We could write the following:

get-ordinal-number.test.js:

1
2
3
4
5
6
7
function getOrdinalNumber() {
  return "1st";
}

test("converts 1 to an ordinal number", function() {
  expect(getOrdinalNumber(1)).toEqual("1st");
});

โœ๏ธ Writing an assertion

Learning Objectives

We now have 2 new concepts: booleans and comparisons. From these concepts, we can now write an assertion about the function formatAs12HourClock.

๐Ÿ”‘ An assertion is a check that our code behaves in a particular way: this check can either succeed or fail.

So far we’ve used the log function console.log. We can write assertions using another function: console.assert. The documentation says that console.assert writes an error message to the console if the assertion is false. If the assertion is true, nothing happens. As 10 + 32 === 42 evaluates to true, no message will be written to the console.

const calculation = 10 + 32;
const result = 42;
console.assert(calculation === result);

๐ŸŽฎ Work through the exercises

Experiment with assertions

const calculation = 10 + 32;
const result = 42;
console.assert(calculation === result);

Change the value assigned to the result variable to make the assertion fail. Check the output you get in the console.

๐Ÿ‘‰๐Ÿฟ Keep Going

Let’s look at an example using formatAs12HourClock:

function formatAs12HourClock() {}
console.assert(formatAs12HourClock("08:00") === "08:00 am");

Predict and explain if the assertion will succeed or fail. Pay particular attention to the return value of formatAs12HourClock.

Clarity with arguments

It would be useful to have more information as to why this assertion failed. We can pass an additional argument to console.assert:

function formatAs12HourClock() {}

console.assert(
  formatAs12HourClock("08:00") === "08:00 am",
  `current output: ${formatAs12HourClock("08:00")}, target output: 08:00 am`
);

Let’s break down these arguments to make sense of what’s going on:

  1. first argument - formatAs12HourClock("08:00") === "08:00 am" - the condition we’re checking
  2. second argument - `current output: ${formatAs12HourClock("08:00")}, target output: 08:00 am` - a message string that will be logged to the console if the condition is false.

๐Ÿงน Refactor

We can tidy up the assertion even further. As weโ€™re reusing the same expressions, we can store their result in variables with meaningful names so we can reuse them:

1
2
3
4
5
6
7
8
function formatAs12HourClock() {}

const currentOutput = formatAs12HourClock("08:00");
const targetOutput = "08:00 am";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

Execute this code; we now get a log in the console:

Assertion failed: current output: undefined, target output: 08:00 am

๐Ÿงฐ Implementing the functionality

On line 3, the function is being passed a single argument "08:00". But our function ignores it: it doesn’t declare any parameters. We can parameterise the function and label the input as time:

function formatAs12HourClock(time) {}

According to our assertion, when we call our function with an input of "08:00" we need to create an output of "08:00 am". If we add "am" to the time, we’ll get the target output. We can update our function with a template literal, set the return value and then re-run our code including our assertion to check the function is returning the correct value.

๐Ÿ““ We can and should continually check our assertions to see if our functionโ€™s current output meets our target output.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function formatAs12HourClock(time) {
  return `${time} am`;
}

const currentOutput = formatAs12HourClock("08:00");
const targetOutput = "08:00 am";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

โœ… Nothing is printed to the console, so this assertion is passing ๐Ÿ˜Ž

Try yourself

Create a javascript file on your local machine and execute the code above. Double check you are seeing the same output in your terminal.

๐Ÿ’ผ Checking different cases

So far weโ€™ve only created assertions that check the functionโ€™s behaviour for times between midnight and midday. In these cases, there is a pattern: take the current time and add " am" to the end.

But this isn’t the pattern we need to follow for all times. To make sure our function works for all times, we need to write more assertions.

We need to assert that the function behaves correctly when the time is later than midday.

Before we think about any code, we should think about our problem. Separating problem and code lets us focus better. First we can focus on the data. Then we can focus on the code.

First, let’s think of an example time in 24 hour clock - we’ll pick 23:00.

Next, let’s work out what we expect our example time to be in 12 hour clock: 11:00 pm.

Now that we’ve thought about the problem, we can write the code. Let’s create an assertion for our function when passed an input of "23:00":

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function formatAs12HourClock(time) {
  return `${time} am`;
}

const currentOutput = formatAs12HourClock("08:00");
const targetOutput = "08:00 am";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

const currentOutput = formatAs12HourClock("23:00");
const targetOutput = "11:00 pm";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

Save this code to a file. Before you run it in Node, write down what you think will happen. Then run it with Node - compare what you saw with what you predicted.

โŒ Errors

Learning Objectives

๐Ÿ—ฃ๏ธ Recall: A programming language is a set of rules for writing computer instructions.

So we need to understand what happens when we break those rules.

Let’s take an example:

1
2
3
const firstName = "Francesco;
const age = 33;
const nationality = "Italian";

On line 1, we have a variable declaration, but the string has a missing " We’re not obeying the syntactic rules for JavaScript: the rules for writing expressions, statements and other parts of the language.

When we execute the code above, we get this:

const firstName = "Francesco;
                  ^^^^^^^^^^^

Uncaught SyntaxError: Invalid or unexpected token

We get a SyntaxError message. This error message is telling us that we’ve broken the rules of the language.

Predict, Explain

Each block of code in this activity is broken. Before you run each block of code:

  1. Predict the error.
  2. Explain why the error happened.
const volunteer = "Shadi";
const volunteer = "Abdi";
const volunteer = "Shadi";
volunteer = "Hinde";
console.log(Math.round(10.3);

Saving return values

We can store the return value of a function in a variable. Function calls are also expressions. This means their value can also be stored in variables, just like with operations on numbers or strings.

Suppose we have a file arithmetic.js containing this code:

const result = Math.round(10.3);

When this program is executed, it creates a variable called result and assigns to it the return value of the function, in this case the rounded number.

So result will have a value of 10.

๐Ÿ”ญ Logging and returning

Most functions return values we can use in our program.

Math.round takes a single input, does a calculation and then returns a value that we can use when our program is running.

Some functions don’t produce useful return values in our running program; but they can still cause effects.

Predict, Run, Observe

const result = console.log("hello world");
  1. Predict what result will evaluate to when the code above runs.
  2. Execute this line in the Node REPL.
  3. Evaluate the value of the result variable to observe what happens.

When this program runs, the variable result will evaluate to undefined. undefined is a data type in JavaScript which usually means no value has been assigned. Unlike the number data type, which contains many possible values (1, 2, 10.3, etc), the undefined data type has exactly one value, undefined.

This can feel confusing as console.log is a function with a set of instructions. console.log does have an effect: it logs values to the console. However, console.log doesn’t produce an output that we can use inside the rest of our running program.

๐Ÿ’ก tip

Key fact: console.log is used to print values to the terminal. It doesnโ€™t produce an output in the running program.

โ“ Conditionally executing code

Learning Objectives

Recall: JavaScript programs are built up from sequences of declarations and statements.

In programming, we can use an if statement to execute some code when a given condition is true. In JavaScript, we can write an if statement as follows:

if (condition) {
  // do some code in here
}

The if statement consists of:

  1. if keyword: this is the start of the if statement
  2. condition: condition is an expression that evaluates to true or false. The condition must be in a pair of parentheses: ()
  3. {}: a block statement: any code we want to execute if the condition is true goes inside the curly braces here

We can represent this with a diagram too:

flowchart LR IC{condition} IB[Execute code in body] EXIT([End of if statement]) IC --> |true| IB IC -.-> |false| EXIT IB --> EXIT

๐Ÿ•น๏ธ Playing computer

function checkDivisibility(a, b) {
  if (a % b === 0) {
    return `${a} is divisible by ${b}`;
  }

  return `${a} is not divisible by ${b}`;
}

console.log(checkDivisibility(10, 2));
console.log(checkDivisibility(50, 3));

Play computer with the example above, step through each line in the code and work out happens each time the function is called.

  • What is the return value each time the function is called?
  • What value is assigned to the parameters a and b each time the function is called?

If you’ve not encountered any syntax before, remember to look it up in some documentation.

๐Ÿ•น๏ธ Playing computer

function getCountryCode(phoneNumber) {
  if (phoneNumber.startsWith("+44")) {
    return "UK";
  }
}

getCountryCode("+447831620328");
getCountryCode("+989871783972");

Play computer with the example above, step through each line in the code and work out happens each time the function is called.

  • What is the return value each time the function is called?
  • What value is assigned to the phoneNumber parameter each time the function is called?
  • Try describing the purpose of this function - what should it do?

โžก๏ธ Arrow functions

Learning Objectives

As we write more code, we are going to write lots and lots of anonymous functions ๐Ÿงถ ๐Ÿงถ anonymous functions An anonymous function is a function which is not bound to a name in the scope where it is defined. .

JavaScript has even shorter ways of writing an anonymous function. These four functions all do the same thing:

function convertToPercentage(decimalNumber) {
  return `${decimalNumber * 100}%`;
}
// We can skip the name of the function if we don't need it to have a name.
function (decimalNumber) {
  return `${decimalNumber * 100}%`;
}
// We can also skip the keyword 'function'.
// If we do this, we need an arrow between our parameters and the function body.
(decimalNumber) => {
  return `${decimalNumber * 100}%`;
}
// If our function just returns a single value,
// without needing any other statements in our function,
// we can even skip the return keyword.
(decimalNumber) => `${decimalNumber * 100}%`;

This can make it easier and quicker to write functions. It also reduces the number of things we need to read in a function.

Applying all of these techniques, we can rewrite our Jest test with fewer words:

test("works for any number ending in 1", () => {
  expect(getOrdinalNumber(1)).toBe("1st");
  expect(getOrdinalNumber(11)).toBe("11th");
  expect(getOrdinalNumber(21)).toBe("21st");
});

It doesn’t matter whether you use arrow functions or use the function keyword - they work the same.

Not all arrow functions are anonymous - you can assign them to a variable too:

const convertToPercentage = (decimalNumber) => `${decimalNumber * 100}%`;

Anonymous vs named refers to whether the function is bound to a name, not whether it was defined with the function keyword or an =>.

๐Ÿƒ Installing Jest

Learning Objectives

Jest is a package used to help us to write and run test cases in JavaScript. Our next step will be to figure out how to install the Jest package on our machine, so that we can use it in our project.

We can find out more about the Jest framework from the documentation online.

In the Getting started section of the documentation, Jest gives us the following command:

npm install jest --save-dev

Let’s break down the different parts of this command.

  • npm - npm is the package management tool we are using, so we need to run it.

  • install - npm has a subcommand called install. We use it to download a package from the npm registry onto our machine and install it.

  • jest - this is the name of the package we want to install on our machine.

  • --save-dev - this means the package is needed for development but not needed in production. Our ordinal app doesn’t need jest to run, but we need it to help us develop it.

So overall we can think of this command as saying: “Please go to the npm database, find the Jest package and install it on my local machine”

Let’s execute this command in the same directory as the package.json. To double check we’re in the correct directory, we can run pwd:

$ pwd
.../The Docs/ordinal-testing-example

pwd is telling us we’re in the ordinal-testing-example directory.

We need to double check the package.json is also there too.

$ ls
package.json

Now we can execute the command

npm install --save-dev jest

Our project structure will now look as follows:

ordinal-testing-example
โ”œโ”€โ”€ node_modules
โ”œโ”€โ”€ package-lock.json
โ””โ”€โ”€ package.json

1 directory, 3 files

After running the command, we now have a directory called node_modules in our project too.

The node_modules directory contains all the code from the dependencies ๐Ÿงถ ๐Ÿงถ dependencies A dependency is a package that your project depends upon. we installed in our project. You won’t need to look inside the node_modules directory - you just need to know it contains the code for Jest and any other dependencies we install in our project.

Running the npm command also updated our package.json file for us:

{
  "name": "week-4-test-example",
  "description": "An example application showing how to write tests using the jest framework",
  "devDependencies": {
    "jest": "^29.5.0"
  }
}

We’ve now got some additional information inside the package.json:

"devDependencies": {
  "jest":  "^29.5.0"
}

๐Ÿ•น๏ธ Follow along

Install Jest on your local machine. Double check you’ve got the correct files and folders written to your local machine.

๐ŸŽ’ Starting a project

Learning Objectives

Let’s start a brand new project in a directory called ordinal-testing-example and create a file called package.json in our project.

  1. Open your terminal and ensure you’re inside the <span class="c-our-name">The Docs</span> directory you created earlier in the course.
  2. Make a new directory on your local machine called ordinal-testing-example.
  3. Change directory into ordinal-testing-example and double-check your current working directory.
% pwd
.../<span class="c-our-name">The Docs</span>/ordinal-testing-example

๐Ÿ‘‰๐Ÿฝ Now create a package.json file

๐Ÿ’ก Package

A package.json stores useful information about our project, like the name, description, and version. It is written in the JSON format.
  1. Create a package.json in ordinal-testing-example.
  2. Make sure it contains a name and description.

๐Ÿ‘‰๐Ÿฝ Need help? Follow step by step instructions

  1. Create a package.json file in your project directory:
touch package.json
  1. Add a name to it like this:
{
  "name": "ordinal-testing-example"
}
  1. Add a description:
{
  "name": "ordinal-testing-example",
  "description": "An example application showing how to write tests using the jest framework"
}

We can continue adding more information about our project as the project grows. For now, double-check we only have a package.json in our project:

% ls
package.json

๐ŸŽฎ Playing computer

Learning Objectives

To understand how convertToPercentage works we must build a mental model of how the computer executes our code. To build this model, we use a method called playing computer ๐Ÿงถ ๐Ÿงถ playing computer .Playing computer means simulating how the computer executes our code. We “step through” the code, line by line, and work out what the computer does when it follows each instruction.

We will use an interactive code visualiser to play computer.

๐Ÿ‘ฃ Step through

In a JavaScript program, each line is an instruction that will have some effect. For example, a line of code with a variable declaration means “store a new variable with this value in memory”. In the interactive widget, arrows are used to show which line just executed and which line is next to be executed.

Click next to see what happens when the computer executes the following program. Pay particular attention to what happens when the function convertToPercentage is called.

๐Ÿ–ผ๏ธ Global frame

As we step through the program, we keep track of two things: memory and the line that is being currently executed. We keep track of this information using a frame ๐Ÿงถ ๐Ÿงถ frame Think of a frame as the context in which some code gets executed. We use frames to keep track of memory and the line of code that is being currently executed. .

The global frame is always the first frame that gets created when our program starts executing. It is like the starting point for our program, the place where code gets executed first. When we run the code above, decimalNumber and convertToPercentage are both stored in the global frame.

๐Ÿ–ผ๏ธ ย Local frame

๐Ÿ’ก recall

A function call is an instruction to run the code inside a function

Whenever we call a function a new frame is created for executing the code inside that function. In the example above, we call the function convertToPercentage on line 7 and then a new frame is created for convertToPercentage. Inside the convertToPercentage frame, the computer executes the instructions inside convertToPercentage, storing new variables in memory and keeping track of the current line that is being executed.

๐Ÿ’ฌ Declarations and statements

Learning Objectives

A variable declaration is an example of a declaration ๐Ÿงถ ๐Ÿงถ declaration A declaration is an instruction that binds an identifier to a value . It has the effect of creating a variable.

let versionNumber = "2.0.0"; // declaration
versionNumber = "2.0.1"; // statement

The code above has one variable declaration and one statement.

  1. The first line is a declaration - creating a variable versionNumber with a value of "2.0.0"
  2. The second line is a statement - reassignment ๐Ÿงถ ๐Ÿงถ reassignment Reassignment means changing the value associated with an identifier. of the value of versionNumber to "2.0.1"

In this example, we’ve used the let keyword to declare a new variable. The let keyword allows us to create new variables like the const keyword.

However, we can reassign the value of a variable that is declared with the let keyword.

If we’d used const to declare versionNumber, we wouldn’t be allowed to reassign it a new value.

In JavaScript, we build up programs by combining declarations and statements.

๐Ÿ’ผ First test case

Learning Objectives

๐ŸŽฏ Goal: Write a test for the case below, using Jest:

Case 1 ๐Ÿ’ผ

Our first case is that the ordinal number for 1 should equal "1st".

We can create a file called get-ordinal-number.test.js and write our first test there. We can use documentation to work out how to write our first test using Jest.

get-ordinal-number.test.js:

test("converts 1 to an ordinal number", function() {});

Let’s break down this syntax.

The test function is part of the Jest API, a function we use to perform a particular task. In particular, we’re using test to create a test case. Before, we could use Math.round and console.log because Math and console are provided for us by Node.

test isn’t provided by Node, but when we ask Jest to run our tests, it will make sure the test function exists and that our code can use it.

Let’s break down the arguments we’re passing to test:

  • 1st argument: "converts 1 to an ordinal number", a string which describes the behaviour we’re testing for
  • 2nd argument: function() {}, we will write some assertions in this function() {} to check the behaviour

โš–๏ธ Creating assertions

We need to write an assertion inside the body of function() {} inside get-ordinal-number.test.js

get-ordinal-number.test.js:

test("converts 1 to an ordinal number", function() {});

๐Ÿ’ก Recall

The assertion is the part of the test code that checks if a particular thing is true or not.

In this example, we want to check that the following is true:

We expect getOrdinalNumber(1) to be "1st"

An assertion in Jest looks like this:

expect(currentOutput).toEqual(targetOutput);

The function toEqual is used to check that the current output of getOrdinalNumber(1) and the target output of "1st" are equal to each other.

toEqual is just one example of a function called a matcher. A matcher is a function we use to compare values in Jest.

So the whole test looks like this:

test("converts 1 to an ordinal number", function() {
  expect(getOrdinalNumber(1)).toEqual("1st");
});

๐Ÿ‘Ÿ Running tests

We can try running the file get-ordinal-number.test.js with node in the following way:

node get-ordinal-number.test.js

but we get an error:

ReferenceError: test is not defined

Googling “ReferenceError JavaScript”, MDN tells us this is because we’re referring to a variable that doesn’t exist. This is because test isnโ€™t defined anywhere in the file.

We need to execute this file so that the Jest API is available in our file. We can do this by running the test file using Jest: we do this using an npm script.

The “scripts” section of the package.json is where we can write useful commands we’ll use in our project. We can add a “scripts” section to the package.json so that it reads as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "name": "week-4-test-example",
  "description": "An example application showing how to write tests using the jest framework",
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "jest": "^29.5.0"
  }
}

Finally, we’ll need to run our tests. Now we can run the command npm test.

When we execute the command, npm test, we will run npm, and npm will look inside the “scripts” section of the package.json and look up the command for “test” - in this case, “jest”. npm will then run “jest”.

We can’t ourselves just run jest on the command line, because it isn’t installed in a place our terminal knows about. But when npm runs a script, it will make sure all dependencies installed for the project are available.

๐Ÿ“ Running scripts

Learning Objectives

So far weโ€™ve seen how expressions can be evaluated using the Node REPL. The Node REPL is useful for evaluating expressions quickly.

But usually, our programs have many instructions, and we want to keep and re-run them instead of typing them out each time. So we save our instructions in files. Node can also execute instructions written in a file.

We use the node command to run a JavaScript file in the terminal. A JavaScript file ends with .js - this is the “file extension”.

Letโ€™s suppose we have a file age.js. We run the command node age.js. This terminal command is an instruction to execute the program written inside age.js. Our program has five lines. So the computer will read and execute the program one line at a time:

const yearOfBirth = 1990; // declaration
const currentYear = 2023; // declaration

currentYear++; // statement
`I am ${currentYear - yearOfBirth} years old`; // statement

Activity

Check you can run a file with Node:

  1. In your terminal, create a new file called example.js.
  2. Try writing a few lines in the file.
  3. Get Node to run this file. (Don’t use the REPL now - you should run a command to execute the whole file.)

Once the computer executes these statements, the execution of the program is complete. But weโ€™re left with a problem. With the REPL, when the user inputs an expression statement or declaration, the computer reads and executes the line and immediately prints feedback to the terminal. With a file, the computer will execute each line sequentially until completion without printing the values of each expression it evaluates.

So this new problem can be expressed as a question:

โ“ Problem

“How can we check what the values evaluated to in our program during execution?”

๐Ÿ“ค Returning from a function

Learning Objectives

We need a way to access the percentage string that is created inside convertToPercentage. To access values created inside functions, we write return statements ๐Ÿงถ ๐Ÿงถ return statements We write a return statement to specify a function’s return value. If your function call is like a question, the return value is the answer. It’s what comes back. .

We can add a return statement to convertToPercentage like this:

1
2
3
4
5
6
const decimalNumber = 0.5;

function convertToPercentage() {
  const percentage = `${decimalNumber * 100}%`;
  return percentage;
}

If we want, we could also remove the variable percentage, since we can return the value of the expression directly:

1
2
3
4
5
const decimalNumber = 0.5;

function convertToPercentage() {
  return `${decimalNumber * 100}%`;
}

๐Ÿ”Ž Checking the output

We can store a function’s return value in a variable.

const result = Math.round(10.3);
console.log(result); // logs 10 to the console

We call Math.round which takes the input 10.3 and then returns the rounded number 10. So result stores a value of 10.

Math.round is a function implemented by other developers and convertToPercentage is a function we’re implementing, but calling convertToPercentage is just like calling Math.round.

Now we want to call the function convertToPercentage and store the return value in a variable.

We can store the return value in a variable in exactly the same way:

1
2
3
4
5
6
7
8
const decimalNumber = 0.5;

function convertToPercentage() {
  const percentage = `${decimalNumber * 100}%`;
  return percentage;
}

const result = convertToPercentage(0.5);

Log out the value of result to the console using console.log.

1
2
3
4
5
6
7
8
9
const decimalNumber = 0.5;

function convertToPercentage() {
  const percentage = `${decimalNumber * 100}%`;
  return percentage;
}

const result = convertToPercentage(0.5);
console.log(result);

This will now print the following when run:

50%

๐Ÿ“ฆ Using packages

Learning Objectives

When writing software, we continually make use of software written by other developers. We can call these packages ๐Ÿงถ ๐Ÿงถ packages A package is some code which is grouped together to provide some functionality.

We use packages so that we don’t have to solve every problem ourselves. Other people have often solved some things we need to do really well. Using other people’s solutions to parts of a problem means we can focus our time and effort on what’s special about our problem.

Imagine we wanted to work out what the time is in a user’s city. Instead of writing code to work out the time for every city’s time zone (and when they change!), we can use a package some “city time” experts have written, and which they keep up to date.

Different programming languages give developers different ways of accessing packages for use in their code. We will use npm ๐Ÿงถ ๐Ÿงถ npm Node Package Manager, or npm, downloads and manages useful packages of code from the npm registry.

๐Ÿ”„ Reusing variable names

Learning Objectives

We saw this error:

SyntaxError: Identifier 'currentOutput' has already been declared

Now that we understand it, let’s fix it.

We’re not allowed to declare a new variable with the same name as an old one. Both lines 5 and 12 here try to declare a new variable named currentOutput:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function formatAs12HourClock(time) {
  return `${time} am`;
}

const currentOutput = formatAs12HourClock("08:00");
const targetOutput = "08:00 am";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

const currentOutput = formatAs12HourClock("23:00");
const targetOutput = "11:00 pm";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

remember

Make sure you understand the error message before you try to fix the code.

If you don’t understand what’s wrong, you probably won’t be able to fix it!

Fixing the error

We want to do multiple assertions. And we’re using variables in our assertions. But we’re not allowed to use the same name twice. The simplest way we can fix this problem is by changing the name of the second variable. Remember to also change where we use the variable, not just where we declare it!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function formatAs12HourClock(time) {
  return `${time} am`;
}

const currentOutput = formatAs12HourClock("08:00");
const targetOutput = "08:00 am";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

const currentOutput2 = formatAs12HourClock("23:00");
const targetOutput = "11:00 pm";
console.assert(
  currentOutput2 === targetOutput,
  `current output: ${currentOutput2}, target output: ${targetOutput}`
);

Trying again

After making this change, let’s try running our code again. We get this output:

% node clock-example.js
/Users/dwh/CYF/clock-example.js:13
const targetOutput = "11:00 pm";
      ^

SyntaxError: Identifier 'targetOutput' has already been declared
    at wrapSafe (node:internal/modules/cjs/loader:1383:18)
    at Module._compile (node:internal/modules/cjs/loader:1412:20)
    at Module._extensions..js (node:internal/modules/cjs/loader:1551:10)
    at Module.load (node:internal/modules/cjs/loader:1282:32)
    at Module._load (node:internal/modules/cjs/loader:1098:12)
    at TracingChannel.traceSync (node:diagnostics_channel:315:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:215:24)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:158:5)
    at node:internal/main/run_main_module:30:49

Node.js v22.4.1

Fortunately, we’ve seen this kind of error before. It’s exactly the same as the last one, but about the targetOutput variable on line 13, instead of the currentOutput variable on line 12.

exercise

Fix this error, and try running your code again.

Now the second assertion fails with the following message:

% node clock-example.js
Assertion failed: current output: 23:00 am, target output: 11:00 pm

We expect this. Our function just adds " am" to the end of the time, which only works for times in the morning. We have proven our code isn’t complete yet. Next we can fix it so that this test passes.

Exercise 1

โœ๏ธ Write another assertion that checks formatAs12HourClock returns the target output when passed another time input in the afternoon.

๐Ÿ”Ž Interpreting errors

Learning Objectives

An error is thrown

When we run the file with Node, we get an error in the console:

% node clock-example.js
/Users/dwh/CYF/clock-example.js:12
const currentOutput = formatAs12HourClock("23:00");
      ^

SyntaxError: Identifier 'currentOutput' has already been declared
    at wrapSafe (node:internal/modules/cjs/loader:1383:18)
    at Module._compile (node:internal/modules/cjs/loader:1412:20)
    at Module._extensions..js (node:internal/modules/cjs/loader:1551:10)
    at Module.load (node:internal/modules/cjs/loader:1282:32)
    at Module._load (node:internal/modules/cjs/loader:1098:12)
    at TracingChannel.traceSync (node:diagnostics_channel:315:14)
    at wrapModuleLoad (node:internal/modules/cjs/loader:215:24)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:158:5)
    at node:internal/main/run_main_module:30:49

Node.js v22.4.1

When an error is thrown, the program stops and an error report is sent to the user.

As programmers, we will see a lot of errors. It’s useful for us to be able to read them.

Interpreting the output

Each line of output here tells us something useful.

The first line is:

/Users/dwh/CYF/clock-example.js:12

Your output was probably different. But it will have the same parts. Some text, then a colon (:), then a number.

Exercise

  1. Work out what the parts of this line mean.

  2. Why are they different on my computer than yours?

  3. How can we use both pieces of information?

Often, looking at one line of a file is enough to understand what’s wrong. So the message also shows us a copy of the line that caused the problem:

const currentOutput = formatAs12HourClock("23:00");

Then the output tells us the error message:

SyntaxError: Identifier 'currentOutput' has already been declared

We may not know what this means yet, but it’s something we can learn about.

Exercise

Write down three ways you could find out what this means.

Each line starting with “at” is showing us a “Stack trace”. We’ll skip over this for now. In the future we’ll see how it can be useful to us.

Finally, we have this line:

Node.js v22.4.1

Exercise

What does this line mean? Why might it be useful to know this information?

Add your answer to your spaced repetition calendar. Your understanding of this will grow over time. Answer the question again in the future, and compare it to your previous answer.

๐Ÿ”Ž Testing a sub-goal

Learning Objectives

Earlier we defined a sub-goal to find a value for the hours from the time input. We’ve found that Number(time.slice(0,2)) is an expression that evaluates to the hours from time. So we can write an if statement using this expression:

if (Number(time.slice(0, 2)) > 12) {
}

If the time is "23:00" then the expression Number(time.slice(0, 2)) > 12 will evaluate to true and the body of the if statement will be executed.

This if statement is implementing the following part of the diagram from earlier:

flowchart TD A{Check: Are the hours greater than 12?}

Before we worry about how we handle times in the afternoon, we can check that we’ve solved this sub-goal.

We can check that we are correctly identifying times in the afternoon by adding in our if statement (which we think is correct), with a placeholder body:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function formatAs12HourClock(time) {
  if (Number(time.slice(0, 2)) > 12) {
    return "Don't know how to handle times in the afternoon yet";
  }
  return `${time} am`;
}

const currentOutput = formatAs12HourClock("08:00");
const targetOutput = "08:00 am";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

const currentOutput2 = formatAs12HourClock("23:00");
const targetOutput2 = "11:00 pm";
console.assert(
  currentOutput2 === targetOutput2,
  `current output: ${currentOutput2}, target output: ${targetOutput2}`
);

If we run our code, we expect the current output of the 23:00 test-case to have changed. It won’t be correct - the assertion will still fail. But if it hasn’t changed, we know our if statement is wrong.

% node clock-example.js
Assertion failed: current output: Don't know how to handle times in the afternoon yet, target output: 11:00 pm

Even though we know the code on line 3 is incorrect, this was a useful step. It allowed us to run our code more often, and check that we’re on the right track.

remember

Run your code as often as you can.

Make small changes and run your code.

Sometimes we write code we know we will delete because it lets us get feedback sooner.

Now we can focus on just one problem: how to handle times after midday (i.e. fixing line 3). We don’t need to worry about both detecting the time and handling it.

If the output of this assert still printed "23:00 am" we would have stopped here and debugged that. Again, we could focus on just one problem.

๐Ÿ”ฌ Identifying missing tests

Learning Objectives

We started off writing one test for our code - checking that it correctly handled the input 08:00. We wrote an implementation that passed all our (1) tests!

Then we realised there was a bug - it didn’t handle times after midday correctly. So we wrote another test - for the input 23:00. We saw our implementation failed that test. And we fixed it. And we had an implementation that passed all our (2) tests!

When will we be happy that our implementation works for all possible inputs? When do we have enough tests?

Groups of input

One way to approach this is to think about what groups of input our problem may have.

We’ve already identified two examples of groups of input to the problem of converting 24 hour clocks to 12 hour clocks: Times before midday and times after midday.

One way to find extra cases to consider (and extra tests to write) is to try to think of different groups of input.

For example, some times are exactly on the hour (end in :00) and other times have a non-zero number of minutes.

exercise

Set a timer for 5 minutes. Write down as many groups of input to this problem as you can. Write an example assertion for each one.

If you find any bugs in the implementation, go fix them!

Edge cases

Another way to consider this question is to think about what edge cases there are in the problem.

Definition: edge case

An edge case is an unusual value which may need special treatment.

Some examples are: the minimum value, the maximum value, and the boundary between two groups of input.

Some example edge cases for this problem are:
00:00
The minimum time, which is 12:00 am in 12 hour clock.
This is also the only hour that is bigger in 12 hour clock than 24 hour clock.
24:00
The maximum time.
12:00
Where time changes from am to pm. The edge between morning times and afternoon times.

Often these edge cases are where bugs happen.

exercise

Set a timer for 5 minutes. Write down as many edge cases of input to this problem as you can. Write an example assertion for each one.

If you find any bugs in the implementation, go fix them!

๐Ÿ”ญ Scope

Learning Objectives

The function convertToPercentage will only be useful if we can access the percentage string that it creates. Otherwise, we won’t be able to use the result of convertToPercentage in other parts of our code. We can try accessing the percentage variable outside the function body like this:

1
2
3
4
5
6
7
8
const decimalNumber = 0.5;

function convertToPercentage() {
  const percentage = `${decimalNumber * 100}%`;
}

convertToPercentage(0.5);
console.log(percentage);

However if we run the code above, we get an error:

ReferenceError: percentage is not defined

We get an error because of scope ๐Ÿงถ ๐Ÿงถ scope Scope means where variables are and what you can access. . When we define convertToPercentage we also define a local scope - the region of code enclosed inside convertToPercentage’s function body. This region is convertToPercentage’s local scope. This means any variables we declare inside convertToPercentage’s local scope can only be accessed within this region. If we attempt to reference a variable outside the scope where it was declared, then get a ReferenceError.

๐Ÿ•› 12 vs 24 hour clock

Learning Objectives

We usually write the time in one of two ways: the analogue 12 hour clock or the digital 24 hour clock. The 12 hour clock counts up to 12: it resets at midday. The 24 hour clock counts up to 24: it resets at midnight.

Conversion from 24 hour to 12 hour ๐Ÿงถ ๐Ÿงถ Conversion from 24 hour to 12 hour
2๏ธโƒฃ4๏ธโƒฃ hour time๐Ÿ•› 12 hour time
09:0009:00 am
10:0010:00 am
11:3011:30 am
12:0012:00 pm
13:431:43 pm
14:002:00 pm

We use the notation “HH:MM”. HH is our stand-in for the hours value. MM is our stand-in for the minutes value.

๐Ÿงฉ Stating the problem

Let’s pose a problem: given any time in 24 hour clock, we want to format it as a 12 hour clock time. To achieve this goal, we’re going to implement a function formatAs12HourClock.

Given a time in 24 hour clock
When we call formatAs12HourClock
Then we get back a string representing the same time in 12 hour clock.

๐Ÿงช Our tests:

I expect formatAs12HourClock("09:00") to be "09:00 am"
I expect formatAs12HourClock("14:19") to be "2:19 pm"

Interacting with computers

Learning Objectives

Modern computers are complicated: it would be too difficult and time-consuming to list all the components that make up a modern computer. So to build our mental model, we will use this simple definition of a computer:

A computer is a device used to store and perform operations on data.

๐ŸŽ›๏ธ Jest's Application Programming Interface

Learning Objectives

With Jest installed, we need to figure out how to use the Jest framework to write tests. This means we need to look at APIs ๐Ÿงถ ๐Ÿงถ APIs An API is a boundary between a programmer and an application, enabling a programmer to use an applicationโ€™s functionality without being concerned with how the application was built. again.

API stands for

  • Application
  • Programming
  • Interface.

We’ve encountered the word interface already.

But we can break down each word in this acronym to understand it altogether.

  • An application is a program or piece of software designed to serve some purpose.

  • Programming refers to the process of writing code or software.

  • An ๐Ÿ•น๏ธinterface is a shared boundary between two or more systems.

We’ve encountered several functions like console.log and Math.round already. console.log and Math.round are APIs.

console.log is actually implemented in a different language (C++), but that doesn’t matter - its functionality is exposed to us when we write JavaScript, and we don’t need to care how it’s actually implemented or how it works.

Jest provides an API so we can write tests. So we have to find out about the Jest API to start writing tests with Jest.

Try and list other examples of APIs you’ve used since the start of the course. Start a thread in Slack to discuss with your class.

๐Ÿ—๏ธ Assembling the parts

Learning Objectives

Earlier we defined a sub-goal to find a value for the hours from the time input. We’ve found that Number(time.slice(0,2)) is an expression that evaluates to the hours from time. So we can write an if statement using this expression:

if (Number(time.slice(0, 2)) > 12) {
}

If the time is "23:00" then the expression Number(time.slice(0, 2)) > 12 will evaluate to true and the body of the if statement will be executed.

This if statement is implementing the following part of the diagram from earlier:

flowchart TD A{Check: Are the hours greater than 12?}

Now we can format the string using our approach from earlier: weโ€™ll need to append "pm" to the string expression and subtract 12 from the hours. So we get the following:

if (Number(time.slice(0, 2)) > 12) {
  return `${time.slice(0, 2) - 12}:00 pm`;
}

The return statement above implements the following steps we set out earlier:

flowchart LR D[Step 4: subtract 12 from the hours] D --> E[Step 5: add 'pm' to the rest of the time] E --> F[Step 6: return the new time]

Now we can re-run our assertions from earlier to check our function behaves as target.

๐Ÿ—๏ธ Sub-goal #2

Learning Objectives

Now we can think about what we do when we’ve identified a time is after midday.

Earlier, we observed what to do when the time goes beyond midday: subtract 12 from the hours time to get the new hours for the 12 hour clock time.

Before writing code, we can define our approach in steps:

Starting with an input like "23:00":

flowchart LR A[extract the hours, '23', from the time '23:00'] --> B[convert '23' to a number, 23] B --> C{check: Are the hours greater than 12?} C -- false --> D[add am to time string] C -- true --> E[subtract 12 from the hours, to get 11] subgraph "Time after midday" E --> F[add 'pm' to the rest of the time, to get '11:00 pm'] F --> G[return the new time] end

Now we can format the string using our approach from earlier: weโ€™ll need to append "pm" to the string expression and subtract 12 from the hours. So we get the following:

if (Number(time.slice(0, 2)) > 12) {
  return `${Number(time.slice(0, 2)) - 12}:00 pm`;
}

The return statement above implements the following steps we set out earlier:

flowchart LR D[subtract 12 from the hours] D --> E[add 'pm' to the rest of the time] E --> F[return the new time]

Now we can re-run our assertions from earlier to check our function behaves as target.

๐Ÿท๏ธ Anonymous functions

Learning Objectives

We have seen functions written like this:

function convertToPercentage(decimalNumber) {
  return `${decimalNumber * 100}%`;
}

In our Jest test, we wrote a function differently:

function() {
  expect(getOrdinalNumber(1)).toBe("1st");
  expect(getOrdinalNumber(11)).toBe("11th");
  expect(getOrdinalNumber(21)).toBe("21st");
}

๐Ÿ‘€ Spot the difference

Stop and identify the difference in syntax between these two function definitions.

We didn’t give a name to the function in our Jest test.

This is ok, because we don’t need it to have a name. We don’t call the function by name. We passed the function as an argument ๐Ÿงถ ๐Ÿงถ argument Arguments are values given to a function which can be different every time we call the function. to the test function. The test function takes the function as a parameter ๐Ÿงถ ๐Ÿงถ parameter A parameter is a named variable inside a function. The variable’s value is given by the caller, when the function is called. . And function parameters get their own names in the scope ๐Ÿงถ ๐Ÿงถ scope Scope is where a variable can be accessed from. When we define function, its parameters are only available inside the function. of the function.

We can imagine the test function is defined like this:

function test(name, testFunction) {
  // Call the passed test function
  testFunction();
}

Inside test our function is labelled with the name testFunction. It would be labelled this whatever we named it before. Even if we didn’t label it ourselves at all, it is still labelled with the name testFunction inside test.

Because it doesn’t matter what we named the function (because we never call it by name), we didn’t give it a name.

Otherwise, these two functions act the same. The only difference between them is whether we created a variable name for the function in the scope where we defined it.

๐Ÿท๏ธ Parameterising a function

Learning Objectives

At the moment, decimalNumber is a variable in the global scope of our program:

const decimalNumber = 0.5; // defined in the global scope of our program

function convertToPercentage() {
  const percentage = `${decimalNumber * 100}%`;
  return percentage;
}

const output1 = convertToPercentage(0.5);
const output2 = convertToPercentage(0.231);

So long as decimalNumber is always in the global scope, convertToPercentage will always go to the global scope to get the value of decimalNumber.

However, we want convertToPercentage to work for any input we pass to it.

To make a function work for any number, we need to handle inputs. We do this using a parameter ๐Ÿงถ ๐Ÿงถ parameter A parameter is a special kind of variable: its value is defined by the caller. .

decimalNumber is still a variable - but as a parameter we don’t assign decimalNumber a value inside the function’s body. It is a placeholder. When we call the function, we pass an input to the function, and the value of that input is assigned to the decimalNumber parameter when the function is called. This happens automatically.

We can add a parameter decimalNumber to our function:

1
2
3
4
5
6
7
8
function convertToPercentage(decimalNumber) {
  // now decimalNumber is a parameter of convertToPercentage
  const percentage = `${decimalNumber * 100}%`;
  return percentage;
}

const output1 = convertToPercentage(0.5);
const output2 = convertToPercentage(0.231);

In the example above, we’re calling convertToPercentage twice: first with an input of 0.5 and second with an input of 0.231. In JavaScript instead of input we use the word argument ๐Ÿงถ ๐Ÿงถ argument Arguments are inputs given to a function inside (). An argument means an input. .

We’re calling convertToPercentage twice: first with an argument of 0.5 and next with an argument of 0.231.

Think of a function as a box. We put data in and then act on it using the rules in the box; at the end, the box gives us new data back. In programming we say that we pass arguments into a function, the function’s code is executed and we get a return value after the function has finished executing. Here’s a diagram:

flowchart LR A[argument] --> B{function} B --> C[return]

Here’s a diagram of what happens when convertToPercentage is passed a specific argument:

flowchart LR A[0.231] --> B{convertToPercentage} B --> C[23.1%]

In this interactive widget we have defined a parameter decimalNumber in the function declaration inside parentheses after the function name convertToPercentage. In our mental model, a function call means going to convertToPercentage and running the code inside the function.

Use the interactive widget to see what happens when the code above is executed. Pay close attention to what happens inside the convertToPercentage frame.

๐Ÿท๏ธ Saving expressions

Learning Objectives

In programming we often want to reuse our work. Consider the string: "Hello there"

Suppose we want to create different greetings for different people, like: "Hello there, Alicia" "Hello there, Barny"

We can use a variable to store this string and reuse it. How can we create a variable ๐Ÿท๏ธ ๐Ÿท๏ธ variable A variable is a label for a piece of data. We assign a piece of data to a label and then refer back to this label, in place of the data.

We can create a variable in our program by writing a variable declaration ๐Ÿงถ ๐Ÿงถ declaration A declaration is an instruction that binds an identifier to a value. , like this:

const greeting = "Hello there";

Break down the different syntactic elements of this variable declaration:

  • const is a keyword used to indicate we’re creating a variable.
  • greeting is the identifier - it can be used to refer to a variable after it has been declared.
  • = is the assignment operator. It means assign to the label greeting the value of the expression on the right hand side.
  • "Hello there" - this is the expression whose value we’re assigning to the label greeting.

Type this variable declaration into the REPL:

const greeting = "Hello there";

Now refer to the label greeting in the REPL:

`${greeting}, Alicia`

Our greeting variable is stored in memory. We can reuse it to build more expressions:

`${greeting}, Barny`
greeting
Store your string in a variable and reuse it

We just used backticks to create a template literal.

`A template literal places ${expressions} inside strings;

With template literals, we can insert expressions into strings to produce new strings. Any time we want to reference a variable inside a template literal we use a dollar sign $ and a set of curly braces {}. We can put any expression (e.g. a variable name) inside the curly braces. The value that expression evaluates to is then placed inside the string.

When an operation uses an expression, that expression is immediately evaluated, and how it was written is forgotten about. That means that the greetAlicia variable is the same in all three of these cases:

const greetAlicia = "Hello there, Alicia";

๐Ÿ“ string literal

In this example, we don’t use a variable or a template to create a string. Instead we write a string "Hello there, Alicia".

A sequence of characters enclosed in quotation marks is called a string literal. "Hello there, Alicia" is a string literal.

Similarly, 10 is a number literal.

const name = "Alicia";
const greetAlicia = `Hello there, ${name}`;
const greeting = "Hello there";
const name = "Alicia";
const greetAlicia = `${greeting}, ${name}`;

The greetAlicia variable doesn’t remember whether you used variables to make it or not - in all three cases, greetAlicia contains the string "Hello there, Alicia". Once a value is made, it doesn’t matter how it was made.

๐Ÿ•น๏ธ Using an interface

Learning Objectives

We want to use computers without understanding exactly how they are built. Every day we ask machines to do things, and usually we have no idea how these machines work. We could not use modern technology if we had to understand it completely before we could use it; it would take too long! Instead we use interfaces ๐Ÿงถ ๐Ÿงถ interfaces Think of an interface as a gate that allows communication between a user and a machine. The user asks the machine to do things via the interface.

Think about a cash machine (ATM). We go to a hole in the wall with a screen and a keypad. The screen and the keypad are the user interface. We press the buttons and ask the machine to do things - like giving our balance, or withdrawing some money from an account. We don’t need to understand how the information it tells us comes on the screen.

Exercise

Define the user interface for these devices:

  • a calculator
  • a microwave
  • a desktop lamp
  • Facebook
  • Alexa
  • ChatGPT

๐Ÿ–ฅ๏ธ Terminal interface

Learning Objectives

Programmers need interfaces to ask computers to do things. A computer terminal is an interface where programmers can issue commands to a computer. Because users enter text instructions and receive text output, we say that the terminal is a text-based interface.

Interface via the terminal

We can input a command into the prompt and hit enter. The terminal then passes this command to the computer to execute. Find your own terminal and input the ls command:

ls
terminal
The terminal is a window on the computer, prompting users for instructions.

๐Ÿ–Š๏ธ Writing computer instructions

We can issue commands to the computer using the terminal. These commands are instructions that the computer knows how to interpret.

The computer knows ls means “list the files and directories in the current directory”.

During the execution of a computer program, a computer will store and modify data ๐Ÿงถ ๐Ÿงถ data Data is information. Text, images, numbers are all forms of data. The data in an executing program is sometimes called the state. A computer program will modify data with operations ๐Ÿงถ ๐Ÿงถ operations Operations modify or create data, from the current data in the program. Adding numbers, joining words, changing text to ALLCAPS, are all operations.

ls is a shell command. Shell is a programming language we use to interact with the files and folders on our computer. You already know at least two more programming languages. Can you name them?

Definition: programming language

A programming language is a limited set of rules for writing computer instructions.

๐Ÿ–จ๏ธ Logging

Learning Objectives

Printing to the terminal

To look at values when our program runs, we can use a function called console.log.

๐Ÿ’ก console.log

console usually means a text interface like a terminal. A log is a written record of something that happened.

So console.log will record something that happens in our program and print it to a text based interface.

console.log logs the result of expressions while our program is executing. This is very useful for complex programs when we need to check what values expressions evaluate to at specific moments of our program execution.

Let’s see how to use console.log . In a file called example.js, write the name of the function console.log.

console.log;

If we run this file with Node, we won’t be able to see anything in the terminal. As with Math.round we need to use the syntax for calling a function. Add brackets after the function name:

console.log("hello there!");

We should see the string "hello there!" logged out in the terminal.

๐Ÿ–ผ๏ธ Testing frameworks

Learning Objectives

To help us think about the requirements of getOrdinalNumber, let’s consider one case:

๐Ÿ’ผ Case 1

const input = 1;
const currentOutput = getOrdinalNumber(input);
const targetOutput = "1st";

Case 1 states that when getOrdinalNumber is called with an input of 1, it has a target output of “1st”. Our first step is to check that getOrdinalNumber works as we have stated.

We have used console.assert to write assertions to write tests for our code before. console.assert is a useful building block, but it is limited. Now we will write tests using a test framework ๐Ÿงถ ๐Ÿงถ test framework A test framework is a set of tools we can use to build tests efficiently. to check our code is behaving in a particular way.

๐Ÿ”‘ A test is any piece of code that runs an assertion on the code we’re testing

We want our tests to:

  • be easy to write
  • be easy to read
  • give clear feedback on what the current output is
  • give clear feedback on what the target output is
  • allows us to easily write multiple test cases

A test framework will help us build test cases like this.

๐Ÿง‘๐Ÿฝ๐Ÿง‘๐Ÿฟ Dialogue

We can use a short dialogue to think about why we want to use a testing framework:

๐Ÿง‘๐Ÿฝ BรผลŸra
Ali, looks like I need to implement a function.
๐Ÿง‘๐Ÿฟ Ali
Cool. How are you going to check it works?
๐Ÿง‘๐Ÿฝ BรผลŸra
I’m going to use tests to check that the function gives the target output as described in the specification
๐Ÿง‘๐Ÿฟ Ali
Seems wise. How are you going to write a lot of tests efficiently?
๐Ÿง‘๐Ÿฝ BรผลŸra
I’m going to use a testing framework to write test cases quickly. The framework will make sure that the tests give fast, reliable feedback.

๐Ÿ—„๏ธ Classifying data

Learning Objectives

Weโ€™re going to focus on the JavaScript programming language.

A programming language organises data with rules so we understand what we can and cannot do with it. Languages split data up into different categories called data types ๐Ÿงถ ๐Ÿงถ data types A data type is a grouping of data with some particular properties . In JavaScript, we have five data types. We will look first at numbers and strings.

Number data type

10 is an example of the number data type. 3.14 is also part of the number data type; both integers (whole numbers) and non-integers are types of number.

-15 is also part of the number data type. Positive and negative numbers, as well as 0, are all types of number.

String data type

A string is a sequence of characters demarcated by quotes.

"Code Your Future"

๐Ÿงฎ Creating expressions

Think of the numbers 10 and 32. We could ask questions about these numbers, like: What is the sum of 10 and 32?

Another way to say this is what do 10 and 32 add up to? In English we can say this in many ways, but in JavaScript we can say this using numbers and an operator. Just like in mathematics, โ€œthe sum of 10 and 32โ€ can be written as 10 + 32:

10 + 32

In JavaScript, + is an operator ๐Ÿงถ ๐Ÿงถ operator An operator represents an operation, or act. . It’s a symbol. In this example, + represents the operation โ€œmake the sum of the numbersโ€. It symbolises addition.

The combination of symbols 10 + 32 is an expression ๐Ÿงถ ๐Ÿงถ expression An expression is a value or any valid combination of values and symbols that results in a single value. We say that expressions evaluate to a single value. So we say that 10 + 32 evaluates to the value 42.

10 is also an expression. It evaluates to the value 10.

"Code Your Future" and "Code Your " + "Future" are also both expressions - both evaluate to the value "Code Your Future".

๐Ÿ—„๏ธ Generalising further

Learning Objectives

In English, ordinal numbers mostly follow the same pattern.

Numbers ending in 1 will generally have an ordinal number ending in “st”.

Here are some examples of this pattern,

1st, 11th, 21st, 31st, 41st,…

All the numbers ending in 1 will continue to end in "st", with the exception of 11. 11 is slightly different and ends with a "th".

We can now update our test case to check that getOrdinalNumber works for lots of different numbers ending in 1.

get-ordinal-number.test.js:

1
2
3
4
5
6
7
8
9
function getOrdinalNumber() {
  return "1st";
}

test("works for any number ending in 1", function() {
  expect(getOrdinalNumber(1)).toBe("1st");
  expect(getOrdinalNumber(11)).toBe("11th");
  expect(getOrdinalNumber(21)).toBe("21st");
});

We’ve also updated the test description because we’re adding more assertions and checking slightly different functionality.

๐Ÿ”ง Implement

Try implementing getOrdinalNumber so it passes the test case above.

๐Ÿ—„๏ธ Generalising further

Learning Objectives

๐Ÿงฐ Handling outliers

We can now implement functionality for getOrdinalNumber.

Our strategy might be something like this:

flowchart LR A{Check is num 11} -- true --> B[return 11th] A -- false --> C[return num + st]

Most of the time we just need to return the number with “st” on the end.

However, 11 is an outlier: it doesn’t conform to this pattern.

So our current strategy for this test case will be to check if the number is 11 first and do something differently ( return "11th" ): otherwise we return the default value of num with "st" on the end.

Here’s the implementation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
function getOrdinalNumber(num) {
  if (num === 11) {
    return "11th";
  }
  return `${num}st`;
}

test("works for any number ending in 1", function () {
  expect(getOrdinalNumber(1)).toBe("1st");
  expect(getOrdinalNumber(11)).toBe("11th");
  expect(getOrdinalNumber(21)).toBe("21st");
});

ย ๐Ÿงญ Future strategies

Now, we’ve handled any numerical inputs ending in 1. We can try to build up functionality for any number ending in 2.

We can start by adding a test case that only asserts something about the input of 2.

We cannot add this assertion to the first test case. The first test case checks for inputs that end in a 1. To check the case when the input ends in 2, we need to introduce a new test case.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
function getOrdinalNumber(num) {
  if (num === 11) {
    return "11th";
  }
  return `${num}st`;
}

test("works for any number ending in 1", function () {
  expect(getOrdinalNumber(1)).toBe("1st");
  expect(getOrdinalNumber(11)).toBe("11th");
  expect(getOrdinalNumber(21)).toBe("21st");
});

test("converts 2 to an ordinal number", function () {
  expect(getOrdinalNumber(2)).toBe("2nd");
});

Check the test output

Here’s the test feedback for the test above:

second-case-fail

Play computer with getOrdinalNumber when it is called with an input of 2 Double check you agree with the test feedback in this case.

Before coding, outline a strategy for handling the second test case.

  Further assertions</span>

Try updating the second test case to check getOrdinalNumber works for other numerical inputs ending in 2.

We know that this doesn’t solve all cases (e.g. it will give the wrong answer for getOrdinalNumber(2)), but it’s a start, and we have a test-case showing that it works in one case.

This points out a limitation of tests. They only test the cases we wrote tests for. Right now, all our tests are passing, but we know our solution doesn’t work for all inputs!

In order to generalise our solution (to make it work “in general” rather than just for one specific case), It’s important to think about what different groups of inputs we may expect.

๐Ÿง  Forming sub-goals

Learning Objectives

For formatAs12HourClock our strategy for inputs like "23:00" involves checking if the hours value is less than 12. For this purpose, we can use the greater than comparison operator >.

> will check if the value on the operator’s left side is less than the value on the operator’s right side.

So 3 > 12 evaluates to false, as 3 is not greater than 12.

So provided we have an expression for hours, we can write an if statement as follows:

if (/* here goes an expression here that evaluates to the hours */ < 12) {
  // do code to format the 12 hours
}

To complete the logic, we can form a sub-goal ๐Ÿงถ ๐Ÿงถ sub-goal A sub-goal is a goal for a smaller problem that makes up some bigger problem .

Any time we’re solving a problem, we can define a goal - a thing we need to achieve to consider the problem solved. We can break a problem into smaller problems, each with its own sub-goal. The problem-solving process involves continually breaking down problems into smaller manageable problems, each with its own sub-goal.

For the implementation of formatAs12HourClock, we can form a sub-goal as follows:

๐ŸŽฏ Sub-goal: Find the hours value from the time input

๐Ÿง  Interpreting this error

Learning Objectives

We saw this error - let’s try to understand it:

SyntaxError: Identifier 'currentOutput' has already been declared

Knowing what we changed

It can be useful to remember when our code last worked, and what we changed since then.

๐Ÿ’ก tip

Source control can help here.

If you commit your code every time you make something work, you can use git to easily see what changed since your last commit.

When we just had the first 10 lines of code here, everything worked. When we added the rest, we got this error:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
function formatAs12HourClock(time) {
  return `${time} am`;
}

const currentOutput = formatAs12HourClock("08:00");
const targetOutput = "08:00 am";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

const currentOutput = formatAs12HourClock("23:00");
const targetOutput = "11:00 pm";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

๐Ÿ’ก Tip

Run your code very often.

If we changed one thing since our code last worked, we know what change is the problem. If we have written 100 lines of code since we last saw it work, we have 100 possible problems.

Interpreting the error message

The error message tries to tell us useful information:

SyntaxError: Identifier 'currentOutput' has already been declared

When we get an error, we should make sure we understand all of the words in the error message. If we don’t, we should look them up or ask someone.

Exercise

For each word in this error message, write down what it means.

If you don’t know a word, look it up.

Make sure you understand each word. Make sure you could explain the word to someone without reading the answer to them.

Expand for example definitions - only expand this after you have written yours down. Compare your answers with these.
  • SyntaxError - If we Google “JavaScript SyntaxError”, MDN tells us this is “an error when trying to interpret syntactically invalid code”. So - we wrote some code which isn’t allowed.
  • Identifier - If we Google “JavaScript Identifier”, MDN tells us: this is “a sequence of characters in the code that identifies a variable, function, or property”. On line 12, the identifier is the variable name: currentOutput.
  • currentOutput - This is the variable name we used in our code. This is the identifier that the error is about.
  • has, already, and been are all standard English words with no special meaning.
  • declared - We learnt about this already in this course - a declaration is where we make a new name (e.g. a new variable) in JavaScript.

Reading that back, we can rephrase this error message:

We wrote some code which isn’t allowed. We tried to declare a new variable named currentOutput. But we had already declared something named currentOutput.

๐Ÿงฉ Percentages

Learning Objectives

Let’s begin with this problem:

Given a decimal number I want to convert it into a percentage format.

For example, given the decimal number 0.5 we return the string "50%". Given the decimal number 0.231 we return the string "23.1%".

Restating the problem

Our function must convert any decimal to a percentage. We have used functions already. Here are some functions we’ve used:

1
2
console.log("hello world"); // logs "hello world" to the console
Math.round(3.141); // evaluates to 3

All these expressions are function calls: weโ€™re passing input ("hello world" or 3.141) to the functions (console.log or Math.round) to use their functionality. Math.round and console.log are functions that the JavaScript language designers have written and stored inside the language, because everyone needs them all the time.

No such pre-built function converts any number to a percentage, so we must write our own. We’re going to create a function called convertToPercentage with the following requirements:

Given a number input

When we call convertToPercentage with the number input

Then we get back a string representing the percentage equivalent of that number.

Here are some examples:

1
convertToPercentage(0.5); // should return "50%"
1
convertToPercentage(0.231); // should return "23.1%"

Useful expressions

It is often helpful to solve a problem in one specific instance before doing it for all cases.

We’re not going to define our function yet. Instead we will work out what our function should do. Then we’ll define a function which does the same thing.

In programming, we always try the simplest thing first. Letโ€™s consider how to convert just one number to a percentage. Look at this variable declaration:

1
const decimalNumber = 0.5;

We want to create an expression for the percentage using the value of decimalNumber. To convert to a percentage, we will multiply the number by 100 and then add a "%" sign on the end.

1
2
const decimalNumber = 0.5;
const percentage = `${decimalNumber * 100}%`;

Recalling template literals, the expression in the curly braces will be evaluated first and then inserted into the string, giving us the percentage string.

Now that we’ve solved the problem of converting a single decimal number to a percentage, let’s practice solving other similar problems using expressions.

Create a new JavaScript file so that you can try running the code for yourself.

Calculating the area and perimeter of a rectangle

In one of these new files, let’s make two variables that describe the dimensions of a rectangle:

const height = 10; // 10 is just an example of a value here - your code should still work if you change this to another value.
const width = 30; // Also just an example - your code should still work if this changes.

Using these variables, let’s calculate the area and perimeter of the rectangle.

We can calculate the area and perimeter by creating expressions that use the height and width variables we just created. Hint: read the links above if you don’t know how to calculate area and perimeter of a rectangle.

Finally, we’ll create two more variables: area and perimeter to store the result of the calculations.

const area = FILL_ME_IN;
const perimeter = FILL_ME_IN;

Now, if we change the numbers assigned to height and width, are the area and perimeter values still correct? Try using console.log to print out the value of the variables and then run the script using Node to view the output.

Remember to create a new JavaScript file to run the code for yourself.

Converting pence to pounds

Like the rectangle example, we’ll start by creating a variable to store a price in pence:

const price = 130; // Just an example value. Try changing this value to 0, 10, or 1521, and make sure you still get the right answer from your code.

Now, you should write an expression that calculates the price in pounds. The price in pounds should be shown with 2 decimal places and start with “ยฃ”.

Try using console.log to print out the value of price in pounds and then run the script using Node to view the output.

๐Ÿงฎ Ordinal numbers

Learning Objectives

๐Ÿข Let’s imagine you’re working in a 10 storey office building. There are 10 different levels. We need a way to describe each level of the building. We start on the ground floor of the building - level with the ground. We use an ordinal number to describe the other levels in the building.

To form the ordinal number we take a number and add the correct suffix ๐Ÿงถ ๐Ÿงถ suffix The suffix comes from the word used to describe each number, like first, second, third etc.

โ˜๐Ÿฟ Up from the ground floor, we are then on the 1st floor (first floor) โ˜๐Ÿฝ Up from the 1st floor, we are on the 2nd floor (second floor)

number+ suffix= ordinal number
1st1st
2nd2nd

What will the ordinal number be for:

a) 21? b) 40? c) 49?
d) 13?

Use ordinal numbers to write the days of the month for the following events:

a) Tomorrow b) A week from now c) Easter Sunday 2024 d) When is Eid expected to occur in 2024

  1. 1st
  2. 2nd
  3. 3rd
  4. 4th
  5. 5th
  6. 6th
  7. 7th
  8. 8th
  9. 9th
  10. 10th

๐Ÿ“‹ Specification

Let’s consider a function called getOrdinalNumber that needs to work like this:

  • it takes one argument - a whole number, like 1, 2, 3, etc
  • it returns a string that represents the ordinal number
getOrdinalNumber(1); // returns "1st";
getOrdinalNumber(2); // returns "2nd";
getOrdinalNumber(6); // returns "6th";

The requirements above form a specification ๐Ÿงถ ๐Ÿงถ specification A specification is a set of requirements for how a piece of software should behave. . Now we have a specification for how the function should work we can create many cases showing how we expect the function getOrdinalNumber to behave when it is called with different inputs.

๐Ÿงฐ Install a UNIX based operating system

Learning Objectives

If you get stuck on any of the below or above instructions, please post in your class channel on Slack.

You probably already have this if you have done previous modules.

โš ๏ธ warning

Code Your Future does not support Windows.

We have found that we don’t reliably have volunteers at class who can debug Windows problems, and they frequently come up, stopping trainees from making progress.

We do not support Windows so as to not waste lots of trainee and volunteer time. We have made this decision even though Windows is a popular operating system and has features like WSL which help to get experience with Unix.

You must use a Unix-based operating system at Code Your Future.

If you have a Mac or Linux machine already, you already have a UNIX based operating system. All CYF-supplied laptops run Mac OS or Linux. If you have your own machine and it runs Windows, you should already have set up a Linux partition.

If you have still not done this, you must do it now. We cannot support learners using Windows. It takes too much time from everybody else. If you need help doing this, post in Slack, or bring your laptop to a CYF co-working space to get support. It’s normal to need help with this process.

Dual Boot on Windows

๐Ÿงฐ Install Node with nvm

Learning Objectives

If you get stuck on any of the below or above instructions, please post in your class channel on Slack.

๐Ÿ’ก tip

Check if you already have NodeJS installed by running node -v in a terminal. The command should return a version number. If it does, you can skip the next steps.

๐Ÿง On Ubuntu

  1. Install nvm by running the following commands in your terminal:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
  1. After the installation is complete, you’ll need to source the nvm script by running:
source ~/.bashrc
  1. Install the latest LTS version of Node.js by running:
nvm install --lts
  1. Check that you have successfully installed Node.js by running:
node -v

You should see a version number like v20.16.0.

  1. Check that you have successfully installed npm by running:
npm -v

You should see a version number like 10.5.0.

๏ฃฟ On Mac

  1. Install nvm by running the following command in your terminal:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
  1. After the installation is complete, you’ll need to source the nvm script by running:
source ~/.zshrc
  1. Install the latest LTS version of Node.js by running:
nvm install --lts
  1. Check that you have successfully installed Node.js by running:
node -v

You should see a version number like v20.16.0.

  1. Check that you have successfully installed npm by running:
npm -v

You should see a version number like 10.5.0.

๐Ÿ’ก Protip

Using nvm allows you to easily install and manage multiple versions of Node.js on your system. This will help you access projects that use older versions of Node.js.

๐Ÿงถ Accessing strings

Learning Objectives

Given a time string we need to access the first 2 characters of the string which represent the hours.

Strings are zero-indexed. Index means position, so zero-indexed means we start counting character positions from 0 onwards.

Here are the positions/indexes for "23:00"

index01234
character"2""3"":""0""0"

In JavaScript, we can use square bracket notation to access specific characters in the string using the index.

time[0]; // evaluates to "2"
time[1]; // evaluates to "3"
time[2]; // evaluates to ":"
// etc

Square bracket access will only give a single character. We must use another method to extract multiple characters from the given string.

โœ‚๏ธ Extracting a slice

To extract 1 or more characters from a string, we can use a function called slice ๐Ÿงถ ๐Ÿงถ slice slice is a function that can take 2 arguments: a start index and an end index. slice will return a section of the string from the start index up to but not including the end index.

time; // holds the value "23:00"
time.slice(0, 2); // will access the characters below
index01
character"2""3"

So time.slice(0,2) will evaluate to "23" when the time is "23:00".

Finally we must convert "23" to the number 23, otherwise we can’t compare this value properly.

exercise

Think about the numbers 100 and 23. Which one is bigger?

Write code which compares the strings "100" and "23" and tells you which one is bigger.

Does this match your expectation? If not, what do you think is happening?

JavaScript uses different ways to compare values depending on their types. If you compare two strings (which may contain numbers), it will do something different than if you compare two numbers.

We can use the Number function to convert the string into a number.

exercise

Work out how you can convert the strings “100” and “23” to numbers.

Write code which compares the numbers 100 and 23 and tells you which one is bigger.

๐Ÿงน Improving the code

Learning Objectives

Refactoring

Now the assertions pass: in other words, our functionโ€™s current output matches with the target output described in the assertions.

In addition to implementing functionality, we also need to continually improve the code quality. Other developers will continue to read our code so it’s vital our code is readable by other humans.

๐Ÿ’ก Definition: refactoring

The process of updating our code quality (without changing the implementation) is called refactoring.

Let’s consider our working implementation so far: Currently, weโ€™re using the same expression twice: Number(time.slice(0, 2)). This means weโ€™re calling the functions Number and slice twice.

Additionally, expressions embedded inside curly braces and parentheses can often be difficult to read. In this situation it makes sense to label the recurring expression so we can reuse it wherever we need to in our code.

Letโ€™s create a variable called hours and assign to it our expression’s result.

function formatAs12HourClock(time) {
  const hours = Number(time.slice(0, 2));

  if (hours > 12) {
    return `${hours - 12}:00 pm`;
  }
  return `${time} am`;
}

Note that the function’s behavior hasn’t changed: it still returns the same outputs from the given inputs. We’ve just improved the implementation without changing the underlying behaviour.

๐Ÿ› Fixing bugs

Here is our current implementation of formatAs12HourClock:

function formatAs12HourClock(time) {
  const hours = Number(time.slice(0, 2));

  if (hours > 12) {
    return `${hours - 12}:00 pm`;
  }
  return `${time} am`;
}

However, formatAs12HourClock currently has a bug ๐Ÿงถ ๐Ÿงถ bug Any unintended behaviour or effect from our software is called a bug.

function formatAs12HourClock(time) {
  const hours = Number(time.slice(0, 2));

  if (hours > 12) {
    return `${hours - 12}:00 pm`;
  }
  return `${time} am`;
}

a) Write an assertion to check the output of formatAs12HourClock when it is called with an input "17:42" b) Check the assertion output and try to explain what the bug is

Once you’ve established the bug in the code, try removing the bug by updating the implementation of formatAs12HourClock.
Once you’ve changed the implementation to formatAs12HourClock, re-run all the assertions to check you’ve not broken any previous functionality.

๐Ÿงน Refactoring repetition

Learning Objectives

Let’s look at our code, which passes all of the tests we’ve written:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function formatAs12HourClock(time) {
  if (Number(time.slice(0, 2)) > 12) {
    return `${Number(time.slice(0, 2)) - 12}:00 pm`;
  }
  return `${time} am`;
}

const currentOutput = formatAs12HourClock("08:00");
const targetOutput = "08:00 am";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

const currentOutput2 = formatAs12HourClock("23:00");
const targetOutput2 = "11:00 pm";
console.assert(
  currentOutput2 === targetOutput2,
  `current output: ${currentOutput2}, target output: ${targetOutput2}`
);

Inside the formatAs12HourClock function we do exactly the same thing twice.

Exercise

Identify the two bits of code inside formatAs12HourClock that do exactly the same thing.

There are a few reasons this isn’t ideal.

  1. It’s not clear what this value represents. You can read it and work it out, but that takes some time.
  2. Doing the same thing twice is slower than doing it once.
  3. In the future if we need to change this code’s implementation, we would need to change it twice.
    Right now our code assumes the hours in a time are always two digits (like 05:00). What if we wanted to support single-digit hours, like 5:00? We would need to make the same change to both lines. It would be easy to change one line and forget the other, which would lead to a bug.

Refactor

Once your code passes your test, look for ways you could make your code better. This doesn’t mean changing what it does - the code works. It means changing how it’s written.

This is called refactoring ๐Ÿงถ ๐Ÿงถ refactoring To refactor means to update our code quality without changing the implementation. Changing how it does something, not changing what it does. .

We can refactor our code to remove this duplication by introducing a variable:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
function formatAs12HourClock(time) {
  const hours = Number(time.slice(0, 2));
  if (hours > 12) {
    return `${hours - 12}:00 pm`;
  }
  return `${time} am`;
}

const currentOutput = formatAs12HourClock("08:00");
const targetOutput = "08:00 am";
console.assert(
  currentOutput === targetOutput,
  `current output: ${currentOutput}, target output: ${targetOutput}`
);

const currentOutput2 = formatAs12HourClock("23:00");
const targetOutput2 = "11:00 pm";
console.assert(
  currentOutput2 === targetOutput2,
  `current output: ${currentOutput2}, target output: ${targetOutput2}`
);

This code does exactly the same thing as the previous code. But it is better in a few ways:

  1. We can now tell more easily what this expression represents. The variable name conveys: it’s the hours from the time.
  2. We only compute the hours once, not twice, which will be a little bit faster.
  3. If we need to change how we identify the hours (e.g. to support single-digit hours), we only need to update one bit of code. Both lines 3 and 4 will automatically use the same value, because they’re referring to the same variable.

๐Ÿ’ก Code Quality

Whenever you finish implementing a sub-goal, or a goal, read your code and look for ways to refactor it.

This will make your code easier to continue working on.

๐Ÿงพ Evaluating expressions

Learning Objectives

๐Ÿ’ก Tip

Computers work by storing and performing operations on data.

Computer programs are built from many expressions. We must understand how expressions are evaluated to understand how computer programs are executed.

We can take an expression like 36 * 45 and ask what it evaluates to. If we know what the * operator represents (multiplication) and if we understand the arithmetic rules represented by the operation we can evaluate this expression ourselves.

Happily, computers can evaluate expressions for us.

NodeJS is an application that runs JavaScript programs. In other words, NodeJS can understand and execute programs written in JavaScript. One feature of Node is the REPL.

๐Ÿ“ Note

REPL is a special type of program that stands for:

  • Read - Users enter some code that Node will read
  • Evaluate - Node will then evaluate this code
  • Print - Node will print the result to the terminal
  • Loop - Node will loop back to the beginning and prompt users to input some more code

With a REPL we can run pieces of code and look at what happens.
{title=“Definition: REPL” AlertType=“definition”}

We input JavaScript instructions that are then executed by NodeJS. The REPL replies with, or prints out, the result of this execution.

Type each of the following expressions into the REPL one at a time and then press enter to check the result.

10 + 32
32 / 10

๐Ÿ“ Note

In this activity, you’ll check you’re ready to use the Node REPL on your machine.

  1. Open the terminal on your computer
  2. Check you’ve got Node installed on your computer
  3. Start the Node REPL in your terminal
  4. Enter the expressions and evaluate them using the Node REPL {title=“Activity” AlertType=“activity”}

If you don’t know how to do any of the steps above, then try searching for an appropriate command online. Searching for things when you’re stuck is super important part of being a developer!

๐Ÿ“ Note

Create your own expressions and enter them into the Node REPL.

๐Ÿง  Before you type in the expressions, predict what the REPL output will be. Write your prediction down and compare it to the outcome. {title=“Activity” AlertType=“activity”}

๐Ÿช„ Declaring functions

Learning Objectives

๐Ÿ’ก Recall

Functions are reusable blocks of code.

To create a function, we can use a function declaration. A function declaration looks like this:

1
function convertToPercentage() {}

The function declaration consists of the following syntactic elements:

  • function keyword, begins the function declaration
  • convertToPercentage - names the function
  • () - any input to the function will go between these round braces (our function above doesn’t take any input (yet), but it still needs the ()s)
  • {} - the body of the function is written inside the curly braces (our function above doesn’t do anything yet, but it still needs the {}s)

We can create a function declaration by wrapping up the percentage variable and the expression for the percentage inside the function.

1
2
3
4
5
const decimalNumber = 0.5;

function convertToPercentage() {
  const percentage = `${decimalNumber * 100}%`;
}

At the moment decimalNumber is not wrapped up inside the body of the function. In the following sections, we will explore what happens when this is the case.

๐Ÿช„ Functions

Learning Objectives

Now, instead of adding or multiplying numbers, weโ€™ll consider 10.3.

๐Ÿค” “What is the nearest whole number to 10.3?”

The process of finding the nearest whole number to a decimal number is called rounding. So we could rephrase our question as:

๐Ÿค” “What does the number 10.3 round to?โ€

โ™ป๏ธ Reusing instructions

There is no operator for rounding the number 10.3 in JavaScript. But we will want to round numbers again and again. We should use a function ๐Ÿงถ ๐Ÿงถ function A function is a reusable set of instructions. .

Math.round is a function. Because a function is a reusable set of instructions, Math.round rounds any number.

Functions usually take inputs and then apply their set of instructions to the inputs to produce an output.

  1. Write Math.round in the Node REPL
  2. Hit enter to evaluate our expression

The REPL output [Function: round] is telling us Math.round is a function.

rounding
[Function: round] indicates that Math.round is a function

๐Ÿ“ฒ Calling a function

For our function to work, we need Node to read the instructions and execute ๐Ÿงถ ๐Ÿงถ execute Execution means the computer reads and follows instructions. them. Write the following in the REPL:

Math.round(10.3);

Notice the ( and ) brackets after the name of the function and a number inside the brackets. These brackets mean we are calling the function. The number inside the brackets is the input we’re passing to the function.

๐Ÿ“ Calling a function

Calling a function means telling the computer to read the function’s instructions and carry out its instructions. When calling a function we can also pass inputs to the function.

Math.round(10.3) is a call expression; read this as:

“apply the set of instructions for Math.round to the number 10.3.”

If we type Math.round(10.3) then we get the result 10. So we say that Math.round(10.3) returns 10.

A call expression is an expression which evaluates to the value returned by the function when it is called. So the expression Math.round(10.3) evaluates to the value 10.

If we assign that expression to a variable, or use it in a string, we’ll get the value 10. So we can write:

const roundedValue = Math.round(10.3);

or we can write:

const roundedValueInAString = `10.3 rounds to ${Math.round(10.3)}`;

Both of these instructions evaluate the call expression Math.round(10.3) to the returned value 10 as soon as the call expression appears. The variable roundedValue will have a numeric value 10 (just like if we’d written const roundedValue = 10;), and the variable roundedValueInAString will have a string value "10.3 rounds to 10".