diff --git a/exercises/03_acceptance/cucumber/features/add_one.feature b/exercises/03_acceptance/cucumber/features/add_one.feature index 756d5f1..81684bf 100644 --- a/exercises/03_acceptance/cucumber/features/add_one.feature +++ b/exercises/03_acceptance/cucumber/features/add_one.feature @@ -1,12 +1,9 @@ - Feature: Adding Items The user should be able to add a single item to the list. + + Scenario: add item via function + Given I currently have no items + When I add "bread" with the quantity "17" + Then that data exists in memory + -Scenario: add a single item - Given The browser is open on the home page - When I enter "bread" in the "item" field - And I enter "42" in the "qty" field - And I click on the submit button - Then the heading should be "ToDo List" - And the list should contain "1" row - And the item in row "1" should be "bread" diff --git a/exercises/03_acceptance/cucumber/features/manage-todo-list.steps.js b/exercises/03_acceptance/cucumber/features/manage-todo-list.steps.js deleted file mode 100644 index a16504a..0000000 --- a/exercises/03_acceptance/cucumber/features/manage-todo-list.steps.js +++ /dev/null @@ -1,111 +0,0 @@ - -'use strict' - -// Given The browser is open on the home page -// When I enter "bread" in the "item" field -// And I enter "42" in the "qty" field -// And I click on the submit button -// Then the heading should be "ToDo List" -// And the list should contain "1" row -// And the item in row "1" should be "bread" - -const { defineFeature, loadFeature } = require('jest-cucumber') -const feature = loadFeature('./features/add_one.feature') - -const PAGE = 'http://localhost:8080' -const ENTER_EVENT = 'Enter' -const INPUT_SELECTOR = 'section input' -const TODO_ITEMS_SELECTOR = 'ul.todo-list li' -const todoItemSelector = index => `ul.todo-list li:nth-child(${index})` -const todoItemLabelSelector = index => `${todoItemSelector(index)} label` -const deleteButtonSelector = index => `${todoItemSelector(index)} button` - -async function addTodoItem(label) { - await expect(page).toFill(INPUT_SELECTOR, label) - const inputElement = await expect(page).toMatchElement(INPUT_SELECTOR) - await inputElement.press(ENTER_EVENT) -} - -async function deleteTodoItem(index) { - const todoItemElement = await expect(page).toMatchElement( - todoItemSelector(index) - ) - await todoItemElement.hover(); - await expect(page).toClick(deleteButtonSelector(index)) -} - -defineFeature(feature, test => { - let todoList - - beforeEach(async () => { - await page.goto(PAGE) - await page.evaluate(() => { - localStorage.clear() - }) - await page.reload() - todoList = [] - }) - - const givenIHaveTheTodoList = given => { - given("I have the todo list", async table => { - todoList = table - for (let index = 0; index < table.length; index++) { - await addTodoItem(table[index].Label) - } - }) - } - - const thenIExpectTheTodoListToHaveNumberOfItems = then => { - then(/^I expect the todo list to have (\d+) items?$/, async number => { - const todoItemCount = await page.$$eval( - TODO_ITEMS_SELECTOR, - items => items.length - ) - expect(todoItemCount).toBe(parseInt(number)) - }) - } - - test("Add todo item", ({ given, when, then }) => { - givenIHaveTheTodoList(given) - - when(/^I write the todo item "(.*)" in the input field$/, async label => { - await expect(page).toFill(INPUT_SELECTOR, label) - }) - - when('I press enter', async () => { - const inputElement = await expect(page).toMatchElement(INPUT_SELECTOR) - inputElement.press(ENTER_EVENT) - }) - - thenIExpectTheTodoListToHaveNumberOfItems(then) - - then( - /^I expect to see the todo item "(.*)" in the todo list$/, - async label => { - await expect(page).toMatchElement(todoItemLabelSelector(2), { - text: label - }) - } - ) - }) - - test("Delete todo item", ({ given, when, then }) => { - givenIHaveTheTodoList(given) - - when(/^I press the delete button of the todo item "(.*)"$/, async label => { - const index = todoList.findIndex(todoItem => todoItem.Label === label) - await deleteTodoItem(index + 1) - }) - - thenIExpectTheTodoListToHaveNumberOfItems(then) - - then( - /^I expect to not see the todo item "(.*)" in the todo list$/, - async label => { - await expect(page).not.toMatchElement(todoItemLabelSelector(1), { - text: label - }) - } - ) - }) -}) diff --git a/exercises/03_acceptance/cucumber/features/nextFeature.devel b/exercises/03_acceptance/cucumber/features/nextFeature.devel new file mode 100644 index 0000000..7479c16 --- /dev/null +++ b/exercises/03_acceptance/cucumber/features/nextFeature.devel @@ -0,0 +1,10 @@ +#When ready copy the code below into add_one.feature and implement it into add_one.steps.js. + + Scenario: add item via webpage + Given The browser is open on the home page + When I enter "bread" in the "item" field + When I enter "42" in the "qty" field + When I click on the submit button + Then the heading should be "ToDo List" + Then the list should contain "1" row + Then the item in row "1" should be "bread" \ No newline at end of file diff --git a/exercises/03_acceptance/cucumber/package.json b/exercises/03_acceptance/cucumber/package.json index 0b2234e..a7d790e 100644 --- a/exercises/03_acceptance/cucumber/package.json +++ b/exercises/03_acceptance/cucumber/package.json @@ -4,6 +4,7 @@ "description": "", "main": "index.js", "scripts": { + "cucumber": "cucumber-js ./features -r ./steps", "linter": "node_modules/.bin/eslint .", "test": "node_modules/.bin/jest --coverage --runInBand --detectOpenHandles", "watch": "node_modules/.bin/jest --coverage --watchAll", @@ -16,7 +17,7 @@ "http-status-codes": "^1.3.2", "koa": "^2.8.1", "koa-bodyparser": "^4.2.1", - "koa-handlebars": "^1.0.0", + "koa-handlebars": "^2.0.0", "koa-hbs-renderer": "^1.2.0", "koa-router": "^7.4.0", "koa-static": "^5.0.0", @@ -35,6 +36,9 @@ "projects": [ "/jest-test.config.js" ], + "testMatch": [ + "**/*.steps.js" + ], "preset": "jest-puppeteer" } } diff --git a/exercises/03_acceptance/cucumber/readme.md b/exercises/03_acceptance/cucumber/readme.md index 915a7e3..7ab4b6f 100644 --- a/exercises/03_acceptance/cucumber/readme.md +++ b/exercises/03_acceptance/cucumber/readme.md @@ -1,6 +1,71 @@ -# ToDo +# Project Description This project is designed to teach you how to write and debug unit tests and employ the Test-Driven Development (TDD) methodology in your software development. You should open this directory directly in VS Code, ensuring that this file is in the root of the file tree. + +# Cucumber and Gherkin + +To demonstrate TDD we will be using Cucumber and Gherkin in order to make our tests readable by any ordinary person. + +Gherkin is a way to write tests, example: + + Given I currently have no items + When I add "bread" with the quantity "17" + Then that data exists in memory + +Whereas Cucumber is a way to integrate them (to ensure they actually compile and run), example: + + Given('I currently have no items', function () { + // Write code here that turns the phrase above into concrete actions + }); + + When('I add {string} with the quantity {string}', function (string, string2) { + // Write code here that turns the phrase above into concrete actions + }); + + Then('that data exists in memory', function () { + // Write code here that turns the phrase above into concrete actions + }); + +To run your tests just use
 npm run cucumber 
+ +# Installation + +This repository should already be setup to be ready to go, but if you ever want to replicate it in one of your projects here is what you need to do. + +1. Create your own node project (should know how to by now) +2. Install cucumber: +
 npm install --save-dev cucumber 
+3. You can run tests manually as such: +
 node_modules/.bin/cucumber-js ./features -r ./steps
+4. If you want to run them when you type "npm run cucumber", add this to your package.json: +
 "scripts": {
+    "cucumber": "cucumber-js ./features -r ./steps" } 
+5. Put your gherkin files (.feature) in /features and cucumber files (.steps) in /steps. + + +# Activity + +After you've wrapped your head around existing tests try giving "features/nextFeature.devel" a try. It accomplishes the same thing as our unit test, but instead checks the functionality at a higher level (acceptance/integration testing). + +There are some instructions and support code already available in "steps/add_one.steps.js", you just need to implement the tests in the same way we did with our first Scenario. + +Remember to have your website running in a different terminal if you want puppeteer to be able to access it. + +# Notes + +If you want to develop a new Scenario, or even a new Feature, just write the Gherkin tests first (the human readable tests), then try running them. They will obviously not pass, but the terminal will help you write them. + +Example: +
+1) Scenario: add item via function # features/add_one.feature:4
+   ? Given I currently have no items
+       Undefined. Implement with the following snippet:
+
+         Given('I currently have no items', function () {
+           // Write code here that turns the phrase above into concrete actions
+           return 'pending';
+         });
+
diff --git a/exercises/03_acceptance/cucumber/steps/add_one.steps.js b/exercises/03_acceptance/cucumber/steps/add_one.steps.js new file mode 100644 index 0000000..ddcc60f --- /dev/null +++ b/exercises/03_acceptance/cucumber/steps/add_one.steps.js @@ -0,0 +1,83 @@ +'use strict' + +const { Given, When, Then } = require('cucumber') +const assert = require('assert'); + +const todo = require('../modules/todo.js') + +// Scenario: add item via function + Given('I currently have no items', function () { + // Write code here that turns the phrase above into concrete actions + todo.clear() + }); + + When('I add {string} with the quantity {string}', function (string, string2) { + // Write code here that turns the phrase above into concrete actions + todo.add(string, string2) + }); + + Then('that data exists in memory', function () { + // Write code here that turns the phrase above into concrete actions + let currentData = todo.getAll()[0] + assert.equal(currentData.item, "bread") + assert.equal(currentData.qty, 17) + }); + +/* +STARTING CODE TO HELP WITH "nextFeature.devel" + +Copy the contents of nextFeature.devel into add_one.feature, +then come back to this file and implement the features one by one. + +There is already some setup code, so you only need to focus on the tests. + + +const puppeteer = require('puppeteer') + +const PAGE = 'http://localhost:8080' + +const width = 800 +const height = 600 +const delayMS = 5 + +let page +let browser + +async function initialize() { + console.log("Launching browser") + browser = await puppeteer.launch({ headless: true, slowMo: delayMS, args: ['--disable-gpu', `--no-sandbox`, '--disable-dev-shm-usage'] }) + page = await browser.newPage() + await page.setViewport({ width, height }) + await page.goto(PAGE) + await page.evaluate(() => { + localStorage.clear() + }) + await page.reload() +} + +async function addToPage(field, value){ + // add puppeteer code + console.log("In development!") +} + +async function pressSubmit(){ + // add puppeteer code + console.log("In development!") +} + +// Scenario: add item via webpage + Given('The browser is open on the home page', async function () { + await initialize() + }); + + When('I enter {string} in the {string} field', async function (value, field) { + await addToPage(field, value); + }); + + When('I click on the submit button', async () => { + await pressSubmit() + }) + + +}) +*/ \ No newline at end of file