diff --git a/README.md b/README.md index 78f7106..aad1994 100644 --- a/README.md +++ b/README.md @@ -532,7 +532,25 @@ Reference: ## 4. Testing -_TODO_ +I've implemented one end-to-end test and one unit test +using [Playwright](https://playwright.dev/). +This was straightforward besides small details like the `*.mjs` extension +and the fact that you cannot use named imports when importing from +`public/scripts`. + +There's a lot more to explore here, but it's not much different from +testing other frontend stacks. It's actually simpler as there was zero +configuration and just one dependency. + +However, it's currently lacking code coverage. Playwright provides some +[code coverage facilities](https://playwright.dev/docs/api/class-coverage) +but it's not straight-forward to produce a standard LCOV report from that, +and it would probably be difficult to unify end-to-end and unit test coverage. + +Reference: + +- [addItem.test.mjs](./test/e2e/addItem.test.mjs) +- [util.test.mjs](./test/unit/util.test.mjs) ## 5. Assessment @@ -615,6 +633,7 @@ and some opinionated statements based on my experience in the industry. - Little indirection - Low coupling - The result is literally just a bunch of HTML, CSS, and JS files. +- Straight-forward, zero-config testing with Playwright All source files (HTML, CSS and JS) combine to **under 2400 lines of code**, including comments and empty lines. @@ -663,6 +682,7 @@ would reduce the comparably low code size (see above) even further. continuously monitor regressions with extensive test suites. The cost of browser testing is surely a lot higher when using a vanilla approach. +- No code coverage from tests --- @@ -805,6 +825,7 @@ Thanks! ### 05/2023 +- Add basic testing - Fix stylelint errors - Update dependencies diff --git a/package-lock.json b/package-lock.json index e7f6787..fbb0ce4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.1.0", "license": "ISC", "devDependencies": { + "@playwright/test": "^1.33.0", "eslint": "^8.20.0", "eslint-plugin-compat": "^4.0.2", "http-server": "^14.1.1", @@ -316,6 +317,25 @@ "node": ">= 8" } }, + "node_modules/@playwright/test": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.33.0.tgz", + "integrity": "sha512-YunBa2mE7Hq4CfPkGzQRK916a4tuZoVx/EpLjeWlTVOnD4S2+fdaQZE0LJkbfhN5FTSKNLdcl7MoT5XB37bTkg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "playwright-core": "1.33.0" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, "node_modules/@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", @@ -328,6 +348,12 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "node_modules/@types/node": { + "version": "20.1.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz", + "integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q==", + "dev": true + }, "node_modules/@types/normalize-package-data": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", @@ -1154,6 +1180,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -2121,6 +2161,18 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/playwright-core": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.33.0.tgz", + "integrity": "sha512-aizyPE1Cj62vAECdph1iaMILpT0WUDCq3E6rW6I+dleSbBoGbktvJtzS6VHkZ4DKNEOG9qJpiom/ZxO+S15LAw==", + "dev": true, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=14" + } + }, "node_modules/portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", @@ -3352,6 +3404,17 @@ "fastq": "^1.6.0" } }, + "@playwright/test": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.33.0.tgz", + "integrity": "sha512-YunBa2mE7Hq4CfPkGzQRK916a4tuZoVx/EpLjeWlTVOnD4S2+fdaQZE0LJkbfhN5FTSKNLdcl7MoT5XB37bTkg==", + "dev": true, + "requires": { + "@types/node": "*", + "fsevents": "2.3.2", + "playwright-core": "1.33.0" + } + }, "@tsconfig/node14": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", @@ -3364,6 +3427,12 @@ "integrity": "sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==", "dev": true }, + "@types/node": { + "version": "20.1.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.1.4.tgz", + "integrity": "sha512-At4pvmIOki8yuwLtd7BNHl3CiWNbtclUbNtScGx4OHfBd4/oWoJC8KRCIxXwkdndzhxOsPXihrsOoydxBjlE9Q==", + "dev": true + }, "@types/normalize-package-data": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.1.tgz", @@ -3961,6 +4030,13 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, "function-bind": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", @@ -4675,6 +4751,12 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "playwright-core": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.33.0.tgz", + "integrity": "sha512-aizyPE1Cj62vAECdph1iaMILpT0WUDCq3E6rW6I+dleSbBoGbktvJtzS6VHkZ4DKNEOG9qJpiom/ZxO+S15LAw==", + "dev": true + }, "portfinder": { "version": "1.0.32", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.32.tgz", diff --git a/package.json b/package.json index f4353de..60f1d69 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "format-check": "prettier --check .", "lint": "eslint public", "lint-styles": "stylelint public/styles/*", - "serve": "http-server -c-1 public" + "serve": "http-server -c-1 public", + "test": "playwright test" }, "repository": { "type": "git", @@ -26,6 +27,7 @@ }, "homepage": "https://github.com/morris/vanilla-todo#readme", "devDependencies": { + "@playwright/test": "^1.33.0", "eslint": "^8.20.0", "eslint-plugin-compat": "^4.0.2", "http-server": "^14.1.1", diff --git a/test/e2e/addItem.test.mjs b/test/e2e/addItem.test.mjs new file mode 100644 index 0000000..479727b --- /dev/null +++ b/test/e2e/addItem.test.mjs @@ -0,0 +1,27 @@ +import { expect, test } from '@playwright/test'; + +test("Add item to today's todo list (Enter)", async ({ page }) => { + await page.goto('http://localhost:8080'); + + const input = page.locator('.-today .todo-item-input > input').filter(); + await input.focus(); + await input.type('Hello, world!'); + await page.keyboard.press('Enter'); + + await expect(page.locator('.-today .todo-item > .label')).toHaveText( + 'Hello, world!' + ); +}); + +test("Add item to today's todo list (click)", async ({ page }) => { + await page.goto('http://localhost:8080'); + + const input = page.locator('.-today .todo-item-input > input').filter(); + await input.focus(); + await input.type('Hello, world!'); + await page.locator('.-today .todo-item-input > .save').click(); + + await expect(page.locator('.-today .todo-item > .label')).toHaveText( + 'Hello, world!' + ); +}); diff --git a/test/unit/util.test.mjs b/test/unit/util.test.mjs new file mode 100644 index 0000000..8ced186 --- /dev/null +++ b/test/unit/util.test.mjs @@ -0,0 +1,9 @@ +import { expect, test } from '@playwright/test'; +import util from '../../public/scripts/util.js'; + +test('formatDate', () => { + expect(util.formatDate(new Date(0))).toEqual('January 1st 1970'); + expect(util.formatDate(new Date('2023-05-13 12:00:00'))).toEqual( + 'May 13th 2023' + ); +});