diff --git a/01 Setup Win10.md b/01 Setup Win10.md new file mode 100644 index 00000000..5b478f49 --- /dev/null +++ b/01 Setup Win10.md @@ -0,0 +1,60 @@ + +# Setup for Windows 10 Users + +The tools used in this module are designed to be used in a Unix or Linux environment. Whilst this will create challenges for Windows 10 user there are some steps you will need to take. Please read and follow the steps below carefully: + +Install the Windows Subsystem for Linux (WSL). Open PowerShell as Administrator and run: + +``` shell +Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux +``` + +Restart your computer when prompted. + +Now install Visual Studio Code and once launched install the [remote-wsl](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-wsl) component by Microsoft. You should also install the [browser-preview](https://marketplace.visualstudio.com/items?itemName=auchenberg.vscode-browser-preview) component which will allow you to open html web pages. + +![remote-wsl](exercises/.images/remote-wsl.png) + +Now you need to install Ubuntu. This can be found by searching for `run linux on windows` in the Microsoft Store. You will be presented with the following screen. + +![Microsoft Store](exercises/.images/store.png) + +Choose the Ubuntu operating system (v18.04 LTS) and install. Once installed, click on the **Launch** button, this will open a console window and you will need to wait for a few minutes for the installation to complete. + +## Cloning the Forked Repository + +You will now need to fork the foundation lab by clicking on the Fork button. This will create a copy of the repository. See the standard setup instructions for more details. + +Now you can clone your forked repository by running the following command in the Ubuntu console, replacing xxx with the URL of your repository. + +```shell +git clone xxx +``` + +This will create a directory called `foundation` in the Ubuntu console. The final step is to launch VS Code from within the WSL environment by running the following command: + +```shell +code foundation +``` + +This will launch VS Code from within the WSL with the contents of the `foundation/` directory. If you open the integrated terminal (using the **Terminal** menu) you will see that you have the full ubuntu bash shell. You can now run all the remaining steps from this integrated terminal, just as you would for a standard Linux install. + +## Installing NodeJS + +These steps are identical to those used on a full Ubuntu installation. Start by installing the Node Version Manager (NVM) tool: + +```shell +curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.0/install.sh | bash +nvm --version +nvm install node +``` + +If the `nvm` command is not found you will need to reload the shell: + +```shell +source ~/.profile +``` + +Now try to install again. + +Now you can go directly to step 4 in the standard setup instructions. diff --git a/04 CSS3.md b/04 CSS3.md index 153ca8e1..4630da37 100644 --- a/04 CSS3.md +++ b/04 CSS3.md @@ -536,7 +536,7 @@ body { The next task is to build a navigation menu bar to the website using CSS. Depending on the number of menu items, the menu bar can contain multi-level menus. -A menu bar is be defined as an HTML unordered list. Each menu in a menu bar is a list item. +A menu bar can be defined as a HTML unordered list. Each menu in a menu bar is a list item. The menus can contain submenus. A submenu is coded as a list that is within the parent menu's list item. diff --git a/05 JavaScript.md b/05 JavaScript.md index a52e42fb..1cfdf9ae 100644 --- a/05 JavaScript.md +++ b/05 JavaScript.md @@ -42,10 +42,7 @@ Start by running the `maths.js` script and map the output it generates against t 1. Create a new function called `multiply()` that takes two parameters, `a` and `b` and returns the _product_ of the two. - what happens if you call it with only a single parameter? -2. Modify the function so it uses a default parameter to multiply by 1 if the second parameter is missing. - - What happens if you don't supply _any_ parameters? - - Add a second default parameter to prevent this. -3. Write an _arrow function expression_ stored in a constant called `squareRoot` which calculates and returns the square root of the supplied number. You will need to use the `sqrt()` method which is part of the `Math` object. +2. Write an _arrow function expression_ stored in a constant called `squareRoot` which calculates and returns the square root of the supplied number. You will need to use the `sqrt()` method which is part of the `Math` object. Open the `contact.js` script, implement the `validateEmail()` function and thoroughly test it, you should avoid using regular expressions at this stage: @@ -53,6 +50,87 @@ Open the `contact.js` script, implement the `validateEmail()` function and thoro 2. Check that there is a `@` character and that it is not at the start of the string (HINT: use the [indexOf](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf) String prototype method. 3. Check that there is a period (.) character after the `@` character but before the end of the string. +#### 1.1.2 Function Parameters + +In the JavaScript language although we define a function with a set of specified _parameters_, when we call the function we don't need to pass these arguments: + +We can choose to pass fewer arguments than are specified in the function parameters. Any parameters that don't have a matching argument are set to `undefined`. For example, the following code will print `undefined`. + +```javascript +function sqr(num) { + return num * num +} +sql() // returns NaN (not a number) +``` + +This can cause issues in your code so to prevent this we provide [Default Parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters). If an arguement is missing when a function is called this specified a default value to use. For example consider this version of the function: + +```javascript +function sqr(num = null) { + return num * num +} +sqr() // returns 0 +``` + +In JavaScript: + +- A value of `undefined` means a value has not been assigned to a variable. +- A value of `null` is a value assigned to a variable and means _no value_. + +It is also possible to pass in more arguements than there are parameters in the function declaration. The following is quite valid: + +```javascript +function add(num1, num2) { + return num1 + num2 +} +add(4, 2, 1) // returns 6 +``` + +As you can see, if there are too many arguments, the extra ones are ignored however JavaScript provides a mechanism to access all the arguments passed to a function regardless of whether they match the parameter list by using the _array-like_ `arguments` object, for example: + +```javascript +function add() { + let total = 0 + for(arg of arguments) total += arg + return total +} +add(4, 2, 1) // returns 7 +``` + +Using _hidden_ or _magic_ variables that magically come into existence can make your code hard to understand so ECMA6 introduced [Rest Parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters), parameters that can hold any arguments that don't match the parameters in the function declaration. Take the following code: + +```javascript +function add(num1, num2, ...others) { + let total = num1 + num2 + for(arg of others) total += arg + return total +} +add(4, 2,1) // returns 7 +``` + +This demonstrates how the rest parameter _mops up_ any surplus arguments and could be written as: + +```javascript +function add(...numbers) { + let total = 0 + for(arg of numbers) total += arg + return total +} + +console.log(add(4, 2, 1)) // returns 7 +``` + +#### 1.1.3 Test Your Understanding + +1. create a function called `divideThis()` that takes two arguments, a number to be divided, `dividend` and the number to divide by, `divisor`. The function should return the _quotient_. +2. What happens if you don't pass a parameter for the `divisor` parameter? Can you fix this by supplying a suitable _default parameter_? +3. Call the `multiply()` function from the previous task omitting the second parameter. Can you modify the function so it uses a default parameter to multiply by 1 if the second parameter is missing. + - What happens if you don't supply _any_ parameters? + - Add a second default parameter to prevent this. +4. Create a new function called `average()` that takes one or more numerical parameters to return the average of these: + - Write this to make use of the `arguments` construct. + - Rewrite this to use an ECMA6 rest parameter. + ### 1.2 Function Expressions Functions are a data type in JavaScript (they are objects but more on that later). As such they can be stored in variables for later execution. Prior to ECMA6 they were declared using the `function` keyword like this: @@ -99,7 +177,7 @@ const sqr = num => num * num 2. Compare this to the original version: which is more _readable_? 3. Create a function expression that takes two string parameters and returns the longest string and assign this to a constant called `longest. check this works correctly. 4. Modify the function expression so that it can handle any number of string parameters (use a _rest parameter_). (hint: you will need to use a [`for...in`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...in) statement to loop through the strings. How does this differ from a [`for...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of) statement?) -5. Use a [ternary operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) instead of the `if` statement in the loop. +5. Use a [ternary operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator) instead of the `if` statement in the loop. 6. Finally use the [`reduce()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) method to replace the `for...in` loop to reduce the function to a single line. ## 2 Callbacks @@ -235,6 +313,13 @@ const grade = employee.grade || 'A' This will retrieve the value of the grade property if defined and store it in the `const` variable. If this property is missing the `const` variable will contain the string `'A'`. +#### 3.2.1 Test Your Understanding + +1. Create a new object called `university` which should contain three properties, `year1`, `year2` and `year3`. Each of these properties should store an object whos keys are the module codes and values the titles of the modules. +2. Create a variable called `study01` containing the `year1` object. +3. Use the `for...in` statement to iterate over this `study01` object printing out all of the _module codes_. +4. Use the `for...of` statement to print out all of the _module names_. + ### 3.3 JSON Data JSON (JavaScript Object Notation) is a standard text-based format to represent structured data. This is very useful as it means we can take any JavaScript object and convert it into a text string. This can then be saved to disk or posted to a web server, etc. It also means that you can take a JSON-formatted text string and convert it into a complex JavaScript object! @@ -289,10 +374,24 @@ Lets apply our knowledge of callbacks to implement a simple quotes tool. 3. The contents of the file is a utf8 string, use `JSON.parse()` to convert this into a JavaScript object (array) and print this to the terminal instead. 4. Create a loop to iterate through the array, printing the contents of each index. 5. Modify the code so that it only prints the quotes string (not the entire object). +6. Convert the `university` object from the previous exercise into a JSON string and save it to the filesystem as `university.json`. ### 3.3 ECMA6 Object Destructuring -In ECMA6 is is possible to extract multiple pieces of data into separate variables by destructuring using an _object pattern_. This is syntactically similar to creating object literals (see the example below). +There are situations where we want to retrieve multiple object properties and store then in different variables, for example: + +```javascript +const employee = { + firstName: 'Colin', + 'last name': 'Stephen', + 'department': 'Computing' +} +const first = employee.firstName +const last = employee['last name'] +console.log(`${first} ${last}`) +``` + +In ECMA6 it is possible to extract multiple pieces of data into separate variables by destructuring using a [Desctructuring Assignment](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Object_destructuring). This is syntactically similar to creating object literals (see the example below). ```javascript const employee = { @@ -301,11 +400,15 @@ const employee = { 'department': 'Computing' } -let {firstName: first, 'last name': last, department: dept} = employee +const {firstName: first, 'last name': last, department: dept} = employee console.log(first) // prints 'Colin' console.log(dept) // prints 'Computing' ``` +#### 3.3.1 Test Your Understanding + +1. Take the `university` object you created in an earlier exercise and use a single line destructuring assignment to create three variables, `year1`, `year2` and `year3`. + ### 3.4 Getters and Setters Most object properties are simple values and you can simply assign a value. Sometimes however properties need to be calculated. One solution is to store a function as one of the properties however we would need to call a function to retrieve the value: @@ -582,7 +685,7 @@ const matt = new ECMA6Student('matt') console.log(ECMA6Student.studentCount()) // prints '2' ``` -Notice that the static vsriable `count` is public (so the `studentCount()` method is somewhat superfluous in this example!). This highlights one of the limitations of JavaScript, the lack of a simple way to define private attributes (variables and methods). The next section goes into this in more detail and explains some workarounds (hacks) to get around this. +Notice that the static variable `count` is public (so the `studentCount()` method is somewhat superfluous in this example!). This highlights one of the limitations of JavaScript, the lack of a simple way to define private attributes (variables and methods). The next section goes into this in more detail and explains some workarounds (hacks) to get around this. ### 4.5 Handling Data Encapsulation @@ -601,20 +704,3 @@ You should take time to understand the [pros and cons](https://2ality.com/2016/0 2. Use this to create a second **constructor function** class called `OldPickup` that includes `payload` and `seats` fields and use this to create two pickup objects. 3. Now use the same information to create a class called `NewVehicle` and extend this to create a class called `NewPickup` and use this to create two or more pickup objects. 4. Add a static member to capture the total value of all the pickup sales and print this to the terminal. - - - -Show how objects can be turned into strings and saved. text data loaded and converted into a JavaScript object. - -### 5.1.1 Test Your Understanding - -### 5.2 RESTful APIs - -Show how data can be retrieved from an API in JSON format. - -//TODO: use the OMDB API in this section - -OMDB key: 220d2590 - -First task is for students to get an OMDB API key and paste it into the provided script. - diff --git a/06 Code Quality.md b/06 Code Quality.md index 4b36f4ad..47d21c4b 100644 --- a/06 Code Quality.md +++ b/06 Code Quality.md @@ -32,11 +32,19 @@ Next you will need to do is to split your code up to make it easier to understan Lets examine this module: -1. The `module.exports` object is used to define what functionality will be made available to our `index.js` file. In this case we are exporting the Object Prototype. Notice we are using the new `Class` constructor. -2. Lines 6-8 are the constructor where we define our array that can be accessed by the object. -3. The rest of the file defines a series of functions that form part of the object prototype. +1. We define an object prototype called `List`. +2. This contains an object prototype definition using the modern `class` syntax. If you are not familiar with the concept refer back to the **JavaScript** lab sheet. +3. On lines 43-45 we create an object containing all the functionality we want to export and pass this to the `module.exports` object. This uses the new **Object Property Value Shorthand** syntax (see below). -Now look at the top of the `index.js` file. Lines 22-23 import our module into the `List` variable. This can then be used to create a new object using our `List` object prototype. This object is called `list` and provides access to all the functionality defined in the object prototype. Foe example to add an item we can use: +The object property value shorthand notation is useful if the keys have the same name as the variables passed-in as properties. For example the code in the module could be written as: + +```javascript +module.exports = { + List: List +} +``` + +Now look at the top of the `index.js` file. Lines 18-19 import the module and create an instance of the List prototype. Notice that we are only importing the `List` prototype from the module. This `List` object prototype is then used to create a new object. This object is called `list` and provides access to all the functionality defined in the object prototype. Foe example to add an item we can use: ```javascript list.add('bread', 42) @@ -44,17 +52,16 @@ list.add('bread', 42) This will call the `add()` function that is part of our `todo` object prototype. +Try running the server and adding an item to the list. Currently there is no functionality implemented at all! All the functionality is in the module we have imported. + ### 2.1 Test Your Understanding The custom object prototype defined in the `list.js` module already contains the functionality needed by your app. -1. Uncomment lines 22-23 to import the module and create a custom object. -2. In the `router.post('/')` function call replace lines 41-42 with a call to `list.add()`, passing the item name and quantity as parameters. -3. Now modify the `router.get('/')` function callback by replacing lines 29-30 with a call to the `list.getAll()` function. -4. To test the functionality so far, comment out the array declaration on line 20 and try starting the web server. You should be able to add items, the data is now stored in the custom object. -5. Finally replace line 53 with a call to the appropriate function in the custom object. - -Now much of the business logic has been moved to the separate module, are there any module imports in `index.js` that are no longer needed? Locate these and delete. +1. Modify the callback function in the `router.post('/')` function, inserting a call to `list.add()`, passing the item name and quantity as parameters. +2. Now modify the callback function in the `router.get('/')` function, replacing the empty array declaration with a call to the `list.getAll()` function. +3. Test the base functionality so far, you should be able to add items to the list. +4. Finally implement the delete functionality. ## 3 Linting @@ -76,7 +83,7 @@ $ node_modules/.bin/eslint todo/index.js You will see a list of issues that the linter has flagged in your code. Notice that some of these are flagged as errors (serious) and some as warnings (recommendations). Each message includes: -1. The line and comumn number where the error was found. +1. The line and column number where the error was found. 2. A description of the error. 3. The rule that is being broken. @@ -91,7 +98,7 @@ $ node_modules/.bin/eslint todo/ ### 3.1 Test Your Understanding 1. Locate and fix all the errors and warnings in the `index.js`. -2. Install `eslint` globally and then install the [eslint extension](https://github.com/Microsoft/vscode-eslint). After restarting your editor you should see any errors and warnings flagged in the editor. +2. Install the [eslint extension](https://github.com/Microsoft/vscode-eslint). After restarting your editor you should see any errors and warnings flagged in the editor. 3. Use the `eslint` plugin to locate and fix all the errors in the `modules/list.js` file. ## 4 Documentation @@ -124,10 +131,16 @@ Because the code to be run after a callback is run needs to be _inside_ the call Open the file `nestedCallbacks.js` which asks for a _base_ currency code then prints out all the exchange rates against other currencies. Notice that there are four functions defined, three of which include a callback. Our script is designed to capture user input using `stdin` (needing a callback), identify whether a currency code is valid (requiring a second callback) and then getting the currency conversion rates for the specified currency (requiring a third callback). -1. Notice that the `checkValidCurrencyCode()` function is called by the callback for the `getInput()` function and the `getData()` function is called by the callback for the `checkValidCurrencyCode()` function. -2. Each callback takes two parameters as normal. The first contains the error (if any) and this needs to be handled in each callback. -3. The data from the first callback is needed when calling the third function so needs to be stored in an immutable variable (constant). -4. The fourth, and final, function does not have a callback. +1. The script starts be calling the `read.question()` function (line 13) which takes a callback function as its second parameter. +2. We want the first `request()` function (line 18) to be run after the initial `read.question()` function has completed. + 1. To do this it needs to be called from inside the `read.question()` callback function. +3. The second `request()` function should run after the first one has completed and the data has been processed. + 1. To do this it needs to be called from inside the callback function of the first `request()` call. +4. The final step is to call the `read.question()` function again (line 38)) which should be run once the second request has completed. + 1. Therefore this needs to be in the callback function from the second `request()` function. + +As you can see, each step has to be nested inside the previous step's callback, creating an ever increasing level of nested code sometime referred to as [Callback Hell](http://callbackhell.com/) or the [Pyramid of Doom](https://en.wikipedia.org/wiki/Pyramid_of_doom_(programming)). + Callbacks are the simplest possible mechanism for asynchronous code in JavaScript. Unfortunately, raw callbacks sacrifice the control flow, exception handling, and function semantics familiar from synchronous code. diff --git a/exercises/.images/remote-wsl.png b/exercises/.images/remote-wsl.png new file mode 100644 index 00000000..e6679da2 Binary files /dev/null and b/exercises/.images/remote-wsl.png differ diff --git a/exercises/.images/store.png b/exercises/.images/store.png new file mode 100644 index 00000000..e34853d0 Binary files /dev/null and b/exercises/.images/store.png differ diff --git a/exercises/01_setup/index.js b/exercises/01_setup/index.js index ef00019f..af07c3e0 100644 --- a/exercises/01_setup/index.js +++ b/exercises/01_setup/index.js @@ -10,3 +10,4 @@ const port = 8080 app.use(async ctx => ctx.body = 'Hello World') module.exports = app.listen(port, () => console.log(`listening on port ${port}`)) +//aasd \ No newline at end of file diff --git a/exercises/02_http/01_url/index.js b/exercises/02_http/01_url/index.js index ae8e5788..457ddf23 100644 --- a/exercises/02_http/01_url/index.js +++ b/exercises/02_http/01_url/index.js @@ -38,35 +38,32 @@ router.get('/anon', ctx => { }) router.get('/books/:index/:index2', ctx => { - const books = ['The Hobbit', 'Alice in Wonderland', 'The Secret Garden', 'Wallefs life in pictures'] + const books = ['The Hobbit', 'Alice in Wonderland', 'The Secret Garden','Animal Farm'] const parameters = ctx.params console.log(parameters) - const title = books[parameters.index] - const title2 = books[parameters.index2] - if (parameters.index in books && parameters.index2 in books){ - ctx.body = `${title} and ${title2}` - // ctx.body = title + " and " + title2; - } else { - ctx.body = "Go away" - } -}) - -router.get('/name', ctx => ctx.body = JSON.stringify(ctx.query)) -router.get('/hello/:name', ctx => { - let myname = ctx.params.name - if(ctx.query.format === 'upper') myname = myname.toUpperCase() - if(ctx.query.format === 'lower') myname = myname.toLowerCase() - // only applies uppercase if formatting query exists - ctx.body = `hello ${myname}` + if (parameters.index > 3 || parameters.index2 > 3) { + ctx.body = "Number cannot be greater than 3" + } + else if (isNaN(parameters.index) || isNaN(parameters.index2)){ + ctx.body = "A number must be entered" + } + else if (Number.isInteger(parameters.index)){ + ctx.body = "A whole number must be entered" + } + else{ + const title = books[parameters.index] + const title2 = books[parameters.index2] + ctx.body = title + title2 + } }) router.get('/name', ctx => ctx.body = JSON.stringify(ctx.query)) router.get('/hello/:name', ctx => { let myname = ctx.params.name - if(ctx.query.format === 'reverse') myname = myname.toUpperCase() + if(ctx.query.format === 'upper') myname = myname.toUpperCase() // only applies uppercase if formatting query exists ctx.body = `hello ${myname}` }) diff --git a/exercises/06_code_quality/promises.js b/exercises/06_code_quality/promises.js index 30397aea..671a3dd0 100644 --- a/exercises/06_code_quality/promises.js +++ b/exercises/06_code_quality/promises.js @@ -11,7 +11,7 @@ const getInput = prompt => new Promise(resolve => { read.question(`${prompt}: `, value => { console.log(`You entered ${value}`) read.close() - resolve(value) + return resolve(value) }) }) @@ -21,14 +21,14 @@ const checkValidCurrencyCode = code => new Promise( (resolve, reject) => { if (err) reject(new Error('invalid API call')) const rates = JSON.parse(body).rates if (!rates.hasOwnProperty(code)) reject(new Error(`invalid currency code ${code}`)) - resolve(code) + return resolve(code) }) }) const getData = code => new Promise( (resolve, reject) => { request(`${baseURL}?base=${code}`, (err, res, body) => { if (err) reject(new Error('invalid API call')) - resolve(body) + return resolve(body) }) }) @@ -37,7 +37,7 @@ const printObject = data => new Promise( resolve => { data = JSON.parse(data) const str = JSON.stringify(data, null, indent) console.log(str) - resolve() + return resolve() }) const exit = () => new Promise( () => { @@ -48,6 +48,7 @@ getInput('enter base currency') .then(checkValidCurrencyCode) .then(getData) .then(printObject) + .then(() => getInput('convert to')) .then(exit) - .catch( err => console.error(`error: ${err.message}`)) - .then(exit) + .catch(err => console.error(`error: ${err.message}`)) + .then(exit) diff --git a/exercises/06_code_quality/promises2.js b/exercises/06_code_quality/promises2.js new file mode 100644 index 00000000..0424d3c6 --- /dev/null +++ b/exercises/06_code_quality/promises2.js @@ -0,0 +1,46 @@ + +'use strict' + +const request = require('request') +const readline = require('readline') +let userDetails + +const getInput = prompt => new Promise( resolve => { + const io = { input: process.stdin, output: process.stdout } + const read = readline.createInterface(io) + read.question(`${prompt}: `, data => { + // console.log(data) + read.close() + return resolve(data) + }) +}) + +const getData = () => new Promise( (resolve, reject) => { + const options = { + url: 'https://api.github.com/users/marktyers', + headers: { + 'User-Agent': 'request' + } + } + // Do async job + request.get(options, (err, resp, body) => { + if (err) reject(err) + else return resolve(JSON.parse(body)) + }) +}) + +function main() { + getData() + .then(result => { + userDetails = result + console.log('Initialized user details') + console.log(userDetails) + }) + .then(() => getInput('enter base currency')) + .then(data => console.log(data)) + .then(() => getInput('convert to')) + .then(data => console.log(data)) + .catch(err => console.log(err)) +} + +main() diff --git a/exercises/06_code_quality/todo/index.js b/exercises/06_code_quality/todo/index.js index 111255db..ee1f2aaa 100644 --- a/exercises/06_code_quality/todo/index.js +++ b/exercises/06_code_quality/todo/index.js @@ -13,17 +13,17 @@ app.use(bodyParser()); app.use(handlebars({ paths: { views: __dirname + "/views" } })); app.use(router.routes()); - var port = 8080 -var items = [] - -// const List = require('./modules/list') -// const list = new List() +const List = require('./modules/list').List +const list = new List() router.get("/", async function(ctx) { try { - var data = {} + var items = [] // you will need to REPLACE this with a call to the 'list' object!!! + console.log(items) + var data = {items} + ctx.render('home', data); } catch(err) { console.log(err.message); ctx.render('home', {msg: err.message}); @@ -33,8 +33,8 @@ router.get("/", async function(ctx) { router.post("/", function(ctx) { try { var body = ctx.request.body; - var data = {item: body.item, qty: body.qty}; - items.push(data); + console.log(body) + // you will need to add a call to the 'list' object!!! ctx.redirect("/"); } catch(err) { console.log(err.message); @@ -45,13 +45,14 @@ router.post("/", function(ctx) { router.get("/delete/:key", function(ctx) { try { console.log(`key: ${ctx.params.key}`); - items.splice(ctx.params.key, 1); - ctx.redirect('/msg=item deleted'); + // you will need to add a call to the 'list' object!!! + ctx.redirect('/?msg=item deleted'); } catch(err) { console.log(err.message); ctx.redirect("/" + err.message); } }); -module.exports = app.listen(port, function() { +module.exports = app.listen(port, async function() { + console.log('listening on port ' + port); }); \ No newline at end of file diff --git a/exercises/07_unit_testing/todo/index.js b/exercises/07_unit_testing/todo/index.js index 86e1c6e0..429e15b2 100644 --- a/exercises/07_unit_testing/todo/index.js +++ b/exercises/07_unit_testing/todo/index.js @@ -46,7 +46,7 @@ router.get('/delete/:key', ctx => { try { console.log(`key: ${ctx.params.key}`) todo.delete(ctx.params.key) - ctx.redirect('/msg=item deleted') + ctx.redirect('/?msg=item deleted') } catch(err) { console.log(err.message) ctx.redirect(`/${err.message}`) diff --git a/exercises/07_unit_testing/todo/modules/todo.js b/exercises/07_unit_testing/todo/modules/todo.js index f4fdd7b8..6ce40d22 100644 --- a/exercises/07_unit_testing/todo/modules/todo.js +++ b/exercises/07_unit_testing/todo/modules/todo.js @@ -9,8 +9,22 @@ module.exports.clear = () => { module.exports.add = (item, qty) => { qty = Number(qty) +<<<<<<< HEAD + if(isNaN(qty)) throw new Error('the quantity must be a number') + let flag = false + for(let index in data) { + if (data[index].item === item) { + data[index].qty+= qty + flag = true + } + } + if(flag === false) { + data.push({item: item, qty: qty}) + } +======= if(isNaN(qty)) throw new Error('qty must be a number') data.push({item: item, qty: qty}) +>>>>>>> upstream/master } module.exports.getAll = () => { diff --git a/exercises/07_unit_testing/todo/package.json b/exercises/07_unit_testing/todo/package.json index 8b83c896..ace21014 100644 --- a/exercises/07_unit_testing/todo/package.json +++ b/exercises/07_unit_testing/todo/package.json @@ -10,7 +10,7 @@ "author": "", "license": "ISC", "dependencies": { - "handlebars": "^4.2.0", + "handlebars": "^4.3.0", "http-status-codes": "^1.3.2", "koa": "^2.8.1", "koa-bodyparser": "^4.2.1", diff --git a/exercises/07_unit_testing/todo/unit tests/todo.spec.js b/exercises/07_unit_testing/todo/unit tests/todo.spec.js index 55eb70db..2c46bac0 100644 --- a/exercises/07_unit_testing/todo/unit tests/todo.spec.js +++ b/exercises/07_unit_testing/todo/unit tests/todo.spec.js @@ -41,8 +41,26 @@ describe('add()', () => { done() } }) - + // New test goes HERE! + test('duplicates should increase qty', async done => { + expect.assertions(2) + try { + // ACT + todo.add('bread', 4) + todo.add('bread', 2) + // ASSERT + const count = todo.countItems() + expect(count).toBe(1) + const data = todo.getAll() + const qty = data[0].qty + expect(qty).toEqual(6) + } catch(err) { + done.fail('test failed') + } finally { + done() + } + }) }) diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 00000000..74e9b8d4 --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,19 @@ +0 info it worked if it ends with ok +1 verbose cli [ '/usr/bin/node', '/usr/bin/npm', 'run', 'test' ] +2 info using npm@3.5.2 +3 info using node@v8.10.0 +4 verbose stack Error: ENOENT: no such file or directory, open '/home/anir/foundation/package.json' +5 verbose cwd /home/anir/foundation +6 error Linux 5.0.0-23-generic +7 error argv "/usr/bin/node" "/usr/bin/npm" "run" "test" +8 error node v8.10.0 +9 error npm v3.5.2 +10 error path /home/anir/foundation/package.json +11 error code ENOENT +12 error errno -2 +13 error syscall open +14 error enoent ENOENT: no such file or directory, open '/home/anir/foundation/package.json' +15 error enoent ENOENT: no such file or directory, open '/home/anir/foundation/package.json' +15 error enoent This is most likely not a problem with npm itself +15 error enoent and is related to npm not being able to find a file. +16 verbose exit [ -2, true ]