From b6aa03923f85792979118f2aec10c5ec1c68c2c6 Mon Sep 17 00:00:00 2001 From: Ourchitecture <97544811+ourchitectureio@users.noreply.github.com> Date: Fri, 22 Sep 2023 18:30:57 -0400 Subject: [PATCH] feat: new todo Signed-off-by: Ourchitecture <97544811+ourchitectureio@users.noreply.github.com> --- .gitignore | 6 +- .husky/pre-push | 6 - pnpm-lock.yaml | 3 + .../implementations/html-and-expressjs/app.js | 4 +- .../html-and-expressjs/empty-todo-error.png | Bin 0 -> 23194 bytes .../html-and-expressjs/package.json | 5 +- .../html-and-expressjs/playwright.config.js | 3 +- .../public/stylesheets/style.css | 30 ++++- .../html-and-expressjs/routes/createTodo.js | 36 +++++ .../html-and-expressjs/routes/index.js | 1 + .../html-and-expressjs/routes/users.js | 9 -- .../html-and-expressjs/tests/todos.spec.js | 124 +++++++++++++++++- .../html-and-expressjs/views/index.pug | 14 +- 13 files changed, 209 insertions(+), 32 deletions(-) create mode 100644 src/experiments/todo/implementations/html-and-expressjs/empty-todo-error.png create mode 100644 src/experiments/todo/implementations/html-and-expressjs/routes/createTodo.js delete mode 100644 src/experiments/todo/implementations/html-and-expressjs/routes/users.js diff --git a/.gitignore b/.gitignore index 2e74eed..ee7c4f4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ **/.task-output **/.wireit **/node_modules -**/test-results/ -**/playwright-report/ -**/playwright/.cache/ +**/test-results +**/playwright-report +**/playwright/.cache diff --git a/.husky/pre-push b/.husky/pre-push index 72edd5b..99ab7c1 100644 --- a/.husky/pre-push +++ b/.husky/pre-push @@ -1,12 +1,6 @@ #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" -# If pushing tags, don't test anything. -grep "refs/tags/" /dev/null -if [ "$?" -eq "0" ]; then - exit 0 -fi - pnpm test # leave a little room before git messages diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 786ad53..3278b87 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -62,6 +62,9 @@ importers: pug: specifier: 2.0.0-beta11 version: 2.0.0-beta11 + uid-safe: + specifier: ^2.1.5 + version: 2.1.5 devDependencies: '@playwright/test': specifier: ^1.38.1 diff --git a/src/experiments/todo/implementations/html-and-expressjs/app.js b/src/experiments/todo/implementations/html-and-expressjs/app.js index 493c5b2..d9090d8 100644 --- a/src/experiments/todo/implementations/html-and-expressjs/app.js +++ b/src/experiments/todo/implementations/html-and-expressjs/app.js @@ -6,7 +6,7 @@ var logger = require('morgan') var session = require('express-session') var indexRouter = require('./routes/index') -var usersRouter = require('./routes/users') +var createTodoRouter = require('./routes/createTodo') var app = express() @@ -35,7 +35,7 @@ app.use(cookieParser()) app.use(express.static(path.join(__dirname, 'public'))) app.use('/', indexRouter) -app.use('/users', usersRouter) +app.use('/todos/create', createTodoRouter) // catch 404 and forward to error handler app.use(function (req, res, next) { diff --git a/src/experiments/todo/implementations/html-and-expressjs/empty-todo-error.png b/src/experiments/todo/implementations/html-and-expressjs/empty-todo-error.png new file mode 100644 index 0000000000000000000000000000000000000000..39b161f3fc2bf64bccf5c95218086c77258a2e9e GIT binary patch literal 23194 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 literal 0 HcmV?d00001 diff --git a/src/experiments/todo/implementations/html-and-expressjs/package.json b/src/experiments/todo/implementations/html-and-expressjs/package.json index c8b59ed..4ad351e 100644 --- a/src/experiments/todo/implementations/html-and-expressjs/package.json +++ b/src/experiments/todo/implementations/html-and-expressjs/package.json @@ -13,7 +13,7 @@ "command": "nodemon ./bin/www" }, "test": { - "command": "playwright test" + "command": "playwright test --quiet" }, "test:ui": { "command": "playwright test --ui" @@ -27,7 +27,8 @@ "express-session": "^1.17.3", "http-errors": "~1.6.3", "morgan": "~1.9.1", - "pug": "2.0.0-beta11" + "pug": "2.0.0-beta11", + "uid-safe": "^2.1.5" }, "devDependencies": { "@playwright/test": "^1.38.1", diff --git a/src/experiments/todo/implementations/html-and-expressjs/playwright.config.js b/src/experiments/todo/implementations/html-and-expressjs/playwright.config.js index 7f34932..f2d1f8c 100644 --- a/src/experiments/todo/implementations/html-and-expressjs/playwright.config.js +++ b/src/experiments/todo/implementations/html-and-expressjs/playwright.config.js @@ -21,7 +21,8 @@ module.exports = defineConfig({ /* Opt out of parallel tests on CI. */ workers: process.env.CI ? 1 : undefined, /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: 'html', + // Concise 'dot' for CI, default 'list' when running locally + reporter: process.env.CI ? 'dot' : 'list', /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ use: { /* Base URL to use in actions like `await page.goto('/')`. */ diff --git a/src/experiments/todo/implementations/html-and-expressjs/public/stylesheets/style.css b/src/experiments/todo/implementations/html-and-expressjs/public/stylesheets/style.css index 0555a91..75ee0b6 100644 --- a/src/experiments/todo/implementations/html-and-expressjs/public/stylesheets/style.css +++ b/src/experiments/todo/implementations/html-and-expressjs/public/stylesheets/style.css @@ -1,5 +1,6 @@ body { - padding: 50px; + margin: auto; + padding: 1em; font: 14px 'Lucida Grande', Helvetica, @@ -7,6 +8,33 @@ body { sans-serif; } +h1 { + font-size: 2em; + font-weight: bold; +} + +main { + padding: 2em; +} + a { color: #00b7ff; } + +ul { + list-style: circle; +} + +li { + margin-left: 1.5em; +} + +#error { + margin: 1em; + color: red; + font-style: italic; +} + +input[name='new-todo'] { + margin-left: 0.5em; +} diff --git a/src/experiments/todo/implementations/html-and-expressjs/routes/createTodo.js b/src/experiments/todo/implementations/html-and-expressjs/routes/createTodo.js new file mode 100644 index 0000000..26ea1e2 --- /dev/null +++ b/src/experiments/todo/implementations/html-and-expressjs/routes/createTodo.js @@ -0,0 +1,36 @@ +var express = require('express') +var uid = require('uid-safe') + +var router = express.Router() + +router.post('/', function (req, res, next) { + if (!req.session.todos) { + req.session.todos = [] + } + + const newTodoText = req.body['new-todo'] + + if (!newTodoText) { + res.redirect( + '/?e=' + encodeURIComponent('Missing new-todo field value') + ) + return + } + + if (newTodoText.trim().length === 0) { + res.redirect( + '/?e=' + + encodeURIComponent('Empty or whitespace new-todo field value') + ) + return + } + + req.session.todos.push({ + id: uid.sync(18), + text: req.body['new-todo'], + }) + + res.redirect('/') +}) + +module.exports = router diff --git a/src/experiments/todo/implementations/html-and-expressjs/routes/index.js b/src/experiments/todo/implementations/html-and-expressjs/routes/index.js index fcd5302..4dd37c6 100644 --- a/src/experiments/todo/implementations/html-and-expressjs/routes/index.js +++ b/src/experiments/todo/implementations/html-and-expressjs/routes/index.js @@ -10,6 +10,7 @@ router.get('/', function (req, res, next) { res.render('index', { title: 'Our Todos', todos: req.session.todos, + error: req.query.e || null, }) }) diff --git a/src/experiments/todo/implementations/html-and-expressjs/routes/users.js b/src/experiments/todo/implementations/html-and-expressjs/routes/users.js deleted file mode 100644 index a540e24..0000000 --- a/src/experiments/todo/implementations/html-and-expressjs/routes/users.js +++ /dev/null @@ -1,9 +0,0 @@ -var express = require('express') -var router = express.Router() - -/* GET users listing. */ -router.get('/', function (req, res, next) { - res.send('respond with a resource') -}) - -module.exports = router diff --git a/src/experiments/todo/implementations/html-and-expressjs/tests/todos.spec.js b/src/experiments/todo/implementations/html-and-expressjs/tests/todos.spec.js index 6939eb2..65ad92e 100644 --- a/src/experiments/todo/implementations/html-and-expressjs/tests/todos.spec.js +++ b/src/experiments/todo/implementations/html-and-expressjs/tests/todos.spec.js @@ -1,20 +1,132 @@ // @ts-check const { test, expect } = require('@playwright/test') +const CONFIG = { + home: { + url: 'http://localhost:3000/', + titlePattern: /Our Todos/, + newTodo: { + selector: 'input[name="new-todo"]', + }, + newTodoSubmit: { + text: 'Submit', + }, + error: { + selector: '#error', + }, + main: { + selector: '#main', + }, + footer: { + selector: '#footer', + }, + }, +} + test.beforeEach(async ({ page }) => { - await page.goto('http://localhost:3000/') + await page.goto(CONFIG.home.url) }) test('has title', async ({ page }) => { - await expect(page).toHaveTitle(/Our Todos/) + await expect(page).toHaveTitle(CONFIG.home.titlePattern) }) -test('main is hidden without todos', async ({ page }) => { - await expect(page.locator('#main')).toHaveCount(0) +// When there are no todos, #main and #footer should be hidden. +// https://github.com/tastejs/todomvc/blob/master/app-spec.md#no-todos +test.describe('No todos', () => { + test('main is hidden without todos', async ({ page }) => { + await expect(page.locator(CONFIG.home.main.selector)).toHaveCount(0) + }) + + test('footer is hidden without todos', async ({ page }) => { + await expect(page.locator(CONFIG.home.footer.selector)).toHaveCount(0) + }) }) -test('footer is hidden without todos', async ({ page }) => { - await expect(page.locator('#footer')).toHaveCount(0) +/* + New todos are entered in the input at the top of the app. The input element + should be focused when the page is loaded, preferably by using the autofocus + input attribute. Pressing Enter creates the todo, appends it to the todo + list, and clears the input. Make sure to .trim() the input and then check + that it's not empty before creating a new todo. +*/ +// https://github.com/tastejs/todomvc/blob/master/app-spec.md#new-todo +test.describe('New todo', () => { + test('todo input exists', async ({ page }) => { + const newTodoElement = page.locator(CONFIG.home.newTodo.selector) + + await expect(newTodoElement).toBeVisible() + await expect(newTodoElement).toHaveValue('') + }) + + test('todo input has focus', async ({ page }) => { + const newTodoElement = page.locator(CONFIG.home.newTodo.selector) + + const newTodoAutoFocusValue = + await newTodoElement.getAttribute('autofocus') + + await expect(newTodoAutoFocusValue).toBe('') + }) + + test('missing new todo displays error', async ({ page }) => { + const newTodoElement = page.locator(CONFIG.home.newTodo.selector) + + // submits the form + await newTodoElement.press('Enter') + + await page.waitForSelector(CONFIG.home.error.selector) + + await page.screenshot({ path: 'empty-todo-error.png' }) + + const errorElement = page.locator(CONFIG.home.error.selector) + + await expect(errorElement).toBeVisible() + + // BUG: not sure why `toHaveText(...)` is failing + const errorText = await errorElement.textContent() + + expect(errorText).toBe('Missing new-todo field value') + }) + + test('empty new todo displays error', async ({ page }) => { + const newTodoElement = page.locator(CONFIG.home.newTodo.selector) + + // fill with whitespace + await newTodoElement.fill(' ') + + // submits the form + await newTodoElement.press('Enter') + + await page.waitForSelector(CONFIG.home.error.selector) + + const errorElement = page.locator(CONFIG.home.error.selector) + + await expect(errorElement).toBeVisible() + + // BUG: not sure why `toHaveText(...)` is failing + const errorText = await errorElement.textContent() + + expect(errorText).toBe('Empty or whitespace new-todo field value') + }) + + test('enter new todo; main and footer display', async ({ page }) => { + const newTodoElement = page.locator(CONFIG.home.newTodo.selector) + await newTodoElement.fill('testing a new todo') + + // submits the form + await newTodoElement.press('Enter') + + await page.waitForSelector(CONFIG.home.main.selector) + + const newTodoElementAfterRedirect = page.locator( + CONFIG.home.newTodo.selector + ) + await expect(newTodoElementAfterRedirect).toHaveValue('') + + await expect(page.locator(CONFIG.home.error.selector)).not.toBeVisible() + await expect(page.locator(CONFIG.home.main.selector)).toBeVisible() + await expect(page.locator(CONFIG.home.footer.selector)).toBeVisible() + }) }) // test('get started link', async ({ page }) => { diff --git a/src/experiments/todo/implementations/html-and-expressjs/views/index.pug b/src/experiments/todo/implementations/html-and-expressjs/views/index.pug index 2e2503a..8fd7975 100644 --- a/src/experiments/todo/implementations/html-and-expressjs/views/index.pug +++ b/src/experiments/todo/implementations/html-and-expressjs/views/index.pug @@ -2,7 +2,17 @@ extends layout block content header - h1= title + h1=title + main + form(method="POST",action="/todos/create") + label New todo + input(name="new-todo",placeholder="Describe the todo",value="",autofocus) + if error + div#error= error + if todos.length > 0 + section#main + ul + each todo in todos + li(data-id=todo.id) #{todo.text} if todos.length > 0 - main#main Some main content footer#footer Some footer content