diff --git a/.env-example b/.env-example new file mode 100644 index 0000000..3874f4c --- /dev/null +++ b/.env-example @@ -0,0 +1 @@ +HYF_SECRET= \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..62e7dc2 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,51 @@ +module.exports = { + env: { + browser: true, + commonjs: true, + es6: true, + node: true, + jest: true, + }, + plugins: ['hyf', 'no-autofix'], + extends: ['eslint:recommended'], + parserOptions: { + ecmaVersion: 2020, + }, + globals: { + axios: 'readonly', + }, + rules: { + 'no-console': 'off', + 'no-var': 'error', + 'prefer-const': 'off', + 'no-autofix/prefer-const': 'warn', + 'new-cap': 'error', + 'no-useless-computed-key': 'error', + eqeqeq: 'error', + 'no-restricted-syntax': [ + 'warn', + { + selector: + "ExpressionStatement > CallExpression > MemberExpression > Identifier[name='map']", + message: 'Results from `map` are unused. Replace `map` with `forEach`.', + }, + { + selector: "MemberExpression[property.name='innerText']", + message: + 'The assignment tests do not support `innerText`. Please replace with `textContent`.', + }, + { + selector: "MemberExpression[property.name='innerHTML']", + message: + 'Please do not use `innerHTML` in the assignment. Use `textContent` and/or `document.createElement()` instead.', + }, + { + selector: 'ForInStatement', + message: 'Avoid `for in` loops. Prefer `Object.keys()` instead.', + }, + ], + 'hyf/use-map-result': 'error', + 'hyf/camelcase': 'warn', + 'hyf/no-commented-out-code': 'warn', + }, +}; diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4b72510 --- /dev/null +++ b/.gitignore @@ -0,0 +1,124 @@ +.idea +sysinfo.json +.DS_Store + +# Logs +logs +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# test-runner +.recent.json +.disclaimer +@assignment* diff --git a/.hashes.json b/.hashes.json new file mode 100644 index 0000000..cbd6b59 --- /dev/null +++ b/.hashes.json @@ -0,0 +1,31 @@ +{ + "ex1-giveCompliment": "60df6f8686e387e8c2fb72789b35af2c66a27b224112dcf5b0c0eeb7ab694852", + "ex2-dogYears": "98e25ac9c737f580a1736eee1dcea2cb39a9739429c84aa182aaa4a13437e646", + "ex3-tellFortune": "684d840ab303b2bfbcc0aee3e4bcf94478b63f4dbe3a603ccba489bd922442a5", + "ex4-shoppingCart": "1ce7c5ff829999bccc0234f10d5304c80b8421ce4dc9dbae81cf0888c977b5fe", + "ex5-shoppingCartPure": "9fde65b469f4a86031503c6700845db4e832c76fd4322de4009be67a12c2dd6b", + "ex6-totalCost": "c52ceb8ee35009539c45ce3fb74b4cc00e2895a7e214419f313b7e61f6679376", + "ex7-mindPrivacy": "47ce19447d1c3e32a702dd708ed06091e9ea9996e01725b8f8b219f4524782af", + "ex1-doubleEvenNumbers.test": "fca4268f9c4ea8ba7a19d564a7150b3f7f1e2f3fb254d2a0902a0838d067a1dc", + "ex2-mondaysWorth.test": "3205b7ea8a12a08e200fca68febb9d373cfe8b8c005b32c990dbd91d0519c0e6", + "ex3-lemonAllergy.test": "58055218f88d00d396ab254800218a50fcfab10ed0548602aece6040a799c4a8", + "ex4-observable": "7c28d25384d6ef4e06a59ec651a053a2f5781fc555f9df4f2f2aeb7d07507354", + "ex5-wallet": "b2664b9411474a468f999c8c3bb7590046dd3dd3c883795d9ebffbee1140dea4", + "ex1-bookList": "889909bb59ecd158fe43a0bf36bcc4708bbb941e4566bcaf080481bedef7b0f0", + "ex2-aboutMe": "759d12d04b4c42592089e4ad77b0cc9fe440f543220f09eb2bbdccedacbc503f", + "ex3-hijackLogo": "86782e88e1dbb6571bb3576983e37e0293935531932621c13728924f24587aaf", + "ex4-whatsTheTime": "8106f06feb81e186a44a7c8bf57339187717c896f4c726008b85030fb868a391", + "ex5-catWalk": "748e45f93e283bf8972bc64a01f393fb28cb8d50efcfc6a664cf664b7c998508", + "ex6-gameOfLife": "0c29b5725dd8c3cba7e9bb6785ea1729f9229561642b65cff14ee379f9553efb", + "ex1-johnWho": "00f3567ed9d194394b7a5bfe66ff9442bdadf715dd0700be1a9f43667d2fae2a", + "ex2-checkDoubleDigits": "185367ae2f185e90ec03cc220029607981514d909f1b9ecc3a4f01607e14c88c", + "ex3-rollDie": "9ad20d60390ddf6029db0a87007100de4271f9c2056f9b4578bff6013338cb3c", + "ex4-pokerDiceAll": "f9eacfcbbd34dc82b5d1a66810b130929287aada2e0b84ea9bd95d6582f5150d", + "ex5-pokerDiceChain": "23651f5d5cbc70b78728c07290beb9700f7bae71edff6f7bd6bc478f168be1d6", + "ex1-programmerFun": "5f7ecc0ba804c290534a4ec32bb1d940e9783a105581f8c6fd06015a538b5c1c", + "ex2-pokemonApp": "47a8794931e8aaa2c0b24212334cb5a82b25ae09e857806f1debe5b83207f081", + "ex3-rollAnAce": "7d8d1a5bb999d4f3ff74675f57fb9cf2e02d9486c5c0308b2a3d545c59c5adf5", + "ex4-diceRace": "70eb2aa459da54f85959fd1a19d34e0287ea2c1f6f99bcb34686c16f175196c1", + "ex5-vscDebug": "b8f99814051d491d0aebe495dcab79d86ac4c98cecc063cf1604451e9a7a1090", + "ex6-browserDebug": "41899e54aeed71c8f8c2b4b02b264fea8952171b0074351964a28856b9b6e45b" +} \ No newline at end of file diff --git a/.htmlvalidate.json b/.htmlvalidate.json new file mode 100644 index 0000000..bf80768 --- /dev/null +++ b/.htmlvalidate.json @@ -0,0 +1,10 @@ +{ + "extends": ["html-validate:recommended"], + + "rules": { + "no-inline-style": "off", + "void-style": "off", + "no-trailing-whitespace": "off", + "attribute-boolean-style": "off" + } +} diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..eef34aa --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,6 @@ +{ + "default": true, + "line-length": false, + "no-inline-html": false, + "no-duplicate-header": false +} diff --git a/.tours/test-runner.tour b/.tours/test-runner.tour new file mode 100644 index 0000000..8036f7f --- /dev/null +++ b/.tours/test-runner.tour @@ -0,0 +1,147 @@ +{ + "$schema": "https://aka.ms/codetour-schema", + "title": "1. test-runner", + "steps": [ + { + "file": "test-runner/index.js", + "description": "The test runner starts by calling function `main()`.", + "line": 231, + "title": "entry point" + }, + { + "file": "test-runner/index.js", + "description": "The function `compileMenuData()` is called to compile menu data by scanning the repo's directory tree, looking for exercise files and directories that match a prescribed naming convention. The returned menu data (a JavaScript object) is used to drive the menu dialog.", + "line": 175, + "title": "get menu data" + }, + { + "file": "test-runner/test-runner-helpers.js", + "description": "Exercise files and folders must reside in a folder named **assignment** and match a pattern that starts with the letters **ex** followed by a number and a dash. Each **assignment** folder is a subfolder of a **Week𝑛** folder, which itself is a subfolder of a module specific top-level folder. For instance:\n\n```console\n1-JavaScript/\n Week3/\n assignment/\n ex1-giveCompliment.js\n ...\n```\n\nNote that Windows paths contain back slashes that must be replaced with forward slashes as required by `fast-glob`.", + "line": 26, + "selection": { + "start": { + "line": 20, + "character": 3 + }, + "end": { + "line": 27, + "character": 1 + } + }, + "title": "compile menu data" + }, + { + "file": "test-runner/test-runner-helpers.js", + "description": "We use a regular expression to extract the **module**, **week** and *exercise* name from each file path. If the exercise is a (JavaScript) file, we strip off the `.js` file extension.", + "line": 43, + "selection": { + "start": { + "line": 28, + "character": 1 + }, + "end": { + "line": 44, + "character": 1 + } + }, + "title": "build menu data object" + }, + { + "file": "test-runner/index.js", + "description": "As a time-saver, a user is given the option to re-run the last test (if there is one). This information is kept in the file `.recent.json`.", + "line": 183, + "selection": { + "start": { + "line": 179, + "character": 1 + }, + "end": { + "line": 184, + "character": 1 + } + }, + "title": "recent selection" + }, + { + "file": "test-runner/index.js", + "description": "If the user wishes to select a different exercise to test (or if there was no previous to re-run) a series of menu prompts allows the user to select a **module**, **week** and **exercise** to test.", + "line": 190, + "title": "selection menu", + "selection": { + "start": { + "line": 185, + "character": 1 + }, + "end": { + "line": 191, + "character": 1 + } + } + }, + { + "file": "test-runner/index.js", + "description": "The students' exercises are in subfolders of the **assignment** folder. For testing the \"happy path\" of the unit tests completed exercises can be placed in an alternate folder, the name of whhich can be defined through the `ASSIGNMENT_FOLDER` environment variable. The recommended folder for this purpose is **@assignment** (git-ignored). The npm script **npm run testalt** sets the `ASSIGNMENT_FOLDER` environment variable to **@assignment** before running the test.", + "line": 198 + }, + { + "file": "test-runner/index.js", + "description": "A hash is computed over each exercise as part of the **posinstall** npm script. These hashes are stored in the file `.hashes.json` (git-ignored). When the use runs a test the hash is recomputed and compare with the stored hash. If the hash values are the same then obviously the exercise has not been touched.", + "line": 201, + "title": "recompute hash", + "selection": { + "start": { + "line": 203, + "character": 1 + }, + "end": { + "line": 207, + "character": 6 + } + } + }, + { + "file": "test-runner/index.js", + "description": "If the exercise is untouched, we log a message to the console and to log file.", + "line": 207, + "selection": { + "start": { + "line": 203, + "character": 1 + }, + "end": { + "line": 208, + "character": 1 + } + }, + "title": "compare hashes" + }, + { + "file": "test-runner/index.js", + "description": "Running a test comprises three steps:\n\n1. Unit test with Jest\n2. ESLint check\n3. Spelling check\n\nEach of these steps returns a (potentially multiline) string with error information or just an empty string if there were no errors. These strings are concatenated to form an error report.", + "line": 213, + "selection": { + "start": { + "line": 210, + "character": 1 + }, + "end": { + "line": 214, + "character": 1 + } + }, + "title": "run test steps" + }, + { + "file": "test-runner/index.js", + "description": "An error report is written to the `test-reports` folder, but only if the student did some work on the exercise.", + "line": 216, + "title": "write report" + }, + { + "file": "test-runner/index.js", + "description": "A textual disclaimer is shown at the end of the test that can be silenced for subsequent tests.", + "line": 220, + "title": "show disclaimer" + } + ] +} \ No newline at end of file diff --git a/.tours/unit-test-browser.tour b/.tours/unit-test-browser.tour new file mode 100644 index 0000000..7354e86 --- /dev/null +++ b/.tours/unit-test-browser.tour @@ -0,0 +1,88 @@ +{ + "$schema": "https://aka.ms/codetour-schema", + "title": "4. unit test browser", + "steps": [ + { + "file": "2-Browsers/Week1/unit-tests/ex4-whatsTheTime.test.js", + "description": "For browser-based exercises we make use of `jsdom` through the helper function `prepare()`.", + "line": 11, + "title": "beforeAll prepare" + }, + { + "file": "test-runner/jsdom-helpers.js", + "description": "We use the JSDOM convenience function `JSDOM.fromFile()` to load `index.html` into jsdom. We add some sleep time to allow JavaScript file used in ` + + diff --git a/1-JavaScript/Week3/assignment/ex5-wallet/index.js b/1-JavaScript/Week3/assignment/ex5-wallet/index.js new file mode 100644 index 0000000..2cacd1b --- /dev/null +++ b/1-JavaScript/Week3/assignment/ex5-wallet/index.js @@ -0,0 +1,118 @@ +'use strict'; + +// Based on an example from: Philipp Beau (@ze_german) + +const eurosFormatter = new Intl.NumberFormat('nl-NL', { + style: 'currency', + currency: 'EUR', +}); + +function createWallet(name, cash = 0) { + function deposit(amount) { + cash += amount; + } + + function withdraw(amount) { + if (cash - amount < 0) { + console.log(`Insufficient funds!`); + return 0; + } + + cash -= amount; + return amount; + } + + function transferInto(wallet, amount) { + console.log( + `Transferring ${eurosFormatter.format(amount)} from ${name} to ${ + wallet.name + }` + ); + const withdrawnAmount = withdraw(amount); + wallet.deposit(withdrawnAmount); + } + + function reportBalance() { + console.log(`Name: ${name}, balance: ${eurosFormatter.format(cash)}`); + } + + const getName = () => name; + + return { + deposit, + withdraw, + transferInto, + reportBalance, + getName, + }; +} + +const walletJack = createWallet('Jack', 100); +const walletJoe = createWallet('Joe', 10); +const walletJane = createWallet('Jane', 20); + +walletJack.transferInto(walletJoe, 50); +walletJane.transferInto(walletJoe, 25); + +walletJane.deposit(20); +walletJane.transferInto(walletJoe, 25); + +walletJack.reportBalance(); +walletJoe.reportBalance(); +walletJane.reportBalance(); + +// * End of exercise code + +/******************************************************************************* + * TODO: Multiple choice: provide your answers by replacing `undefined` with the + * TODO: letter corresponding to your choice, e.g. answer: 'a' + ******************************************************************************/ +// prettier-ignore +// eslint-disable-next-line no-unused-vars +const quiz = { + q1: { + question: 'At line 26, which variables are in the scope marked Closure?', + choices: { + a: 'There is no scope marked Closure', + b: 'cash, name', + c: 'amount, this, wallet' + }, + answer: undefined, + }, + q2: { + question: 'What is in the Call Stack, from top to bottom?', + choices: { + a: 'withdraw, anonymous', + b: 'anonymous, transferInto', + c: 'transferInto, anonymous' + }, + answer: undefined, + }, + q3: { + question: 'What tooltip appears when hovering over the third debug button?', + choices: { + a: 'Step into next function call', + b: 'Step out of current function', + c: 'Step' + }, + answer: undefined, + }, + q4: { + question: 'What is displayed in the console?', + choices: { + a: 'Transferring € 50,00 from Jack to Joe', + b: 'Transferring € 50,00 from Jack to undefined', + c: 'Transferring € 50,00 from Jack to Jane' + }, + answer: undefined, + }, + q5: { + question: 'The owner of the wallet with insufficient funds is:', + choices: { + a: 'Jack', + b: 'Joe', + c: 'Jane' + }, + answer: undefined, + }, +}; diff --git a/1-JavaScript/Week3/test-reports/ex1-doubleEvenNumbers.test.todo.txt b/1-JavaScript/Week3/test-reports/ex1-doubleEvenNumbers.test.todo.txt new file mode 100644 index 0000000..d8d8689 --- /dev/null +++ b/1-JavaScript/Week3/test-reports/ex1-doubleEvenNumbers.test.todo.txt @@ -0,0 +1 @@ +This test has not been run. \ No newline at end of file diff --git a/1-JavaScript/Week3/test-reports/ex2-mondaysWorth.test.todo.txt b/1-JavaScript/Week3/test-reports/ex2-mondaysWorth.test.todo.txt new file mode 100644 index 0000000..d8d8689 --- /dev/null +++ b/1-JavaScript/Week3/test-reports/ex2-mondaysWorth.test.todo.txt @@ -0,0 +1 @@ +This test has not been run. \ No newline at end of file diff --git a/1-JavaScript/Week3/test-reports/ex3-lemonAllergy.test.todo.txt b/1-JavaScript/Week3/test-reports/ex3-lemonAllergy.test.todo.txt new file mode 100644 index 0000000..d8d8689 --- /dev/null +++ b/1-JavaScript/Week3/test-reports/ex3-lemonAllergy.test.todo.txt @@ -0,0 +1 @@ +This test has not been run. \ No newline at end of file diff --git a/1-JavaScript/Week3/test-reports/ex4-observable.todo.txt b/1-JavaScript/Week3/test-reports/ex4-observable.todo.txt new file mode 100644 index 0000000..d8d8689 --- /dev/null +++ b/1-JavaScript/Week3/test-reports/ex4-observable.todo.txt @@ -0,0 +1 @@ +This test has not been run. \ No newline at end of file diff --git a/1-JavaScript/Week3/test-reports/ex5-wallet.todo.txt b/1-JavaScript/Week3/test-reports/ex5-wallet.todo.txt new file mode 100644 index 0000000..d8d8689 --- /dev/null +++ b/1-JavaScript/Week3/test-reports/ex5-wallet.todo.txt @@ -0,0 +1 @@ +This test has not been run. \ No newline at end of file diff --git a/1-JavaScript/Week3/unit-tests/ex5-wallet.test.js b/1-JavaScript/Week3/unit-tests/ex5-wallet.test.js new file mode 100644 index 0000000..41abd7f --- /dev/null +++ b/1-JavaScript/Week3/unit-tests/ex5-wallet.test.js @@ -0,0 +1,45 @@ +/* eslint-disable hyf/camelcase */ +'use strict'; +const walk = require('acorn-walk'); +const { beforeAllHelper } = require('../../../test-runner/unit-test-helpers'); + +describe('wallet', () => { + let rootNode; + const state = { answers: [] }; + + beforeAll(() => { + ({ rootNode } = beforeAllHelper(__filename, { + parse: true, + noRequire: true, + })); + + rootNode && + walk.simple(rootNode, { + Property({ key, value }) { + if (key.name === 'answer') { + state.answers.push(value.value); + } + }, + }); + }); + + test('q1: At line 26, which variables are in the scope marked Closure?', () => { + expect(state.answers[0] === 'b').toBe(true); + }); + + test('q2: What is in the Call Stack, from top to bottom?', () => { + expect(state.answers[1] === 'c').toBe(true); + }); + + test('q3: What tooltip appears when hovering over the third debug button?', () => { + expect(state.answers[2] === 'a').toBe(true); + }); + + test('q4: What is displayed in the console?', () => { + expect(state.answers[3] === 'a').toBe(true); + }); + + test('q5: The owner of the wallet with insufficient funds is?', () => { + expect(state.answers[4] === 'c').toBe(true); + }); +}); diff --git a/2-Browsers/Week1/README.md b/2-Browsers/Week1/README.md new file mode 100644 index 0000000..218bf37 --- /dev/null +++ b/2-Browsers/Week1/README.md @@ -0,0 +1,223 @@ +# Assignment Browsers Week 1 + +## Deliverable Exercises + +The assignment for this week can be found in the `assignment` folder. + +> :collision: **Important** +> +> In this assignment you are not allowed to use `.innerHTML`. To create HTML elements, use `document.createElement()`. To set the text content of an element, use `.textContent`. The assignment tests currently do not support `.innerText`. + +## Exercise 1: The book list + +**Folder**: `ex1-bookList` + +I'd like to display my three favorite books inside a nice webpage! + +```js +const myBooks = [ + { + title: 'The Design of Everyday Things', + author: 'Don Norman', + isbn: '978-0465050659', + alreadyRead: false, + }, + { + title: 'The Most Human Human', + author: 'Brian Christian', + isbn: '978-1617933431', + alreadyRead: true, + }, + { + title: 'The Pragmatic Programmer', + author: 'Andrew Hunt', + isbn: '978-0201616224', + alreadyRead: true, + }, +]; +``` + +1. Iterate through the array of books. +2. For each book, create a `

` element with the book `title` and `author`. +3. Use a `