diff --git a/.env-example b/.env-example index 3874f4c35..b5cc7846b 100644 --- a/.env-example +++ b/.env-example @@ -1 +1,5 @@ -HYF_SECRET= \ No newline at end of file +# Development +ASSIGNMENT_FOLDER=assignment +BRANCH_CHECKS=0 +ENABLE_CLEAN=1 +# HUSKY=0 \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js index 62e7dc28e..b475e0ef2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,14 +6,11 @@ module.exports = { node: true, jest: true, }, - plugins: ['hyf', 'no-autofix'], + plugins: ['no-autofix'], extends: ['eslint:recommended'], parserOptions: { ecmaVersion: 2020, }, - globals: { - axios: 'readonly', - }, rules: { 'no-console': 'off', 'no-var': 'error', @@ -44,8 +41,8 @@ module.exports = { message: 'Avoid `for in` loops. Prefer `Object.keys()` instead.', }, ], - 'hyf/use-map-result': 'error', - 'hyf/camelcase': 'warn', - 'hyf/no-commented-out-code': 'warn', + // 'hyf/use-map-result': 'error', + // 'hyf/camelcase': 'warn', + // 'hyf/no-commented-out-code': 'warn', }, }; diff --git a/.github-later/workflows/ci.yml b/.github-later/workflows/ci.yml new file mode 100644 index 000000000..e01c6c5c4 --- /dev/null +++ b/.github-later/workflows/ci.yml @@ -0,0 +1,24 @@ +name: 'CI' +on: + pull_request: +jobs: + build-test: + runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [20.x] + + steps: + - uses: actions/checkout@v4 # checkout the repo + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci # install packages + - run: ./module-week.sh # run tests (configured to use jest-junit reporter) + - uses: actions/upload-artifact@v4 # upload test results + if: success() || failure() # run this step even if previous step failed + with: + name: test-results + path: junit.xml diff --git a/.github-later/workflows/test-report.yml b/.github-later/workflows/test-report.yml new file mode 100644 index 000000000..45451b7da --- /dev/null +++ b/.github-later/workflows/test-report.yml @@ -0,0 +1,20 @@ +name: 'Test Report' +on: + workflow_run: + workflows: ['CI'] # runs after CI workflow + types: + - completed +permissions: + contents: read + actions: read + checks: write +jobs: + report: + runs-on: ubuntu-latest + steps: + - uses: dorny/test-reporter@v1 + with: + artifact: test-results # artifact name + name: JEST Tests # Name of the check run which will be created + path: '*.xml' # Path to test results (inside artifact .zip) + reporter: jest-junit # Format of test results diff --git a/.gitignore b/.gitignore index 4b72510b7..c62043e2d 100644 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,4 @@ dist .recent.json .disclaimer @assignment* +.dist/ diff --git a/.hashes.json b/.hashes.json index cbd6b59f0..c6dc8be95 100644 --- a/.hashes.json +++ b/.hashes.json @@ -1,31 +1,47 @@ { - "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" + "1-JavaScript": { + "Week2": { + "ex1-giveCompliment": "c2c8b6253ad706989b9120f00d80e8453b5be70f34c35fcd7567dcf5550ba897", + "ex2-dogYears": "83cde87e41e27739a745da5038550cdb2470ce917b8b6f3b5b5d92d9576c6b8e", + "ex3-tellFortune": "8b2f61a4b1fc2603bac8d09a497fce6febcc94103f4a8eef05562f31410c04e0", + "ex4-shoppingCart": "faa80df26c206721a4126e3e11f2d8601e7a74b5f480b5c2b895cd21573389db", + "ex5-shoppingCartPure": "3476061983b7ffbd6550e5e57fd7499bd2808d31ee2d4bc61cb97404d9f38d44", + "ex6-totalCost": "5b1d0494344f16ac5b707ba868879c799b503b60595afa6684698ea4bf7aa351", + "ex7-mindPrivacy": "f809108686ee3c05c2b7dc39de4ec6259ae48b7940f4cf56f34293302e256380" + }, + "Week3": { + "ex1-doubleEvenNumbers.test": "99e7d34f1878d376c2c14367a5de445a3bf00abee43ff86996f1d2a1bb1bc683", + "ex2-mondaysWorth.test": "25e38c63390f21cac3f8cf83f683f79cb516b94c693c8679f6ddcba341859620", + "ex3-lemonAllergy.test": "a3438041779e7419eac19d7112feff3cf91aec0055810ad9b721683cbf83312e", + "ex4-observable": "4085411da456ca5f34e7bd7c8607e1e1ff6d82c7acec2f823791e4c422961d72", + "ex5-wallet": "2c8d09de71b9044b8107b2e86d7b66a3b82eebea17c17d7153bfc68fdf20da5e" + } + }, + "2-Browsers": { + "Week1": { + "ex1-bookList": "8ac2dee143392160c9d60b181eb3f43b7b1318ea2addfe490c9c1cff048d5d9b", + "ex2-aboutMe": "3fc6e8adc0af28648d048c388dd087a8bbc1d22d8b2aebe7b3329c8423d26810", + "ex3-hijackLogo": "9d5ad72401c231c92e03df70946d9ba40759c780b7c39756d8b17cd15945d241", + "ex4-whatsTheTime": "80ebbed95a4c838d067d9a4eab32bc002101ab60847a2e0bc4ff463e97c354fd", + "ex5-catWalk": "4d75417fd3a7792b084fa234753d4cdd652d40d3990bad13aa7eec11a5db648a", + "ex6-gameOfLife": "10ccd24d0b46559e7c3b778ef9d1659995064f36fb329b7aba2261b65e9f0355" + } + }, + "3-UsingAPIs": { + "Week1": { + "ex1-johnWho": "3a45143e7c62f304fd5bd5eb706cea077912373fa521c80934adac49ec9436ef", + "ex2-checkDoubleDigits": "05d6893c90f828e26c4471b69054252a7c094a8a1ea02e28fb72716230cfbbc8", + "ex3-rollDie": "a3001b96605ee20f187421d3323635c1556cc77c06346ff25d79302a2b798c55", + "ex4-pokerDiceAll": "c03cf0564e4702c69bc4224ceba797efbf69420ed87979cb823c920567866923", + "ex5-pokerDiceChain": "e0b3668e27909a717506dd028b67b7c3b53f8ba1e5b643c39dfb2a40c44d89f4" + }, + "Week2": { + "ex1-programmerFun": "e6cbf8715cf9b840b26fc8d8553dd235ed52b456fa6b37d639f6d54625e58797", + "ex2-pokemonApp": "b9ec9888524d269f507943086df9ccba3ae32c76b1d39ac6565f6f8371c04631", + "ex3-rollAnAce": "8c840efffc0734a9b226588289a44df893955da251120e21ef1a1afa10a5e181", + "ex4-diceRace": "d21ce44070d9ff596b941d138ca1a701fbfc1292afdd412cc8b8951ef512d413", + "ex5-vscDebug": "6242bc8861bdb0abea48d00fa6f2cd9bf3a6f93fdd9b708ad130c8b64c6c75eb", + "ex6-browserDebug": "1787583932555b23bcb625030cfcad2094bc222824dcba2d79db4bc01e6b9142" + } + } } \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit new file mode 100644 index 000000000..33d42f29c --- /dev/null +++ b/.husky/pre-commit @@ -0,0 +1,3 @@ +#!/bin/bash +echo "Branch name: $(git rev-parse --abbrev-ref HEAD)" +npm run pre-commit \ No newline at end of file diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 000000000..1860e5563 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,3 @@ +#!/bin/bash +echo "Branch name: $(git rev-parse --abbrev-ref HEAD)" +npm run pre-push \ No newline at end of file diff --git a/.markdownlint.json b/.markdownlint.json index eef34aa53..e532ef2b8 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -2,5 +2,6 @@ "default": true, "line-length": false, "no-inline-html": false, - "no-duplicate-header": false + "no-duplicate-header": false, + "table-pipe-style": false } diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..f1239caa5 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +# Ignore JSON files +*.json \ No newline at end of file diff --git a/.tours/test-runner.tour b/.tours/test-runner.tour deleted file mode 100644 index 8036f7fb1..000000000 --- a/.tours/test-runner.tour +++ /dev/null @@ -1,147 +0,0 @@ -{ - "$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 deleted file mode 100644 index 7354e8694..000000000 --- a/.tours/unit-test-browser.tour +++ /dev/null @@ -1,88 +0,0 @@ -{ - "$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/2-Browsers/Week1/assignment/ex2-aboutMe/index.html b/2-Browsers/Week1/assignment/ex2-aboutMe/index.html index f5f404605..7b3c532ea 100644 --- a/2-Browsers/Week1/assignment/ex2-aboutMe/index.html +++ b/2-Browsers/Week1/assignment/ex2-aboutMe/index.html @@ -1,4 +1,4 @@ - + @@ -15,6 +15,6 @@

About Me

  • Hometown:
  • - + diff --git a/2-Browsers/Week1/assignment/ex2-aboutMe/index.js b/2-Browsers/Week1/assignment/ex2-aboutMe/index.js index d44ae0ddc..a03686b70 100644 --- a/2-Browsers/Week1/assignment/ex2-aboutMe/index.js +++ b/2-Browsers/Week1/assignment/ex2-aboutMe/index.js @@ -1,4 +1,3 @@ -'use strict'; /*------------------------------------------------------------------------------ Full description at: https://github.com/HackYourFuture/Assignments/tree/main/2-Browsers/Week1#exercise-2-about-me diff --git a/2-Browsers/Week1/assignment/ex3-hijackLogo.js b/2-Browsers/Week1/assignment/ex3-hijackLogo.js index c52f6d744..1b3d596e9 100644 --- a/2-Browsers/Week1/assignment/ex3-hijackLogo.js +++ b/2-Browsers/Week1/assignment/ex3-hijackLogo.js @@ -1,4 +1,3 @@ -'use strict'; /*------------------------------------------------------------------------------ Full description at: https://github.com/HackYourFuture/Assignments/tree/main/2-Browsers/Week1#exercise-3-the-logo-hijack diff --git a/2-Browsers/Week1/assignment/ex4-whatsTheTime/index.html b/2-Browsers/Week1/assignment/ex4-whatsTheTime/index.html index db4232201..5dac7dab6 100644 --- a/2-Browsers/Week1/assignment/ex4-whatsTheTime/index.html +++ b/2-Browsers/Week1/assignment/ex4-whatsTheTime/index.html @@ -1,4 +1,4 @@ - + @@ -6,6 +6,6 @@ What's The Time? - + diff --git a/2-Browsers/Week1/assignment/ex4-whatsTheTime/index.js b/2-Browsers/Week1/assignment/ex4-whatsTheTime/index.js index 0724f2d72..30dbdd61d 100644 --- a/2-Browsers/Week1/assignment/ex4-whatsTheTime/index.js +++ b/2-Browsers/Week1/assignment/ex4-whatsTheTime/index.js @@ -1,4 +1,3 @@ -'use strict'; /*------------------------------------------------------------------------------ Full description at: https://github.com/HackYourFuture/Assignments/tree/main/2-Browsers/Week1#exercise-4-whats-the-time diff --git a/2-Browsers/Week1/assignment/ex5-catWalk/index.html b/2-Browsers/Week1/assignment/ex5-catWalk/index.html index 58c619a27..7403bec69 100644 --- a/2-Browsers/Week1/assignment/ex5-catWalk/index.html +++ b/2-Browsers/Week1/assignment/ex5-catWalk/index.html @@ -1,4 +1,4 @@ - + @@ -15,6 +15,6 @@ src="http://www.anniemation.com/clip_art/images/cat-walk.gif" alt="Cat walking" /> - + diff --git a/2-Browsers/Week1/assignment/ex5-catWalk/index.js b/2-Browsers/Week1/assignment/ex5-catWalk/index.js index ad7c83fab..aedb02011 100644 --- a/2-Browsers/Week1/assignment/ex5-catWalk/index.js +++ b/2-Browsers/Week1/assignment/ex5-catWalk/index.js @@ -1,4 +1,3 @@ -'use strict'; /*------------------------------------------------------------------------------ Full description at: https://github.com/HackYourFuture/Assignments/tree/main/2-Browsers/Week1#exercise-5-the-cat-walk diff --git a/2-Browsers/Week1/assignment/ex6-gameOfLife/index.html b/2-Browsers/Week1/assignment/ex6-gameOfLife/index.html index 97b895737..2bb49e7a6 100644 --- a/2-Browsers/Week1/assignment/ex6-gameOfLife/index.html +++ b/2-Browsers/Week1/assignment/ex6-gameOfLife/index.html @@ -1,4 +1,4 @@ - + @@ -35,7 +35,7 @@

    >Wikipedia - Conway's Game of Life

    - + diff --git a/2-Browsers/Week1/assignment/ex6-gameOfLife/index.js b/2-Browsers/Week1/assignment/ex6-gameOfLife/index.js index b744fee03..a0955336a 100644 --- a/2-Browsers/Week1/assignment/ex6-gameOfLife/index.js +++ b/2-Browsers/Week1/assignment/ex6-gameOfLife/index.js @@ -1,4 +1,4 @@ -'use strict'; +// @ts-check /*------------------------------------------------------------------------------ Full description at: https://github.com/HackYourFuture/Assignments/tree/main/2-Browsers/Week1#exercise-6-conways-game-of-life @@ -11,8 +11,23 @@ const CELL_SIZE = 10; const NUM_COLUMNS = 75; const NUM_ROWS = 40; -// Create a cell with the given coordinates and randomly assign its begin state: -// life or death +/** + * @typedef {Object} GridCell + * @property {number} x + * @property {number} y + * @property {boolean} alive + * @property {boolean} [nextAlive] + */ + +/** @typedef {GridCell[]} GridRow */ + +/** + * Create a cell with the given coordinates and randomly assign its begin state: + * life or death + * @param {number} x + * @param {number} y + * @returns {GridCell} + */ function createCell(x, y) { const alive = Math.random() > 0.5; return { @@ -22,13 +37,21 @@ function createCell(x, y) { }; } -// Create the game "engine" with a closure -function createGame(context, numRows, numColumns) { +/** + * Create the game "engine" with a closure + * @param {CanvasRenderingContext2D} context + * @param {number} numRows + * @param {number} numColumns + * @returns + */ +export function createGame(context, numRows, numColumns) { + /** @type {GridRow[]} */ const grid = []; // Create the grid as a two-dimensional array (i.e. an array of arrays) function createGrid() { for (let y = 0; y < numRows; y++) { + /** @type {GridRow} */ const row = []; for (let x = 0; x < numColumns; x++) { const cell = createCell(x, y); @@ -38,14 +61,20 @@ function createGame(context, numRows, numColumns) { } } - // Execute a callback for each cell in the grid + /** + * Execute a callback for each cell in the grid + * @param {(cell: GridCell) => void} callback + */ function forEachCell(callback) { grid.forEach((row) => { row.forEach((cell) => callback(cell)); }); } - // Draw a cell onto the canvas + /** + * Draw a cell onto the canvas + * @param {GridCell} cell + */ function drawCell(cell) { // Draw cell background context.fillStyle = '#303030'; @@ -68,7 +97,12 @@ function createGame(context, numRows, numColumns) { } } - // Check the state of the cell at the given coordinates + /** + * Check the state of the cell at the given coordinates + * @param {number} x + * @param {number} y + * @returns {0 | 1} + */ function isAlive(x, y) { // Out-of-border cells are presumed dead if (x < 0 || x >= numColumns || y < 0 || y >= numRows) { @@ -78,7 +112,11 @@ function createGame(context, numRows, numColumns) { return grid[y][x].alive ? 1 : 0; } - // Count the number of living neighboring cells for a given cell + /** + * Count the number of living neighboring cells for a given cell + * @param {GridCell} cell + * @returns {number} + */ function countLivingNeighbors(cell) { const { x, y } = cell; return ( @@ -93,8 +131,10 @@ function createGame(context, numRows, numColumns) { ); } - // Update the state of the cells in the grid by applying the Game Of Life - // rules on each cell. + /** + * Update the state of the cells in the grid by applying the Game Of Life + * rules on each cell. + */ function updateGrid() { // Loop over all cells to determine their next state. forEachCell((cell) => { @@ -115,17 +155,23 @@ function createGame(context, numRows, numColumns) { // Apply the newly computed state to the cells forEachCell((cell) => { - cell.alive = cell.nextAlive; + cell.alive = cell.nextAlive ?? false; }); } - // Render a visual representation of the grid + // + + /** + * Render a visual representation of the grid + */ function renderGrid() { // Draw all cells in the grid forEachCell(drawCell); } - // Execute one game cycle + /** + * Execute one game cycle + */ function gameLoop() { // Update the state of cells in the grid updateGrid(); @@ -139,7 +185,9 @@ function createGame(context, numRows, numColumns) { }, 200); } - // Starts the game + /** + * Start the game + */ function start() { // Create initial grid createGrid(); @@ -158,11 +206,18 @@ function main() { // Resize the canvas to accommodate the desired number of cell rows and // columns const canvas = document.getElementById('canvas'); + if (!(canvas instanceof HTMLCanvasElement)) { + throw new Error('Canvas element not found'); + } + canvas.height = NUM_ROWS * CELL_SIZE; canvas.width = NUM_COLUMNS * CELL_SIZE; // Obtain a context that is needed to draw on the canvas const context = canvas.getContext('2d'); + if (!(context instanceof CanvasRenderingContext2D)) { + throw new Error('Context not found'); + } // Create the game "engine" const { start } = createGame(context, NUM_ROWS, NUM_COLUMNS); @@ -177,5 +232,3 @@ try { } catch { // ignore if running in node with jest } - -module.exports = createGame; diff --git a/2-Browsers/Week1/test-reports/ex1-bookList.todo.txt b/2-Browsers/Week1/test-reports/ex1-bookList.todo.txt deleted file mode 100644 index d8d868975..000000000 --- a/2-Browsers/Week1/test-reports/ex1-bookList.todo.txt +++ /dev/null @@ -1 +0,0 @@ -This test has not been run. \ No newline at end of file diff --git a/2-Browsers/Week1/test-reports/ex2-aboutMe.todo.txt b/2-Browsers/Week1/test-reports/ex2-aboutMe.todo.txt deleted file mode 100644 index d8d868975..000000000 --- a/2-Browsers/Week1/test-reports/ex2-aboutMe.todo.txt +++ /dev/null @@ -1 +0,0 @@ -This test has not been run. \ No newline at end of file diff --git a/2-Browsers/Week1/test-reports/ex3-hijackLogo.todo.txt b/2-Browsers/Week1/test-reports/ex3-hijackLogo.todo.txt deleted file mode 100644 index d8d868975..000000000 --- a/2-Browsers/Week1/test-reports/ex3-hijackLogo.todo.txt +++ /dev/null @@ -1 +0,0 @@ -This test has not been run. \ No newline at end of file diff --git a/2-Browsers/Week1/test-reports/ex4-whatsTheTime.todo.txt b/2-Browsers/Week1/test-reports/ex4-whatsTheTime.todo.txt deleted file mode 100644 index d8d868975..000000000 --- a/2-Browsers/Week1/test-reports/ex4-whatsTheTime.todo.txt +++ /dev/null @@ -1 +0,0 @@ -This test has not been run. \ No newline at end of file diff --git a/2-Browsers/Week1/test-reports/ex5-catWalk.todo.txt b/2-Browsers/Week1/test-reports/ex5-catWalk.todo.txt deleted file mode 100644 index d8d868975..000000000 --- a/2-Browsers/Week1/test-reports/ex5-catWalk.todo.txt +++ /dev/null @@ -1 +0,0 @@ -This test has not been run. \ No newline at end of file diff --git a/2-Browsers/Week1/test-reports/ex6-gameOfLife.todo.txt b/2-Browsers/Week1/test-reports/ex6-gameOfLife.todo.txt deleted file mode 100644 index d8d868975..000000000 --- a/2-Browsers/Week1/test-reports/ex6-gameOfLife.todo.txt +++ /dev/null @@ -1 +0,0 @@ -This test has not been run. \ No newline at end of file diff --git a/2-Browsers/Week1/unit-tests/.verbose b/2-Browsers/Week1/unit-tests/.verbose deleted file mode 100644 index e69de29bb..000000000 diff --git a/2-Browsers/Week1/unit-tests/ex1-bookList.test.js b/2-Browsers/Week1/unit-tests/ex1-bookList.test.ts similarity index 55% rename from 2-Browsers/Week1/unit-tests/ex1-bookList.test.js rename to 2-Browsers/Week1/unit-tests/ex1-bookList.test.ts index c6b8fd4fa..3be6b13e4 100644 --- a/2-Browsers/Week1/unit-tests/ex1-bookList.test.js +++ b/2-Browsers/Week1/unit-tests/ex1-bookList.test.ts @@ -1,47 +1,66 @@ -/* eslint-disable hyf/camelcase */ -const walk = require('acorn-walk'); -const { +import { simple } from 'acorn-walk'; + +import { DOMWindow } from 'jsdom'; +import { prepare, validateHTML } from '../../../test-runner/jsdom-helpers.js'; +import { beforeAllHelper, + ExerciseInfo, testTodosRemoved, -} = require('../../../test-runner/unit-test-helpers'); -const { prepare, validateHTML } = require('../../../test-runner/jsdom-helpers'); +} from '../../../test-runner/unit-test-helpers.js'; + +type State = { + outerHTML: string; + titlesAndAuthors: string[]; +}; + +describe('br-wk1-ex1-bookList', () => { + const state: State = { + outerHTML: '', + titlesAndAuthors: [], + }; -describe('Generated HTML', () => { - const state = {}; - let document, source, rootNode; + let exInfo: ExerciseInfo; + let document: DOMWindow['document']; beforeAll(async () => { ({ document } = await prepare()); state.outerHTML = document.documentElement.outerHTML; - ({ rootNode, source } = beforeAllHelper(__filename, { - noRequire: true, - parse: true, - })); + exInfo = await beforeAllHelper(__filename, { noImport: true }); - rootNode && - walk.simple(rootNode, { + exInfo.rootNode && + simple(exInfo.rootNode, { VariableDeclarator({ id, init }) { - if (id.name === 'myBooks' && init.type === 'ArrayExpression') { + if ( + id.type === 'Identifier' && + id.name === 'myBooks' && + init?.type === 'ArrayExpression' + ) { state.titlesAndAuthors = init.elements.reduce((acc, element) => { - if (element.type === 'ObjectExpression') { + if (element?.type === 'ObjectExpression') { element.properties.forEach((prop) => { - if (['title', 'author'].includes(prop.key.name)) { + if ( + prop.type === 'Property' && + prop.key.type === 'Identifier' && + prop.value.type === 'Literal' && + typeof prop.value.value === 'string' && + ['title', 'author'].includes(prop.key.name) + ) { acc.push(prop.value.value); } }); } return acc; - }, []); + }, [] as string[]); } }, }); }); test('HTML should be syntactically valid', () => - validateHTML(state.outerHTML)); + validateHTML(state.outerHTML!)); - testTodosRemoved(() => source); + testTodosRemoved(() => exInfo.source); test('should contain a