├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .travis.yml ├── .vscode ├── extensions.json └── settings.json ├── LICENSE ├── README.md ├── Week1 ├── LESSONPLAN.md ├── MAKEME.md ├── README.md ├── assets │ ├── hyf-github-error.png │ └── hyf-github.png └── traversy_ajax_crash │ ├── README.md │ ├── ajax1.html │ ├── ajax1.js │ ├── ajax2.html │ ├── ajax2.js │ ├── ajax3.html │ ├── ajax3.js │ ├── sample.txt │ ├── user.json │ └── users.json ├── Week2 ├── LESSONPLAN.md ├── MAKEME.md ├── README.md ├── assets │ └── week2.png └── traversy_async_crash │ ├── README.md │ ├── callbacks.js │ ├── index.html │ └── promises.js ├── Week3 ├── LESSONPLAN.md ├── MAKEME.md ├── README.md ├── assets │ └── JavaScript3_classes.png ├── traversy_fetch_api │ ├── app.js │ ├── index.html │ ├── sample.txt │ └── users.json └── traversy_oop_crash │ ├── 1_basics_literals.js │ ├── 2_constructor.js │ ├── 3_prototypes.js │ ├── 4_inheritance.js │ ├── 5_object_create.js │ ├── 6_classes.js │ ├── 7_subclasses.js │ ├── README.md │ ├── assets │ ├── 2_constructor.png │ ├── 3_prototypes.png │ ├── 4_inheritance.png │ ├── 6_classes.png │ ├── 7_subclasses.png │ └── function_proto.png │ ├── index-all.html │ ├── index-all.js │ └── index.html ├── apps └── hackyourinfo │ ├── index.js │ ├── package.json │ └── yarn.lock ├── assets ├── API.png ├── OOP.png ├── homework-submission.png ├── javascript3.png ├── pokemon-app.gif ├── stasel.png ├── submit-homework.png ├── trivia-app.gif └── weekflow.png ├── hackyourrepo-app ├── hyf.png ├── index.html ├── script.js └── style.css ├── hand-in-homework-guide.md ├── package-lock.json ├── package.json ├── prettier.config.js ├── test.md └── test ├── jest-puppeteer.config.js ├── jest.config.js ├── package-lock.json ├── package.json └── src ├── config.js ├── fetch-contributors-404.test.js ├── fetch-network-error.test.js ├── fetch-repos-404.test.js ├── helpers.js ├── html-validation.test.js ├── iPhoneX.test.js └── render.test.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["airbnb-base", "prettier"], 3 | "plugins": ["prettier"], 4 | "env": { 5 | "browser": true, 6 | "jest": true 7 | }, 8 | "globals": { 9 | "page": true, 10 | "browser": true, 11 | "context": true, 12 | "jestPuppeteer": true, 13 | "axios": "readonly" 14 | }, 15 | "rules": { 16 | "prettier/prettier": ["error"], 17 | "class-methods-use-this": "off", 18 | "strict": "off", 19 | "no-plusplus": "off", 20 | "linebreak-style": "off", 21 | "no-restricted-syntax": "off", 22 | "no-param-reassign": [ 23 | "error", 24 | { 25 | "props": false 26 | } 27 | ] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | ## For further details please lookup: 5 | ## https://help.github.com/articles/dealing-with-line-endings/#platform-all 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | .netlify 61 | dist/ 62 | iPhoneX.png 63 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '10' 4 | 5 | script: 6 | - npm run lint 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "CoenraadS.bracket-pair-colorizer", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "ritwickdey.LiveServer", 7 | "streetsidesoftware.code-spell-checker", 8 | "techer.open-in-browser" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.formatOnType": true, 4 | "editor.formatOnPaste": true, 5 | "editor.detectIndentation": false, 6 | "editor.tabSize": 2, 7 | "cSpell.words": [ 8 | "READYSTATE", 9 | "Traversy", 10 | "ajaxcrash", 11 | "networkidle", 12 | "remarcmij", 13 | "tabindex", 14 | "whiteframe" 15 | ], 16 | "deno.enable": false 17 | } 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons Attribution 4.0 International License. 2 | To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ 3 | or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED - JavaScript 3 2 | This module has been replace with the Using API's module, find it [here](https://github.com/HackYourFuture/UsingAPIs) 3 | 4 | ```Welcome to JavaScript3! Congratulations on making it this far. You're well on your way to the top! 5 | 6 | A big part of being a programmer means moving data from one place to another. It also means working with other people's software. In this module you'll be learning about one of the core things of what makes a web developer: working with APIs! 7 | 8 | On top of that you'll also learn how to think differently about _how_ you write your programs. Like in any field, once you've mastered a particular way of doing things you start thinking about how it could be done in a smarter, different way. In programming we call these `paradigms` and in this module you'll learn one such paradigm: Object-Oriented Programming! 9 | 10 | ## Before you start 11 | 12 | In the following weeks we will be using a "style guide" to help you write _"clean code"_. Because code is not only meant to be run by computers, but also to be read by humans (your colleagues, and the future version of you), it's best to make your code good. If your code is readable and nicely formatted, you're doing your colleages (and future you) a great service. The idea of a "style guide" comes from visual design, where companies often have a "visual style". For example, watch the following video to get an idea of this: 13 | 14 | ### Setup Style Guide 15 | 16 | Similar to how designers have style guides for their design work, programmers often have "programming style guides". This is set of rules to follow when writing/formatting your code. The styleguide we'll be using is the one from Airbnb: 17 | 18 | - [Front-end Style Guides](https://fronteers.nl/congres/2015/sessions/front-end-style-guides-anna-debenham) 19 | 20 | The style guide we'll be using is the one from Airbnb: 21 | 22 | - [Airbnb JavaScript Style Guide](https://github.com/airbnb/javascript) 23 | 24 | While you do not need to read this guide in detail, it is recommended that you look at sections 1-8, 12-13, 15-21 and 23. 25 | 26 | We also have tools that can automatically check whether your code is correctly formatted according to a style guide. These tools are called "linters". We will be using the JavaScript linter called "ESLint". The following packages are already added to this repository's `package.json`: 27 | 28 | The required packages you need to install before you write code according to the style guide are the following: 29 | 30 | `json 31 | "eslint" 32 | "eslint-config-airbnb-base" 33 | "eslint-config-prettier" 34 | "eslint-plugin-import" 35 | "eslint-plugin-prettier" 36 | "prettier" 37 | ` 38 | 39 | They are already in this repository's `package.json` so all you have to do now to prepare is to execute the following command at the root of this module directory: 40 | 41 | `md 42 | npm install 43 | ` 44 | 45 | ### Forking the right repository 46 | 47 | Before you start with the homework, make sure you've made a fork of the right repository: [HackYourHomework/JavaScript3](https://www.github.com/hackyourhomework/javascript3)). Once you've cloned it to your computer you can proceed by making GIT branches for each week. Start at the `master` branch and execute the following (note that they're 3 different commands): 48 | 49 | `bash 50 | foo@bar:~$ git branch week1-YOURNAME 51 | foo@bar:~$ git branch week2-YOURNAME 52 | foo@bar:~$ git branch week3-YOURNAME 53 | ` 54 | 55 | Then execute `git checkout week1-YOURNAME` and you can get started! 56 | 57 | If you have any questions or if something is not entirely clear ¯\_(ツ)\_/¯, please ask/comment on Slack! 58 | 59 | ## Learning goals 60 | 61 | In order to successfully complete this module you will need to master the following: 62 | 63 | - Learn what an `Application Programming Interface` (API) is 64 | - Catch up on the `history of JavaScript` 65 | - Understand how to write more readable `asynchronous JavaScript` 66 | - Connect with different `public APIs` 67 | - Build a `Single Page Application` (SPA) 68 | - Work with pre-existing code 69 | - Learn about `Object-Oriented Programming` 70 | 71 | ## How to use this repository 72 | 73 | ### Repository content 74 | 75 | This repository consists of 3 essential parts: 76 | 77 | 1. `README`: this document contains all the required theory you need to understand **while** working on the homework. It contains not only the right resources to learn about the concepts, but also lectures done by HackYourFuture teachers. This is the **first thing** you should start with every week 78 | 2. `MAKEME`: this document contains the instructions for each week's homework. Start with the exercises rather quickly, so that you can ground the concepts you read about earlier. 79 | 3. `LESSONPLAN`: this document is meant for teachers as a reference. However, as a student don't be shy to take a look at it as well! 80 | 81 | ### How to study 82 | 83 | Let's say you are just starting out with the JavaScript3 module. This is what you do... 84 | 85 | 1. The week always starts on **Wednesday**. First thing you'll do is open the `README.md` for that week. For the first week of `JavaScript3`, that would be [Week1 Reading](/Week1/README.md) 86 | 2. You spend **Wednesday** and **Thursday** going over the resources and try to get a basic understanding of the concepts. In the meanwhile, you'll also implement any feedback you got on last week's homework (from the JavaScript2 module) 87 | 3. On **Friday** you start with the homework, found in the `MAKEME.md`. For the first week of `JavaScript3`, that would be [Week1 Homework](/Week1/MAKEME.md) 88 | 4. You spend **Friday** and **Saturday** playing around with the exercises and write down any questions you might have 89 | 5. **DEADLINE 1**: You'll submit any questions you might have before **Saturday 23.59**, in the class channel 90 | 6. On **Sunday** you'll attend class. It'll be of the Q&A format, meaning that there will be no new material. Instead your questions shall be discussed and you can learn from others 91 | 7. You spend **Monday** and **Tuesday** finalizing your homework 92 | 8. **DEADLINE 2**: You submit your homework to the right channels (GitHub) before **Tuesday 23.59**. If you can't make it on time, please communicate it with your mentor 93 | 9. Start the new week by going back to point 1! 94 | 95 | In summary: 96 | 97 | ![Weekflow](assets/weekflow.png) 98 | 99 | To have a more detailed overview of the guidelines, please read [this document](https://docs.google.com/document/d/1JUaEbxMQTyljAPFsWIbbLwwvvIXZ0VCHmCCN8RaeVIc/edit?usp=sharing) or ask your mentor/class on Slack! 100 | 101 | ### Video lectures 102 | 103 | For each module HackYourFuture provides you with video lectures. These are made by experienced software developers who know what they're talking about. The main teacher for this module will be [Stasel Seldin](https://hackyourfuture.slack.com/team/UQJGC1MSL): senior iOS developer! 104 | 105 | You can find out more about him here: 106 | 107 | - [GitHub](https://github.com/Stasel) 108 | - [@Stasel on Slack](https://hackyourfuture.slack.com/team/UQJGC1MSL) 109 | 110 | Learn from Stasel in the following playlist of videos he has made for you! (Click on the image to open the link) 111 | 112 | HYF Video 113 | 114 | ## Planning 115 | 116 | | Week | Topic | Reading Materials | Homework | Lesson Plan | 117 | | ---- | ------------------------------------------------------------------------------------------- | ------------------------------ | ------------------------------- | -------------------------------------- | 118 | | 1. | Application Programming Interface (API), AJAX, Modules & Libraries | [Reading W1](/Week1/README.md) | [Homework W1](/Week1/MAKEME.md) | [Lesson Plan W1](/Week1/LESSONPLAN.md) | 119 | | 2. | Promises, Fetch API, JavaScript Versions, 'this' keyword, Arrow functions | [Reading W2](/Week2/README.md) | [Homework W2](/Week2/MAKEME.md) | [Lesson Plan W1](/Week2/LESSONPLAN.md) | 120 | | 3. | Object-Oriented Programming (OOP), ES6 Classes, Async/await, Thinking like a programmer III | [Reading W3](/Week3/README.md) | [Homework W3](/Week3/MAKEME.md) | [Lesson Plan W1](/Week3/LESSONPLAN.md) | 121 | | 4. | Final JavaScript Test | [Details](test.md) | - | - | 122 | 123 | ## Finished? 124 | 125 | Did you finish the module? High five! 126 | 127 | If you feel ready for the next challenge, click [here](https://www.github.com/HackYourFuture/Node.js) to go to Node.js! 128 | 129 | _The HackYourFuture curriculum is subject to CC BY copyright. This means you can freely use our materials, but just make sure to give us credit for it :)_ 130 | 131 | Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License.``` 132 | -------------------------------------------------------------------------------- /Week1/LESSONPLAN.md: -------------------------------------------------------------------------------- 1 | # Lesson Plan JavaScript3 Week 1 2 | 3 | ## Agenda 4 | 5 | The purpose of this class is to introduce to the student: 6 | 7 | - What are `APIs` and how to interact with them 8 | - What is `AJAX` and how to apply it (`XMLHttpRequest`) 9 | - How to use libraries (`axios`) 10 | 11 | ## Core concepts 12 | 13 | FIRST HALF (12.00 - 13.30) 14 | 15 | ## 1. What are APIs and how to interact with them 16 | 17 | ### Explanation 18 | - APIs are created by providers and used by consumers (BE provider, FE consumer) 19 | - Part of an application that can be communicated with from an outside source 20 | - Connect to it using "endpoints" 21 | - Software well-known APIs (Fb APIs, Twitter APIs, Maps APIs, weather APIs); 22 | - API doesn't care which language or technology is used in the consumer or the provider 23 | 24 | #### Types of APIs: 25 | - Private: for employees only under a company network for internal use. 26 | - Semi-private: for clients who paid for the API. 27 | - Public: for everyone on the web. 28 | 29 | #### Architecture styles of API: 30 | - Single purpose: API that gives a direct and specific service. 31 | - Aggregated API: one API as a wrapper for multiple APIs. 32 | - Web services API: punch of APIs working together to forma whole app. 33 | 34 | #### Basic structure of REST API 35 | 36 | - Endpoint: https://api.example.com 37 | - Endpoint with version: https://api.example.com/v1 38 | - Resources: 39 | * https://api.example.com/v1/users 40 | * https://api.example.com/v1/users/create 41 | * https://api.example.com/v1/users/1 42 | * https://api.example.com/v1/users/1/edit 43 | - Query params: 44 | * https://api.example.com/v1/users?limit=10 45 | ### Example 46 | - Give real life example like (Devices like TV, any machine + electricity power socket interface which provides power to any external device) 47 | 48 | ### Excercise 49 | 50 | ### Essence 51 | - Mostly used to request data from some service 52 | - Communication between software and user needs UI interface but software and software needs API as an interface. 53 | 54 | ## 2. What is `AJAX` and how to apply it (`XMLHttpRequest`) 55 | 56 | ### Explanation 57 | - Before AJAX all page reload for all requests, via refreshing the url in the address bar with the new resource. 58 | - It's a technique, not a technology 59 | - `AJAX` stands for Asynchronous JavaScript and XML 60 | - Nowadays we use `JSON` instead of `XML` 61 | - Fetch data without reloading the page 62 | - The XMLHttpRequest API is defined in the browser (window.XMLHttpRequest) 63 | ### Example 64 | Example using the XMLHttpRequest 65 | 66 | ```javascript 67 | const oReq = new XMLHttpRequest(); 68 | oReq.open('GET', `https://api.openweathermap.org/data/2.5/weather?q=${cityName}`); 69 | oReq.send(); 70 | oReq.addEventListener('load', function (event) { 71 | const data = JSON.parse(this.response); 72 | if (data.cod >= 400) { 73 | // error 74 | console.log(data.message); 75 | } else { 76 | //success 77 | console.log(data.coord.lat); 78 | } 79 | }); 80 | 81 | // or another way of getting data 82 | oReq.load = function (event) { 83 | // use oReq.response or this.response 84 | const data = JSON.parse(this.response); 85 | if (data.cod >= 400) { 86 | // error 87 | console.log(data.message); 88 | } else { 89 | //success 90 | console.log(data.coord.lat); 91 | } 92 | }; 93 | 94 | ``` 95 | 96 | ### Excercise 97 | 98 | Steps of doing the following example:- 99 | ** Install the live server plugin in VS (go to plugins -> live server -> install) 100 | 1. Create self-invoked function to wrap your code 101 | 2. Create an object instance of `XMLHttpRequest` 102 | 3. Call the `open` function to fill it with the Request URL and the request Method 103 | 4. Call the `send` function to make the request 104 | 5. Add event listener with a callback for the sucess event `load` 105 | 106 | ### Essence 107 | 108 | SECOND HALF (14.00 - 16.00) 109 | 110 | ## 3. How to use libraries (`axios`) 111 | 112 | ### Explanation 113 | - A library is a code solution a developer (or a team) has written to a common problem 114 | - Usually open-source 115 | - Helps to solve a problem within an application 116 | - Read the documentation on how to use it 117 | ### Example 118 | Same example but using axios 119 | ```javascript 120 | axios 121 | .get(`https://api.openweathermap.org/data/2.5/weather?q=${cityName}`) 122 | .then(function (response) { 123 | // handle success 124 | console.log(response.data); 125 | }).catch(function (error) { 126 | // handle error 127 | console.log(error); 128 | }).finally(function () { 129 | // always be executed 130 | console.log('I am always here') 131 | }); 132 | ``` 133 | 134 | > Note: Give example at the end with binding/showing these data in a DOM element like a
or a list instead of only showing them on the console using console.log. 135 | 136 | ### Excercise 137 | ### Essence 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /Week1/MAKEME.md: -------------------------------------------------------------------------------- 1 | # Homework JavaScript3 Week 1 2 | 3 | ## **Todo list** 4 | 5 | 1. Practice the concepts 6 | 2. JavaScript exercises 7 | 3. Code along 8 | 4. PROJECT: Hack Your Repo I 9 | 10 | ## **1. Practice the concepts** 11 | 12 | This week's concepts can be challenging, therefore let's get an easy introduction using some interactive exercises! Check the following resources out and start practicing: 13 | 14 | - [Learn JavaScript: Requests](https://www.codecademy.com/learn/introduction-to-javascript/modules/intermediate-javascript-requests) 15 | 16 | ## **2. JavaScript exercises** 17 | 18 | > Inside of your `JavaScript3` fork and inside of the `Week1` folder, create a folder called `homework`. Inside of that folder, create a folder called `js-exercises`. For all the following exercises create a new `.js` file in that folder (3 files in total). Make sure the name of each file reflects its content: for example, the filename for exercise one could be `getRandomUser.js`. 19 | 20 | **Exercise 1: Who do we have here?** 21 | 22 | Wouldn't it cool to make a new friend with just the click of a button? 23 | 24 | Write a function that makes a HTTP Request to `https://www.randomuser.me/api` 25 | 26 | - Inside the JavaScript file write two functions: one with `XMLHttpRequest`, and the other with `axios` 27 | - Each function should make a HTTP Request to the given endpoint: `https://www.randomuser.me/api` 28 | - Log the received data to the console 29 | - Incorporate error handling: log to the console the error message 30 | 31 | **Exercise 2: Programmer humor** 32 | 33 | Who knew programmers could be funny? 34 | 35 | Write a function that makes a HTTP Request to `https://xkcd.now.sh/?comic=latest` 36 | 37 | - Inside the same file write two programs: one with `XMLHttpRequest`, and the other with `axios` 38 | - Each function should make a HTTP Request to the given endpoint: `https://xkcd.now.sh/?comic=latest` 39 | - Log the received data to the console 40 | - Render the `img` property into an `` tag in the DOM 41 | - Incorporate error handling: log to the console the error message 42 | 43 | **Exercise 3: Dog photo gallery** 44 | 45 | Let's make a randomized dog photo gallery! 46 | 47 | Write a function that makes a HTTP Request to `https://dog.ceo/api/breeds/image/random`. It should trigger after clicking a button in your webpage. Every time the button is clicked it should append a new dog image to the DOM. 48 | 49 | - Create an `index.html` file that will display your random image 50 | - Add 2 `
34 |
35 | 40 |
41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Week3/traversy_fetch_api/sample.txt: -------------------------------------------------------------------------------- 1 | I am a sample text file -------------------------------------------------------------------------------- /Week3/traversy_fetch_api/users.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id":1, 4 | "name":"Rick", 5 | "email":"rick@gmail.com" 6 | }, 7 | { 8 | "id":2, 9 | "name":"Glenn", 10 | "email":"glenn@gmail.com" 11 | }, 12 | { 13 | "id":3, 14 | "name":"Negan", 15 | "email":"negan@gmail.com" 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/1_basics_literals.js: -------------------------------------------------------------------------------- 1 | // Object Literal 2 | const book1 = { 3 | title: 'Book One', 4 | author: 'John Doe', 5 | year: 2013, 6 | getSummary: function() { 7 | return `${this.title} was written by ${this.author} in ${this.year}.`; 8 | }, 9 | }; 10 | 11 | const book2 = { 12 | title: 'Book Two', 13 | author: 'Jane Doe', 14 | year: 2016, 15 | getSummary: function() { 16 | return `${this.title} was written by ${this.author} in ${this.year}.`; 17 | }, 18 | }; 19 | 20 | console.log(book1.getSummary()); 21 | console.log(book2.getSummary()); 22 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/2_constructor.js: -------------------------------------------------------------------------------- 1 | // Constructor 2 | function Book(title, author, year) { 3 | this.title = title; 4 | this.author = author; 5 | this.year = year; 6 | this.getSummary = function() { 7 | return `${this.title} was written by ${this.author} in ${this.year}.`; 8 | }; 9 | } 10 | 11 | // Instantiate an Object 12 | const book1 = new Book('Book One', 'John Doe', 2013); 13 | const book2 = new Book('Book Two', 'Jane Doe', 2016); 14 | 15 | console.log(book1.getSummary()); 16 | console.log(book2.getSummary()); 17 | 18 | console.log(book1); 19 | 20 | book1.turnPage(); 21 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/3_prototypes.js: -------------------------------------------------------------------------------- 1 | // Constructor 2 | function Book(title, author, year) { 3 | this.title = title; 4 | this.author = author; 5 | this.year = year; 6 | } 7 | 8 | Book.prototype.getSummary = function() { 9 | return `${this.title} was written by ${this.author} in ${this.year}.`; 10 | }; 11 | 12 | Book.prototype.getAge = function() { 13 | const years = new Date().getFullYear() - this.year; 14 | return `${this.title} is ${years} years old.`; 15 | }; 16 | 17 | // Revise / Change Year 18 | Book.prototype.revise = function(newYear) { 19 | this.year = newYear; 20 | this.revised = true; 21 | }; 22 | 23 | // Instantiate an Object 24 | const book1 = new Book('Book One', 'John Doe', 2013); 25 | const book2 = new Book('Book Two', 'Jane Doe', 2016); 26 | 27 | console.log(book1.getSummary()); 28 | console.log(book2.getSummary()); 29 | 30 | console.log(book2); 31 | book2.revise(2018); 32 | console.log(book2); 33 | 34 | console.log(book2.getAge()); 35 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/4_inheritance.js: -------------------------------------------------------------------------------- 1 | // Book Constructor 2 | function Book(title, author, year) { 3 | this.title = title; 4 | this.author = author; 5 | this.year = year; 6 | } 7 | 8 | Book.prototype.getSummary = function() { 9 | return `${this.title} was written by ${this.author} in ${this.year}.`; 10 | }; 11 | 12 | Book.prototype.getAge = function() { 13 | const years = new Date().getFullYear() - this.year; 14 | return `${this.title} is ${years} years old.`; 15 | }; 16 | 17 | // Magazine Constructor 18 | function Magazine(title, author, year, month) { 19 | Book.call(this, title, author, year); 20 | this.month = month; 21 | } 22 | 23 | // Inherit Prototype 24 | Magazine.prototype = Object.create(Book.prototype); 25 | 26 | // Use Magazine Constructor 27 | Magazine.prototype.constructor = Magazine; 28 | 29 | Magazine.prototype.updateMonth = function(month) { 30 | this.month = month; 31 | }; 32 | 33 | // Instantiate Magazine Object 34 | const mag1 = new Magazine('Mag One', 'John Doe', 2018, 'Jan'); 35 | 36 | console.log(mag1); 37 | console.log(mag1.getSummary()); 38 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/5_object_create.js: -------------------------------------------------------------------------------- 1 | // Object of Protos 2 | const bookProtos = { 3 | getSummary: function() { 4 | return `${this.title} was written by ${this.author} in ${this.year}.`; 5 | }, 6 | getAge: function() { 7 | const years = new Date().getFullYear() - this.year; 8 | return `${this.title} is ${years} years old.`; 9 | }, 10 | }; 11 | 12 | // Create Object 13 | const book1 = Object.create(bookProtos); 14 | book1.title = 'Book One'; 15 | book1.author = 'John Doe'; 16 | book1.year = 2013; 17 | 18 | const book2 = Object.create(bookProtos, { 19 | title: { value: 'Book Two' }, 20 | author: { value: 'Jane Doe' }, 21 | year: { value: 2016 }, 22 | }); 23 | 24 | console.log(book1); 25 | console.log(book2); 26 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/6_classes.js: -------------------------------------------------------------------------------- 1 | class Book { 2 | constructor(title, author, year) { 3 | this.title = title; 4 | this.author = author; 5 | this.year = year; 6 | } 7 | 8 | getSummary() { 9 | return `${this.title} was written by ${this.author} in ${this.year}.`; 10 | } 11 | 12 | getAge() { 13 | const years = new Date().getFullYear() - this.year; 14 | return `${this.title} is ${years} years old.`; 15 | } 16 | 17 | revise(newYear) { 18 | this.year = newYear; 19 | this.revised = true; 20 | } 21 | 22 | static topBookStore() { 23 | return 'Barnes & Noble'; 24 | } 25 | } 26 | 27 | // Instantiate an Object 28 | const book1 = new Book('Book One', 'John Doe', 2013); 29 | const book2 = new Book('Book Two', 'Jane Doe', 2016); 30 | 31 | console.log(book1); 32 | 33 | console.log(book1.getSummary()); 34 | console.log(book2.getSummary()); 35 | 36 | console.log(book2); 37 | book2.revise(2018); 38 | console.log(book2); 39 | 40 | console.log(book2.getAge()); 41 | 42 | console.log(Book.topBookStore()); 43 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/7_subclasses.js: -------------------------------------------------------------------------------- 1 | class Book { 2 | constructor(title, author, year) { 3 | this.title = title; 4 | this.author = author; 5 | this.year = year; 6 | } 7 | 8 | getSummary() { 9 | return `${this.title} was written by ${this.author} in ${this.year}.`; 10 | } 11 | } 12 | 13 | class Magazine extends Book { 14 | constructor(title, author, year, month) { 15 | super(title, author, year); 16 | this.month = month; 17 | } 18 | 19 | updateMonth(month) { 20 | this.month = month; 21 | } 22 | } 23 | 24 | // Instantiate Magazine Object 25 | const mag1 = new Magazine('Mag One', 'John Doe', 2018, 'Jan'); 26 | 27 | console.log(mag1); 28 | console.log(mag1.getSummary()); 29 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/README.md: -------------------------------------------------------------------------------- 1 | # OOP Crash Course 2 | 3 | This README is a companion to Brad Traversy's YouTube [JavaScript OOP Crash Course (ES5 & ES6)](https://www.youtube.com/watch?v=vDJpGenyHaA&t=1055s). It describes how one of the key concepts of Object Oriented Programming, **inheritance**, is implemented in JavaScript. Both explicit (pre-ES6) **prototype-based inheritance** as well as ES6 **classes-based inheritance** is covered. However, the principles of OOP itself are not discussed in this document. 4 | 5 | Note that it is not necessary in your daily programming to have full knowledge of all the finer details of prototypal inheritance. As can be seen in this document those details can quickly become rather complex. An overall awareness of the concept of a **prototype** chain as the underlying infrastructure for supporting inheritance will suffice. 6 | 7 | What _is_ important is that you understand the **ES6 class syntax** and how to implement a **class inheritance** with the **extends** keyword (see **6_classes** and **7_subclasses** below). 8 | 9 | >Note: The names of the sections below correspond to the equally named JavaScript example files. 10 | 11 | To run the code: 12 | 13 | 1. Open **index-all.html** by right-clicking the file in the VSCode Explorer and select **Open with Live Server**. 14 | 2. Open the Chrome Developer Tools console. 15 | 3. Select the file to run from the select box. 16 | 17 | To examine a particular example in the Chrome Developer Tools, modify **index.html** to load the desired JavaScript file and open **index.html** in the browser. 18 | 19 | ## 1_basics_literals 20 | 21 | This is the most direct way of 'manually' creating objects and does not introduce any new concepts. 22 | 23 | ## 2_constructors 24 | 25 | Prior to ES6, prototypal inheritance required the use of **constructor** functions. These are regular JavaScript functions that are intended to be used in combination with the `new` keyword. By convention, constructor function names start with an upper case letter (CamelCase). 26 | 27 | When a function is called with `new`, its `this` value will be set to an empty object. By default, this empty object is linked through its **prototype chain** to [`Object.prototype`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#Object_instances_and_Object_prototype_object) (which in itself is an object). That is why methods such as `.hasOwnProperty()` and `.toString()` can be called on _any_ object: they are defined on `Object.prototype`. This is illustrated in Figure 1 below which corresponds to the following code snippet: 28 | 29 | ```js 30 | function Book(title, author, year) { 31 | this.title = title; 32 | this.author = author; 33 | this.year = year; 34 | this.getSummary = function() { 35 | //... 36 | }; 37 | } 38 | 39 | const book1 = new Book('Book One', 'John Doe', 2013); 40 | const book2 = new Book('Book Two', 'Jane Doe', 2016); 41 | ``` 42 | 43 | In this example, each **Book** object gets, in addition to its data properties, its own copy of the `.getSummary()` method. This is wasteful in both memory space and execution time. Instead, shared functions should be assigned to the prototype of the constructor function, as will be shown in the next section. 44 | 45 | 46 | ![2_constructor](./assets/2_constructor.png) 47 | Figure 1. Prototypal linkage and the prototype chain. 48 | 49 | Note that all object instances created with the same constructor function share a single copy of the function's prototype object: 50 | 51 | ```js 52 | book1.__proto__ === Book.prototype // true 53 | book2.__proto__ === Book.prototype // true 54 | ``` 55 | 56 | Output from `console.log(book1)`: 57 | 58 | ``` 59 | Book {title: "Book One", author: "John Doe", year: 2013, getSummary: ƒ} 60 | author: "John Doe" 61 | getSummary: ƒ () 62 | title: "Book One" 63 | year: 2013 64 | __proto__: 65 | constructor: ƒ Book(title, author, year) 66 | __proto__: 67 | constructor: ƒ Object() 68 | hasOwnProperty: ƒ hasOwnProperty() 69 | isPrototypeOf: ƒ isPrototypeOf() 70 | ... 71 | ``` 72 | 73 | ## 3_prototypes 74 | 75 | Functions assigned to the prototype of the constructor function are shared across all object instances created with that same constructor function, as shown in Figure 2. 76 | 77 | ```js 78 | function Book(title, author, year) { 79 | this.author = author; 80 | this.title = title; 81 | this.year = year; 82 | } 83 | 84 | Book.prototype.getSummary = function() { 85 | //... 86 | }; 87 | 88 | Book.prototype.getAge = function() { 89 | //... 90 | }; 91 | 92 | Book.prototype.revise = function(newYear) { 93 | //... 94 | }; 95 | 96 | const book1 = new Book('Book One', 'John Doe', 2013); 97 | const book2 = new Book('Book Two', 'Jane Doe', 2016); 98 | ``` 99 | 100 | ![3_prototypes](./assets/3_prototypes.png) 101 | Figure 2. Methods defined in the prototype of the constructor function are shared by all object instances. 102 | 103 | When calling a method (using dot notation) on an object, JavaScript will first look on the object itself for the method. If not found, it will inspect the **prototype** of the object (using its `__proto__` property). If still not found it will go to the next prototype in the chain, and so on, ultimately arriving at `Object.prototype` for a final inspection. 104 | 105 | For instance, the method `.getSummary()` is not found on the `book1` object. Following the prototype chain, JavaScript finds it on `Book.prototype`. In another example, `.toString()` is not found on the `book1` object, nor on its `Book.prototype`. It _is_ however found on `Object.prototype`. 106 | 107 | But if we attempt to call a `.turnPage()` method on `book1` JavaScript will not find it on the object itself, nor anywhere on its prototype chain. Consequently, JavaScript will throw a run time error: 108 | 109 | ```js 110 | book1.turnPage(); // Uncaught TypeError: book1.turnPage is not a function 111 | ``` 112 | 113 | Output from `console.log(book1)`: 114 | 115 | ``` 116 | Book {title: "Book One", author: "John Doe", year: 2013} 117 | author: "John Doe" 118 | title: "Book One" 119 | year: 2013 120 | __proto__: 121 | getAge: ƒ () 122 | getSummary: ƒ () 123 | revise: ƒ (newYear) 124 | constructor: ƒ Book(title, author, year) 125 | __proto__: 126 | constructor: ƒ Object() 127 | hasOwnProperty: ƒ hasOwnProperty() 128 | isPrototypeOf: ƒ isPrototypeOf() 129 | ... 130 | ``` 131 | 132 | ## 4_inheritance 133 | 134 | An object can inherit behaviour from another object through prototypal linkage. In this example, a **Magazine** object becomes an extended version of a **Book** object. All methods from the base object are also accessible from the inheriting object and both object use a shared **this** value. 135 | 136 | ```js 137 | function Book(title, author, year) { 138 | this.title = title; 139 | this.author = author; 140 | this.year = year; 141 | } 142 | 143 | Book.prototype.getSummary = function() { 144 | //... 145 | }; 146 | 147 | Book.prototype.getAge = function() { 148 | //... 149 | }; 150 | 151 | function Magazine(title, author, year, month) { 152 | Book.call(this, title, author, year); 153 | this.month = month; 154 | } 155 | 156 | Magazine.prototype = Object.create(Book.prototype); 157 | Magazine.prototype.constructor = Magazine; 158 | 159 | Magazine.prototype.updateMonth = function(month) { 160 | //... 161 | }; 162 | 163 | const mag1 = new Magazine('Mag One', 'John Doe', 2018, 'Jan'); 164 | ``` 165 | 166 | 167 | Firstly, remember that when a function is called with `new`, its `this` value is initialized with an empty object (prototype linked to `Object.prototype`). 168 | 169 | In order to initialize the base object (in this example **Book**), its constructor function must be called, with the `this` value set to that of the calling constructor (here, **Magazine**). 170 | 171 | This is done with the [Function.prototype.call()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/call) method (see Figure 4 below), passing the `this` value of the calling constructor as its first argument. If the called constructor expects arguments they are passed as additional arguments following the `this` value. In this example, the called constructor for **Book** expects the arguments `title`, `author` and `year`. 172 | 173 | ```js 174 | function Magazine(title, author, year, month) { 175 | Book.call(this, title, author, year); 176 | //... 177 | } 178 | ``` 179 | 180 | Next, the prototype of the calling constructor must be linked to the called constructor. This is done with the help of [Object.create()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create). 181 | 182 | ```js 183 | Magazine.prototype = Object.create(Book.prototype); 184 | ``` 185 | 186 | Finally, we must update the `.constructor` property of the prototype to point to the correct constructor function. 187 | 188 | ```js 189 | Magazine.prototype.constructor = Magazine; 190 | ``` 191 | 192 | With all this, the prototypal linkage is complete, as shown in Figure 3 below. 193 | 194 | ![4_inheritance](./assets/4_inheritance.png) 195 | Figure 3. Prototype chain from explicit prototypal inheritance. 196 | 197 | From [MDN instanceof](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/instanceof): _The **instanceof operator** tests whether the prototype property of a constructor appears anywhere in the prototype chain of an object._ 198 | 199 | ```js 200 | console.log(mag1 instanceof Magazine); // true 201 | console.log(mag1 instanceof Book); // true 202 | console.log(mag1 instanceof Object); // true 203 | ``` 204 | 205 | Output from `console.log(mag1)`: 206 | 207 | ``` 208 | Magazine {title: "Mag One", author: "Jon Doe", year: 2018, month: "Jan"} 209 | author: "John Doe" 210 | month: "Jan" 211 | title: "Mag One" 212 | year: 2018 213 | __proto__: Book 214 | constructor: ƒ Magazine(title, author, year, month) 215 | updateMonth: ƒ (month) 216 | __proto__: 217 | getAge: ƒ () 218 | getSummary: ƒ () 219 | constructor: ƒ Book(title, author, year) 220 | __proto__: 221 | constructor: ƒ Object() 222 | hasOwnProperty: ƒ hasOwnProperty() 223 | isPrototypeOf: ƒ isPrototypeOf() 224 | ... 225 | ``` 226 | 227 | ![function_proto](./assets/function_proto.png) 228 | Figure 4. Every JavaScript function is prototype-linked to `Function.prototype`, which in its turn if is linked to `Object.prototype`. A function can have properties and methods, just like any object. 229 | 230 | ```js 231 | console.log(typeof Book === 'function'); // true 232 | console.log(Book instanceof Function); // true 233 | console.log(Book instanceof Object); // true 234 | console.log(Book.__proto__ === Function.prototype); // true 235 | console.log(typeof Function === 'function'); // true 236 | ``` 237 | 238 | ## 5_object_create 239 | 240 | The method of implementing prototypal inheritance demonstrated in this example is uncommon in practice and will not be further discussed here. 241 | 242 | ## 6_classes.js 243 | 244 | The pre-ES6 method of implementing explicit prototypal linkage is rather cumbersome as you may have concluded already. Fortunately, ES6 classes make implementing inheritance far simpler as shown in the code snippets below. They still use prototypal linkage behind the scenes, but using a more elegant syntax, familiar from other object-oriented languages such as Java, C++ and C#. This more palatable class syntax in ES6 JavaScript is sometimes referred to as _syntactic sugar_. 245 | 246 | ```js 247 | class Book { 248 | constructor(title, author, year) { 249 | this.title = title; 250 | this.author = author; 251 | this.year = year; 252 | } 253 | 254 | getSummary() { 255 | //... 256 | } 257 | 258 | getAge() { 259 | //... 260 | } 261 | 262 | revise(newYear) { 263 | //... 264 | } 265 | } 266 | 267 | const book1 = new Book('Book One', 'John Doe', 2013); 268 | const book2 = new Book('Book Two', 'Jane Doe', 2016); 269 | ``` 270 | 271 | Similar to the non-ES6 case, all object instances created from the same class share a single copy of its underlying prototype object: 272 | 273 | ```js 274 | book1.__proto__ === Book.prototype // true 275 | book2.__proto__ === Book.prototype // true 276 | ``` 277 | 278 | ![6_classes](./assets/6_classes.png) 279 | Figure 5. ES6 classes: identical to Figure 1, except that the Book constructor is now a `class`. 280 | 281 | Output from `console.log(book1)`: 282 | 283 | ``` 284 | Book {title: "Book One", author: "John Doe", year: 2013} 285 | author: "John Doe" 286 | title: "Book One" 287 | year: 2013 288 | __proto__: 289 | constructor: class Book 290 | getAge: ƒ getAge() 291 | getSummary: ƒ getSummary() 292 | revise: ƒ revise(newYear) 293 | __proto__: 294 | constructor: ƒ Object() 295 | hasOwnProperty: ƒ hasOwnProperty() 296 | isPrototypeOf: ƒ isPrototypeOf() 297 | ... 298 | ``` 299 | 300 | 301 | ## 7_subclasses 302 | 303 | Inheriting from a base class is easy using ES6 class syntax. A class can inherit from another class by means of the `extends` keyword. This automatically sets up the required prototypal linkage, as shown in Figure 6 below. 304 | 305 | ```js 306 | class Book { 307 | constructor(title, author, year) { 308 | this.title = title; 309 | this.author = author; 310 | this.year = year; 311 | } 312 | 313 | getSummary() { 314 | //... 315 | } 316 | } 317 | 318 | class Magazine extends Book { 319 | constructor(title, author, year, month) { 320 | super(title, author, year); 321 | this.month = month; 322 | } 323 | 324 | updateMonth(month) { 325 | //... 326 | } 327 | } 328 | 329 | const mag1 = new Magazine('Mag One', 'John Doe', 2018, 'Jan'); 330 | ``` 331 | 332 | In OOP parlance, the class that inherits from the base class (in this example, **Magazine**) is called a **subclass** of the class it extends. The base class itself (here, **Book**) is called the **superclass**. 333 | 334 | ![7_subclasses](./assets/7_subclasses.png) 335 | Figure 6. ES6 class-based inheritance: `extends`. 336 | 337 | ```js 338 | console.log(mag1 instanceof Magazine); // true 339 | console.log(mag1 instanceof Book); // true 340 | console.log(mag1 instanceof Object); // true 341 | ``` 342 | 343 | Output from `console.log(mag1)`: 344 | 345 | ``` 346 | Magazine {title: "Mag One", author: "Jon Doe", year: 2018, month: "Jan"} 347 | author: "Jon Doe" 348 | month: "Jan" 349 | title: "Mag One" 350 | year: 2018 351 | __proto__: Book 352 | constructor: class Magazine 353 | updateMonth: ƒ updateMonth(month) 354 | __proto__: 355 | constructor: class Book 356 | getSummary: ƒ getSummary() 357 | __proto__: 358 | constructor: ƒ Object() 359 | hasOwnProperty: ƒ hasOwnProperty() 360 | isPrototypeOf: ƒ isPrototypeOf() 361 | ... 362 | ``` 363 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/assets/2_constructor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/Week3/traversy_oop_crash/assets/2_constructor.png -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/assets/3_prototypes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/Week3/traversy_oop_crash/assets/3_prototypes.png -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/assets/4_inheritance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/Week3/traversy_oop_crash/assets/4_inheritance.png -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/assets/6_classes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/Week3/traversy_oop_crash/assets/6_classes.png -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/assets/7_subclasses.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/Week3/traversy_oop_crash/assets/7_subclasses.png -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/assets/function_proto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/Week3/traversy_oop_crash/assets/function_proto.png -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/index-all.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Brad Traversy's OOP Crash Course 8 | 9 | 10 |

Brad Traversy's OOP Crash Course

11 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/index-all.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | { 4 | const select = document.getElementById('select'); 5 | select.addEventListener('change', async () => { 6 | console.clear(); 7 | console.log(select.value); 8 | const response = await fetch(select.value); 9 | const code = await response.text(); 10 | // eslint-disable-next-line no-eval 11 | eval(code); 12 | }); 13 | } 14 | -------------------------------------------------------------------------------- /Week3/traversy_oop_crash/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /apps/hackyourinfo/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const fs = require('fs-extra') 3 | const path = require('path') 4 | const bodyParser = require('body-parser') 5 | const cors = require('cors') 6 | 7 | const rootDir = path.resolve(__dirname) 8 | const infoDir = path.join(rootDir, 'info') 9 | 10 | const app = express() 11 | 12 | app.use(bodyParser.json()) 13 | app.use(cors()) 14 | 15 | app.post('/:name.json', async (request, response) => { 16 | try { 17 | const filename = path.join(infoDir, `${request.params.name}.json`) 18 | await fs.ensureDir(infoDir) 19 | await fs.writeFile(filename, JSON.stringify(request.body, null, 2)) 20 | response.statusCode = 200 21 | response.json({ ok: true }) 22 | } catch (error) { 23 | response.statusCode = 500 24 | response.json({ error: "An error occurred" }) 25 | console.error(error) 26 | } 27 | 28 | response.end() 29 | }) 30 | 31 | app.get('/_all.json', async (request, response) => { 32 | try { 33 | await fs.ensureDir(infoDir) 34 | const files = await fs.readdir(infoDir) 35 | const promises = files.map(file => fs.readFile(path.join(infoDir, file), 'utf8').then(raw => [file, raw])) 36 | const raws = await Promise.all(promises) 37 | 38 | const result = {} 39 | for (const [file, raw] of raws) { 40 | result[file.replace(/\.json$/, '')] = JSON.parse(raw) 41 | } 42 | response.json(result) 43 | } catch (error) { 44 | response.statusCode = 500 45 | response.json({ error: "An error occurred" }) 46 | console.error(error) 47 | } 48 | response.end() 49 | }) 50 | 51 | app.get('/:name.json', async (request, response) => { 52 | try { 53 | const filename = path.join(infoDir, `${request.params.name}.json`) 54 | await fs.ensureDir(infoDir) 55 | const raw = await fs.readFile(filename, 'utf8') 56 | response.json(JSON.parse(raw)) 57 | } catch (error) { 58 | if (error.code === 'ENOENT') { 59 | response.statusCode = 404 60 | response.json({ error: "File not found" }) 61 | } else { 62 | response.statusCode = 500 63 | response.json({ error: "An error occurred" }) 64 | console.error(error) 65 | } 66 | } 67 | response.end() 68 | }) 69 | 70 | app.delete('/_all.json', async (request, response) => { 71 | try { 72 | await fs.emptyDir(infoDir) 73 | response.json({ ok: true }) 74 | } catch (error) { 75 | response.statusCode = 500 76 | response.json({ error: "An error occurred" }) 77 | console.error(error) 78 | } 79 | response.end() 80 | }) 81 | 82 | app.delete('/:name.json', async (request, response) => { 83 | try { 84 | await fs.ensureDir(infoDir) 85 | await fs.unlink(path.join(infoDir, `${request.params.name}.json`)) 86 | response.json({ ok: true }) 87 | } catch (error) { 88 | if (error.code !== 'ENOENT') { 89 | response.statusCode = 500 90 | response.json({ error: "An error occurred" }) 91 | console.error(error) 92 | } else { 93 | response.json({ ok: true }) 94 | } 95 | } 96 | response.end() 97 | }) 98 | 99 | app.use((request, response) => { 100 | response.statusCode = 404 101 | response.json({ error: "Not found" }) 102 | response.end() 103 | }) 104 | 105 | app.listen(process.env.PORT || 80) -------------------------------------------------------------------------------- /apps/hackyourinfo/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hackyourinfo", 3 | "version": "1.0.0", 4 | "description": "Small webserver for class collaboration.", 5 | "main": "index.js", 6 | "author": "Joost Lubach", 7 | "license": "MIT", 8 | "private": false, 9 | "dependencies": { 10 | "body-parser": "^1.18.3", 11 | "cors": "^2.8.5", 12 | "express": "^4.16.4", 13 | "fs-extra": "^7.0.1" 14 | }, 15 | "scripts": { 16 | "start": "node ." 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /apps/hackyourinfo/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | accepts@~1.3.5: 6 | version "1.3.5" 7 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" 8 | integrity sha1-63d99gEXI6OxTopywIBcjoZ0a9I= 9 | dependencies: 10 | mime-types "~2.1.18" 11 | negotiator "0.6.1" 12 | 13 | array-flatten@1.1.1: 14 | version "1.1.1" 15 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 16 | integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= 17 | 18 | body-parser@1.18.3, body-parser@^1.18.3: 19 | version "1.18.3" 20 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" 21 | integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ= 22 | dependencies: 23 | bytes "3.0.0" 24 | content-type "~1.0.4" 25 | debug "2.6.9" 26 | depd "~1.1.2" 27 | http-errors "~1.6.3" 28 | iconv-lite "0.4.23" 29 | on-finished "~2.3.0" 30 | qs "6.5.2" 31 | raw-body "2.3.3" 32 | type-is "~1.6.16" 33 | 34 | bytes@3.0.0: 35 | version "3.0.0" 36 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" 37 | integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg= 38 | 39 | content-disposition@0.5.2: 40 | version "0.5.2" 41 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" 42 | integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= 43 | 44 | content-type@~1.0.4: 45 | version "1.0.4" 46 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" 47 | integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== 48 | 49 | cookie-signature@1.0.6: 50 | version "1.0.6" 51 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 52 | integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw= 53 | 54 | cookie@0.3.1: 55 | version "0.3.1" 56 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" 57 | integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s= 58 | 59 | cors@^2.8.5: 60 | version "2.8.5" 61 | resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" 62 | integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== 63 | dependencies: 64 | object-assign "^4" 65 | vary "^1" 66 | 67 | debug@2.6.9: 68 | version "2.6.9" 69 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 70 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 71 | dependencies: 72 | ms "2.0.0" 73 | 74 | depd@~1.1.2: 75 | version "1.1.2" 76 | resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" 77 | integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= 78 | 79 | destroy@~1.0.4: 80 | version "1.0.4" 81 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" 82 | integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= 83 | 84 | ee-first@1.1.1: 85 | version "1.1.1" 86 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 87 | integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0= 88 | 89 | encodeurl@~1.0.2: 90 | version "1.0.2" 91 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 92 | integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= 93 | 94 | escape-html@~1.0.3: 95 | version "1.0.3" 96 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 97 | integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= 98 | 99 | etag@~1.8.1: 100 | version "1.8.1" 101 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 102 | integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= 103 | 104 | express@^4.16.4: 105 | version "4.16.4" 106 | resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e" 107 | integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg== 108 | dependencies: 109 | accepts "~1.3.5" 110 | array-flatten "1.1.1" 111 | body-parser "1.18.3" 112 | content-disposition "0.5.2" 113 | content-type "~1.0.4" 114 | cookie "0.3.1" 115 | cookie-signature "1.0.6" 116 | debug "2.6.9" 117 | depd "~1.1.2" 118 | encodeurl "~1.0.2" 119 | escape-html "~1.0.3" 120 | etag "~1.8.1" 121 | finalhandler "1.1.1" 122 | fresh "0.5.2" 123 | merge-descriptors "1.0.1" 124 | methods "~1.1.2" 125 | on-finished "~2.3.0" 126 | parseurl "~1.3.2" 127 | path-to-regexp "0.1.7" 128 | proxy-addr "~2.0.4" 129 | qs "6.5.2" 130 | range-parser "~1.2.0" 131 | safe-buffer "5.1.2" 132 | send "0.16.2" 133 | serve-static "1.13.2" 134 | setprototypeof "1.1.0" 135 | statuses "~1.4.0" 136 | type-is "~1.6.16" 137 | utils-merge "1.0.1" 138 | vary "~1.1.2" 139 | 140 | finalhandler@1.1.1: 141 | version "1.1.1" 142 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" 143 | integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg== 144 | dependencies: 145 | debug "2.6.9" 146 | encodeurl "~1.0.2" 147 | escape-html "~1.0.3" 148 | on-finished "~2.3.0" 149 | parseurl "~1.3.2" 150 | statuses "~1.4.0" 151 | unpipe "~1.0.0" 152 | 153 | forwarded@~0.1.2: 154 | version "0.1.2" 155 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" 156 | integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= 157 | 158 | fresh@0.5.2: 159 | version "0.5.2" 160 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 161 | integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= 162 | 163 | fs-extra@^7.0.1: 164 | version "7.0.1" 165 | resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" 166 | integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw== 167 | dependencies: 168 | graceful-fs "^4.1.2" 169 | jsonfile "^4.0.0" 170 | universalify "^0.1.0" 171 | 172 | graceful-fs@^4.1.2, graceful-fs@^4.1.6: 173 | version "4.1.15" 174 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" 175 | integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== 176 | 177 | http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: 178 | version "1.6.3" 179 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" 180 | integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0= 181 | dependencies: 182 | depd "~1.1.2" 183 | inherits "2.0.3" 184 | setprototypeof "1.1.0" 185 | statuses ">= 1.4.0 < 2" 186 | 187 | iconv-lite@0.4.23: 188 | version "0.4.23" 189 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" 190 | integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA== 191 | dependencies: 192 | safer-buffer ">= 2.1.2 < 3" 193 | 194 | inherits@2.0.3: 195 | version "2.0.3" 196 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 197 | integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= 198 | 199 | ipaddr.js@1.8.0: 200 | version "1.8.0" 201 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" 202 | integrity sha1-6qM9bd16zo9/b+DJygRA5wZzix4= 203 | 204 | jsonfile@^4.0.0: 205 | version "4.0.0" 206 | resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" 207 | integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= 208 | optionalDependencies: 209 | graceful-fs "^4.1.6" 210 | 211 | media-typer@0.3.0: 212 | version "0.3.0" 213 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 214 | integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= 215 | 216 | merge-descriptors@1.0.1: 217 | version "1.0.1" 218 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 219 | integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E= 220 | 221 | methods@~1.1.2: 222 | version "1.1.2" 223 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 224 | integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= 225 | 226 | mime-db@~1.37.0: 227 | version "1.37.0" 228 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" 229 | integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== 230 | 231 | mime-types@~2.1.18: 232 | version "2.1.21" 233 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96" 234 | integrity sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg== 235 | dependencies: 236 | mime-db "~1.37.0" 237 | 238 | mime@1.4.1: 239 | version "1.4.1" 240 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" 241 | integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== 242 | 243 | ms@2.0.0: 244 | version "2.0.0" 245 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 246 | integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= 247 | 248 | negotiator@0.6.1: 249 | version "0.6.1" 250 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" 251 | integrity sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk= 252 | 253 | object-assign@^4: 254 | version "4.1.1" 255 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" 256 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= 257 | 258 | on-finished@~2.3.0: 259 | version "2.3.0" 260 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" 261 | integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc= 262 | dependencies: 263 | ee-first "1.1.1" 264 | 265 | parseurl@~1.3.2: 266 | version "1.3.2" 267 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" 268 | integrity sha1-/CidTtiZMRlGDBViUyYs3I3mW/M= 269 | 270 | path-to-regexp@0.1.7: 271 | version "0.1.7" 272 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 273 | integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= 274 | 275 | proxy-addr@~2.0.4: 276 | version "2.0.4" 277 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" 278 | integrity sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA== 279 | dependencies: 280 | forwarded "~0.1.2" 281 | ipaddr.js "1.8.0" 282 | 283 | qs@6.5.2: 284 | version "6.5.2" 285 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" 286 | integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== 287 | 288 | range-parser@~1.2.0: 289 | version "1.2.0" 290 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" 291 | integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= 292 | 293 | raw-body@2.3.3: 294 | version "2.3.3" 295 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" 296 | integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw== 297 | dependencies: 298 | bytes "3.0.0" 299 | http-errors "1.6.3" 300 | iconv-lite "0.4.23" 301 | unpipe "1.0.0" 302 | 303 | safe-buffer@5.1.2: 304 | version "5.1.2" 305 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" 306 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== 307 | 308 | "safer-buffer@>= 2.1.2 < 3": 309 | version "2.1.2" 310 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 311 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 312 | 313 | send@0.16.2: 314 | version "0.16.2" 315 | resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" 316 | integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw== 317 | dependencies: 318 | debug "2.6.9" 319 | depd "~1.1.2" 320 | destroy "~1.0.4" 321 | encodeurl "~1.0.2" 322 | escape-html "~1.0.3" 323 | etag "~1.8.1" 324 | fresh "0.5.2" 325 | http-errors "~1.6.2" 326 | mime "1.4.1" 327 | ms "2.0.0" 328 | on-finished "~2.3.0" 329 | range-parser "~1.2.0" 330 | statuses "~1.4.0" 331 | 332 | serve-static@1.13.2: 333 | version "1.13.2" 334 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" 335 | integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw== 336 | dependencies: 337 | encodeurl "~1.0.2" 338 | escape-html "~1.0.3" 339 | parseurl "~1.3.2" 340 | send "0.16.2" 341 | 342 | setprototypeof@1.1.0: 343 | version "1.1.0" 344 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" 345 | integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ== 346 | 347 | "statuses@>= 1.4.0 < 2": 348 | version "1.5.0" 349 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" 350 | integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow= 351 | 352 | statuses@~1.4.0: 353 | version "1.4.0" 354 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" 355 | integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew== 356 | 357 | type-is@~1.6.16: 358 | version "1.6.16" 359 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" 360 | integrity sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q== 361 | dependencies: 362 | media-typer "0.3.0" 363 | mime-types "~2.1.18" 364 | 365 | universalify@^0.1.0: 366 | version "0.1.2" 367 | resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" 368 | integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== 369 | 370 | unpipe@1.0.0, unpipe@~1.0.0: 371 | version "1.0.0" 372 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 373 | integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw= 374 | 375 | utils-merge@1.0.1: 376 | version "1.0.1" 377 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 378 | integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= 379 | 380 | vary@^1, vary@~1.1.2: 381 | version "1.1.2" 382 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 383 | integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= 384 | -------------------------------------------------------------------------------- /assets/API.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/assets/API.png -------------------------------------------------------------------------------- /assets/OOP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/assets/OOP.png -------------------------------------------------------------------------------- /assets/homework-submission.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/assets/homework-submission.png -------------------------------------------------------------------------------- /assets/javascript3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/assets/javascript3.png -------------------------------------------------------------------------------- /assets/pokemon-app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/assets/pokemon-app.gif -------------------------------------------------------------------------------- /assets/stasel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/assets/stasel.png -------------------------------------------------------------------------------- /assets/submit-homework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/assets/submit-homework.png -------------------------------------------------------------------------------- /assets/trivia-app.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/assets/trivia-app.gif -------------------------------------------------------------------------------- /assets/weekflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/assets/weekflow.png -------------------------------------------------------------------------------- /hackyourrepo-app/hyf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HackYourFuture/JavaScript3/b4abc6240353b503ff08bf9b4b000634a36a2622/hackyourrepo-app/hyf.png -------------------------------------------------------------------------------- /hackyourrepo-app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | HackYourRepo 16 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /hackyourrepo-app/script.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | /* 4 | Write here your JavaScript for HackYourRepo! 5 | */ 6 | -------------------------------------------------------------------------------- /hackyourrepo-app/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | Write here your CSS rules for HackYourRepo! 3 | */ 4 | -------------------------------------------------------------------------------- /hand-in-homework-guide.md: -------------------------------------------------------------------------------- 1 | # How to hand in homework 2 | 3 | In this module you'll submit your homework only using GIT and GitHub. 4 | 5 | 1. [GitHub](https://www.github.com/HackYourFuture/JavaScript3) 6 | 7 | ## 1. GitHub homework guide 8 | 9 | HYF Video 10 | 11 | Watch the video (by clicking the image) or go through the following walk-through to learn how to submit your homework: 12 | 13 | ONE TIME ONLY (START OF EVERY MODULE) 14 | 15 | 1. Create a [fork](https://help.github.com/en/articles/fork-a-repo) of the following repository: [HackYourHomework/JavaScript3](https://www.github.com/hackyourhomework/javascript3). You do this by using the `fork` option on the top right 16 | 2. Navigate to the URL of the forked repository (it should be in your personal GitHub account, under "repositories", under the name `/JavaScript3`) 17 | 3. Clone the repository, using SSH, to your local machine. You can do this by typing in `git clone ` in the command line 18 | 4. On your local machine, navigate to the folder using the command line 19 | 5. Make sure you've cloned it correctly by running `git status` and `git remote -v` from the command line 20 | 21 | EVERY WEEK 22 | 23 | 1. Create a new branch for each week you have homework. For example, for the week 1 homework for JavaScript3 create a branch called `week-1-homework-YOUR_NAME` 24 | 2. Inside the week folder, create another folder called `homework`. Create your homework files in there, while on the correct branch 25 | 3. Once you're finished, `add` and `commit` everything. Make the commit message meaningful, for example `finished project for homework week1` 26 | 4. Push the branch to your forked repository (`/JavaScript3`) 27 | 5. On the GitHub page of this repository, click on the `create pull request` button. Make sure the `base repository` is `HackYourHomework/JavaScript3`, on branch master 28 | 6. Give the pull request a title in the following format: 29 | 30 | ```markdown 31 | Homework week 1 32 | ``` 33 | 34 | 7. Submit the pull request from your forked repository branch into the `master` branch of the original repository (`HackYourHomework/JavaScript3`) 35 | 8. Do a little victory dance because you did it! Good job! 36 | 37 | If you have any questions or if something is not entirely clear ¯\\\_(ツ)\_/¯, please ask/comment on Slack! 38 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "javascript3", 3 | "version": "1.0.0", 4 | "description": "Course content for the JavaScript3 module", 5 | "repository": "https://github.com/HackYourFuture/JavaScript3.git", 6 | "scripts": { 7 | "lint": "eslint homework", 8 | "test": "npm run lint" 9 | }, 10 | "author": "HackYourFuture", 11 | "license": "CC-BY-4.0", 12 | "dependencies": { 13 | "eslint": "^6.2.0", 14 | "eslint-config-airbnb-base": "^14.0.0", 15 | "eslint-config-prettier": "^6.0.0", 16 | "eslint-plugin-import": "^2.18.2", 17 | "eslint-plugin-prettier": "^3.1.0", 18 | "prettier": "^1.19.1" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'all', 4 | bracketSpacing: true, 5 | jsxBracketSameLine: false, 6 | tabWidth: 2, 7 | semi: true, 8 | }; 9 | -------------------------------------------------------------------------------- /test.md: -------------------------------------------------------------------------------- 1 | # Final javascript test 2 | 3 | You’ll be doing a final JavaScript test. It will be about all the JavaScript you’ve learned, from JavaScript 1 to JavaScript 3. 4 | 5 | ## Practical information 6 | 7 | - The test will last **2 hours**. 8 | - It will be done on the computer. 9 | - You can’t use your old code. 10 | - There will be 6 or 7 questions. 11 | 12 | ## Purpose of the test 13 | 14 | The purpose of this exam is to test your comprehension of JavaScript 15 | 16 | - The goal for us is to know how solid your knowledge is and if you need any extra assistance throughout the program. 17 | - The goal for you is to test how well enough you master the material so far. 18 | 19 | Like the homework you will get feedback and are expected to improve upon your weak points. 20 | 21 | ## Test material 22 | 23 | The test will be about all main JavaScript concepts we have discussed for the past weeks of JS. The following are the most important that will be tested: 24 | 25 | - `Functions & Variables` 26 | - `Scope` 27 | - `Loops` 28 | - `Callbacks & Promises` 29 | - `Array functions` 30 | - `Conditional statements` 31 | - `DOM manipulations` 32 | - `Fetch & HTTP Requests` 33 | - `Promises & Async/await` 34 | - `Try…catch` 35 | 36 | ## Preparation 37 | 38 | Advice on how to optimally prepare: 39 | 40 | - Find out 2 things about each concepts listed above: (1) how does the basic structure look and (2) what is the most common use case. 41 | - Ask questions through Slack to your teachers and/or your classmates. 42 | - Practice for understanding (why something is the case), NOT just for repetition shake (and hoping ‘you will understand it one day’). 43 | - Make a summary of all the study material. 44 | - After you prepared try to make the sample questions that are in this file. 45 | 46 | ## During the test 47 | 48 | Advise on how to make a test: 49 | 50 | - **Look for low hanging fruit**. Which of the assignments is easiest for you to do? Tackle that one first. 51 | - **When writing your code, write small chunks at a time, testing each time before continuing**. Use temporary console.log statements to show intermediate results (remove when no longer needed). Don't continue until the code written sofar is working correctly. If you write a whole bunch of code without intermediate testing it becomes difficult to pinpoint where issues occur. 52 | - **If you get stuck in an assignment, move on** to the next one. You can always come back later if time permits. 53 | - When writing code for the browser, **always open the Chrome Developer Tools**. Watch out for error messages in the browser console. Inspect the network tab to examine the data returned from a remote API. 54 | - **Don't over-deliver**. If styling is not required by the assignment, skip it. If time permits, you can do it later. The same goes for handling fetch errors. Focus on delivering a minimum working version that meets the requirement. Ticking off a working version again reduces your stress level. Come back later, if time permits, to embellish your solutions. 55 | - **Don't use Google as a replacement for common sense.** 56 | - After finishing an assignment, read the question again to **make sure you actually gave an answer to the question.** 57 | - Before handing in the test, read it all over again to **pick out the small mistakes.** 58 | 59 | ## Sample questions 60 | 61 | 1. Using JavaScript only (adding HTML to index.html is NOT allowed), create a button element (with text "click me!") and an empty image element and add it to the document. When the button is clicked, insert an image URL into the tag and remove the button. Use the following image URL: https://thehub.dk/files/5ad4b4a9f9ac4aa13c3d2d58/logo_upload-6d537cf7e5de664db275b32b3c6ae12d.png 62 | 63 | 2. Make a HTTP Request using the Fetch API or the regular XMLHttpRequest (whichever one you're more comfortable with). Use the following API: https://reqres.in/api/users 64 | Parse the response and then display the "first_name" and "last_name" of the first three users within the DOM (inside an unordered list) 65 | 66 | If there’s anything unclear please let us know. Also, if any of you need additional support, now is the moment to let us know as we could pair you up with someone to answer any questions you may have. 67 | -------------------------------------------------------------------------------- /test/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: { 3 | command: 'serve ../homework', 4 | port: 5000, 5 | }, 6 | launch: { 7 | headless: true, 8 | }, 9 | // could use for example: 1920 x 1080 10 | defaultViewPort: { 11 | width: 800, 12 | height: 600, 13 | }, 14 | }; 15 | -------------------------------------------------------------------------------- /test/jest.config.js: -------------------------------------------------------------------------------- 1 | // For a detailed explanation regarding each configuration property, visit: 2 | // https://jestjs.io/docs/en/configuration.html 3 | 4 | module.exports = { 5 | preset: 'jest-puppeteer', 6 | // All imported modules in your tests should be mocked automatically 7 | // automock: false, 8 | 9 | // Stop running tests after `n` failures 10 | // bail: 0, 11 | 12 | // Respect "browser" field in package.json when resolving modules 13 | // browser: false, 14 | 15 | // The directory where Jest should store its cached dependency information 16 | // cacheDirectory: "/private/var/folders/z9/94jqvx5d6pj83s6_7_n0wjlr0000gn/T/jest_dx", 17 | 18 | // Automatically clear mock calls and instances between every test 19 | // clearMocks: false, 20 | 21 | // Indicates whether the coverage information should be collected while executing the test 22 | // collectCoverage: false, 23 | 24 | // An array of glob patterns indicating a set of files for which coverage information should be collected 25 | // collectCoverageFrom: null, 26 | 27 | // The directory where Jest should output its coverage files 28 | // coverageDirectory: null, 29 | 30 | // An array of regexp pattern strings used to skip coverage collection 31 | // coveragePathIgnorePatterns: [ 32 | // "/node_modules/" 33 | // ], 34 | 35 | // A list of reporter names that Jest uses when writing coverage reports 36 | // coverageReporters: [ 37 | // "json", 38 | // "text", 39 | // "lcov", 40 | // "clover" 41 | // ], 42 | 43 | // An object that configures minimum threshold enforcement for coverage results 44 | // coverageThreshold: null, 45 | 46 | // A path to a custom dependency extractor 47 | // dependencyExtractor: null, 48 | 49 | // Make calling deprecated APIs throw helpful error messages 50 | // errorOnDeprecated: false, 51 | 52 | // Force coverage collection from ignored files using an array of glob patterns 53 | // forceCoverageMatch: [], 54 | 55 | // A path to a module which exports an async function that is triggered once before all test suites 56 | // globalSetup: null, 57 | 58 | // A path to a module which exports an async function that is triggered once after all test suites 59 | // globalTeardown: null, 60 | 61 | // A set of global variables that need to be available in all test environments 62 | // globals: {}, 63 | 64 | // An array of directory names to be searched recursively up from the requiring module's location 65 | // moduleDirectories: [ 66 | // "node_modules" 67 | // ], 68 | 69 | // An array of file extensions your modules use 70 | // moduleFileExtensions: [ 71 | // "js", 72 | // "json", 73 | // "jsx", 74 | // "ts", 75 | // "tsx", 76 | // "node" 77 | // ], 78 | 79 | // A map from regular expressions to module names that allow to stub out resources with a single module 80 | // moduleNameMapper: {}, 81 | 82 | // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader 83 | // modulePathIgnorePatterns: [], 84 | 85 | // Activates notifications for test results 86 | // notify: false, 87 | 88 | // An enum that specifies notification mode. Requires { notify: true } 89 | // notifyMode: "failure-change", 90 | 91 | // A preset that is used as a base for Jest's configuration 92 | // preset: null, 93 | 94 | // Run tests from one or more projects 95 | // projects: null, 96 | 97 | // Use this configuration option to add custom reporters to Jest 98 | // reporters: undefined, 99 | 100 | // Automatically reset mock state between every test 101 | // resetMocks: false, 102 | 103 | // Reset the module registry before running each individual test 104 | // resetModules: false, 105 | 106 | // A path to a custom resolver 107 | // resolver: null, 108 | 109 | // Automatically restore mock state between every test 110 | // restoreMocks: false, 111 | 112 | // The root directory that Jest should scan for tests and modules within 113 | // rootDir: null, 114 | 115 | // A list of paths to directories that Jest should use to search for files in 116 | // roots: [ 117 | // "" 118 | // ], 119 | 120 | // Allows you to use a custom runner instead of Jest's default test runner 121 | // runner: "jest-runner", 122 | 123 | // The paths to modules that run some code to configure or set up the testing environment before each test 124 | // setupFiles: [], 125 | 126 | // A list of paths to modules that run some code to configure or set up the testing framework before each test 127 | // setupFilesAfterEnv: [], 128 | 129 | // A list of paths to snapshot serializer modules Jest should use for snapshot testing 130 | // snapshotSerializers: [], 131 | 132 | // The test environment that will be used for testing 133 | // testEnvironment: "jest-environment-jsdom", 134 | 135 | // Options that will be passed to the testEnvironment 136 | // testEnvironmentOptions: {}, 137 | 138 | // Adds a location field to test results 139 | // testLocationInResults: false, 140 | 141 | // The glob patterns Jest uses to detect test files 142 | // testMatch: [ 143 | // "**/__tests__/**/*.[jt]s?(x)", 144 | // "**/?(*.)+(spec|test).[tj]s?(x)" 145 | // ], 146 | 147 | // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped 148 | // testPathIgnorePatterns: [ 149 | // "/node_modules/" 150 | // ], 151 | 152 | // The regexp pattern or array of patterns that Jest uses to detect test files 153 | // testRegex: [], 154 | 155 | // This option allows the use of a custom results processor 156 | // testResultsProcessor: null, 157 | 158 | // This option allows use of a custom test runner 159 | // testRunner: "jasmine2", 160 | 161 | // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href 162 | // testURL: "http://localhost", 163 | 164 | // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" 165 | // timers: "real", 166 | 167 | // A map from regular expressions to paths to transformers 168 | // transform: null, 169 | 170 | // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation 171 | // transformIgnorePatterns: [ 172 | // "/node_modules/" 173 | // ], 174 | 175 | // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them 176 | // unmockedModulePathPatterns: undefined, 177 | 178 | // Indicates whether each individual test should be reported during the run 179 | // verbose: null, 180 | 181 | // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode 182 | // watchPathIgnorePatterns: [], 183 | 184 | // Whether to use watchman for file crawling 185 | // watchman: true, 186 | }; 187 | -------------------------------------------------------------------------------- /test/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js3_test", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "app-test:lint": "eslint ../homework", 8 | "app-test:jest": "jest src", 9 | "test": "npm-run-all app-test:**" 10 | }, 11 | "author": "Jim Cramer", 12 | "license": "ISC", 13 | "dependencies": { 14 | "html-validator": "^4.0.3", 15 | "jest": "^24.8.0", 16 | "jest-puppeteer": "^4.2.0", 17 | "npm-run-all": "^4.1.5", 18 | "puppeteer": "^1.18.1", 19 | "serve": "^11.0.2" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /test/src/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | baseUrl: 'http://localhost:5000/', 3 | blockedResourceTypes: ['image', 'stylesheet', 'font'], 4 | }; 5 | -------------------------------------------------------------------------------- /test/src/fetch-contributors-404.test.js: -------------------------------------------------------------------------------- 1 | const config = require('./config'); 2 | 3 | describe('Fetch contributors 404 error handling', () => { 4 | it('should render an HTTP error when fetching contributors in the DOM', async () => { 5 | await page.setRequestInterception(true); 6 | page.on('request', req => { 7 | if (config.blockedResourceTypes.includes(req.resourceType())) { 8 | req.abort(); 9 | } else if (req.url().includes('/contributors')) { 10 | req.respond({ 11 | status: 404, 12 | contentType: 'application/json', 13 | body: JSON.stringify({ 14 | message: 'Not Found', 15 | documentation_url: 'https://developer.github.com/v3', 16 | }), 17 | }); 18 | } else { 19 | req.continue(); 20 | } 21 | }); 22 | 23 | await page.goto(config.baseUrl, { waitUntil: 'networkidle0' }); 24 | 25 | const fetchErrorRendered = await page.evaluate( 26 | () => document.querySelector('.alert-error') != null, 27 | ); 28 | 29 | expect(fetchErrorRendered).toBe(true); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/src/fetch-network-error.test.js: -------------------------------------------------------------------------------- 1 | const config = require('./config'); 2 | 3 | describe('Fetch network error handling', () => { 4 | it('should render a network error in the DOM', async () => { 5 | await page.setRequestInterception(true); 6 | page.on('request', req => { 7 | if ( 8 | config.blockedResourceTypes.includes(req.resourceType()) || 9 | req.url().includes('/repos') 10 | ) { 11 | req.abort(); 12 | } else { 13 | req.continue(); 14 | } 15 | }); 16 | 17 | await page.goto(config.baseUrl, { waitUntil: 'networkidle0' }); 18 | 19 | const fetchErrorRendered = await page.evaluate( 20 | () => document.querySelector('.alert-error') != null, 21 | ); 22 | 23 | expect(fetchErrorRendered).toBe(true); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/src/fetch-repos-404.test.js: -------------------------------------------------------------------------------- 1 | const config = require('./config'); 2 | 3 | describe('Fetch repos 404 error handling', () => { 4 | it('should render an HTTP error when fetching repos in the DOM', async () => { 5 | await page.setRequestInterception(true); 6 | page.on('request', req => { 7 | if (config.blockedResourceTypes.includes(req.resourceType())) { 8 | req.abort(); 9 | } else if (req.url().includes('/repos')) { 10 | req.respond({ 11 | status: 404, 12 | contentType: 'application/json', 13 | body: JSON.stringify({ 14 | message: 'Not Found', 15 | documentation_url: 'https://developer.github.com/v3', 16 | }), 17 | }); 18 | } else { 19 | req.continue(); 20 | } 21 | }); 22 | 23 | await page.goto(config.baseUrl, { waitUntil: 'networkidle0' }); 24 | 25 | const fetchErrorRendered = await page.evaluate( 26 | () => document.querySelector('.alert-error') != null, 27 | ); 28 | 29 | expect(fetchErrorRendered).toBe(true); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /test/src/helpers.js: -------------------------------------------------------------------------------- 1 | const config = require('./config'); 2 | 3 | async function setUp(page) { 4 | await page.setRequestInterception(true); 5 | // no images needed for the tests here 6 | page.on('request', req => { 7 | if (config.blockedResourceTypes.includes(req.resourceType())) { 8 | req.abort(); 9 | } else { 10 | req.continue(); 11 | } 12 | }); 13 | await page.goto(config.baseUrl, { waitUntil: 'networkidle0' }); 14 | } 15 | 16 | module.exports = { 17 | setUp, 18 | }; 19 | -------------------------------------------------------------------------------- /test/src/html-validation.test.js: -------------------------------------------------------------------------------- 1 | const validator = require('html-validator'); 2 | const { setUp } = require('./helpers'); 3 | 4 | describe('HTML validation', () => { 5 | beforeAll(async () => { 6 | await setUp(page); 7 | }); 8 | 9 | it('should generate valid HTML', async () => { 10 | const outerHtml = await page.evaluate(() => document.documentElement.outerHTML); 11 | const options = { 12 | data: `\n${outerHtml}`, 13 | format: 'text', 14 | ignore: 'Warning:', 15 | }; 16 | 17 | const report = await validator(options); 18 | expect(report).toBe('The document validates according to the specified schema(s).'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/src/iPhoneX.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const devices = require('puppeteer/DeviceDescriptors'); 3 | const config = require('./config'); 4 | 5 | describe('Responsiveness for Mobile', () => { 6 | it('should look responsive on a mobile phone', async () => { 7 | console.log('Check the iPhoneX.png file in the root folder for responsiveness.'); 8 | await page.emulate(devices['iPhone X']); 9 | await page.goto(config.baseUrl, { waitUntil: 'networkidle0' }); 10 | await page.screenshot({ path: 'iPhoneX.png' }); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /test/src/render.test.js: -------------------------------------------------------------------------------- 1 | const { setUp } = require('./helpers'); 2 | 3 | describe('Data rendering', () => { 4 | beforeAll(async () => { 5 | await setUp(page); 6 | }); 7 | 8 | it('should have a