diff --git a/README.md b/README.md index cf34026..79655c1 100644 --- a/README.md +++ b/README.md @@ -261,3 +261,11 @@ To run the e2e tests run - ```shell npm run test:e2e ``` + +## Developing + +The CI workflows for this project currently depend upon a v1 package lockfile. When generating a new package-lock.json ensure you generate a v1 lockfile - + +```shell +npm install --lockfile-version 1 +``` diff --git a/codecept.conf.js b/codecept.conf.js index 5809323..7f00f40 100644 --- a/codecept.conf.js +++ b/codecept.conf.js @@ -9,6 +9,9 @@ exports.config = { tests: './src/__tests__/e2e/*.test.js', output: './output', helpers: { + ChaiWrapper: { + require: 'codeceptjs-chai', + }, Puppeteer: { url: 'http://localhost:8080/', show: true, diff --git a/package-lock.json b/package-lock.json index 1222a57..834d8d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "kainos-govuk-datepicker", - "version": "1.3.0", + "version": "1.3.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2763,7 +2763,8 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.0.tgz", "integrity": "sha512-ttOkEkoalEHa7RaFYpM0ErK1xc4twg3Am9hfHhL7MVqlHebnkYd2wuI/ZqTDj0cVzZho6PdinY0phFZV3O0Mzg==", - "dev": true + "dev": true, + "requires": {} }, "@webpack-cli/info": { "version": "1.4.0", @@ -2778,7 +2779,8 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.0.tgz", "integrity": "sha512-ZkVeqEmRpBV2GHvjjUZqEai2PpUbuq8Bqd//vEYsp63J8WyexI8ppCqVS3Zs0QADf6aWuPdU+0XsPI647PVlQA==", - "dev": true + "dev": true, + "requires": {} }, "@xmldom/xmldom": { "version": "0.8.10", @@ -2840,7 +2842,8 @@ "version": "5.3.2", "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true + "dev": true, + "requires": {} }, "acorn-walk": { "version": "7.2.0", @@ -2883,7 +2886,8 @@ "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true + "dev": true, + "requires": {} }, "ansi-colors": { "version": "4.1.1", @@ -3748,6 +3752,47 @@ "lodash-pickdeep": "^1.0.2" } }, + "chai-exclude": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/chai-exclude/-/chai-exclude-2.1.0.tgz", + "integrity": "sha512-IBnm50Mvl3O1YhPpTgbU8MK0Gw7NHcb18WT2TxGdPKOMtdtZVKLHmQwdvOF7mTlHVQStbXuZKFwkevFtbHjpVg==", + "dev": true, + "requires": { + "fclone": "^1.0.11" + } + }, + "chai-json-schema": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/chai-json-schema/-/chai-json-schema-1.5.1.tgz", + "integrity": "sha512-TR/xPDxRhqwFFCWg1HgL8nNWbpNfUwaib6pBN++QKpnd0t+o3+MBvAn5CM1mpdUMaM76oJAtUjGKdjGad01lIA==", + "dev": true, + "requires": { + "jsonpointer.js": "0.4.0", + "tv4": "^1.3.0" + } + }, + "chai-json-schema-ajv": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/chai-json-schema-ajv/-/chai-json-schema-ajv-5.2.4.tgz", + "integrity": "sha512-KjbsSQUZDT4ed/TYmxgoMXU+qTv6KtI+QTzkjVQNNBEc5DAmmKoYwexCOxxTW15tt33muqRwvuq79v52piZZbw==", + "dev": true + }, + "chai-match-pattern": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/chai-match-pattern/-/chai-match-pattern-1.3.0.tgz", + "integrity": "sha512-DflyfI8lZ56YuYAZMTBPWghjqFQfqY1IR0ZZXrjlGZJuRvtN0TjJMBpLsrMfc45kjivXJ06iayuP7lzG6ij1bQ==", + "dev": true, + "requires": { + "lodash-match-pattern": "^2.3.1" + } + }, + "chai-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/chai-string/-/chai-string-1.5.0.tgz", + "integrity": "sha512-sydDC3S3pNAQMYwJrs6dQX0oBQ6KfIPuOZ78n7rocW0eJJlsHPh2t3kwW7xfwYA/1Bf6/arGtSUo16rxR2JFlw==", + "dev": true, + "requires": {} + }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", @@ -3780,6 +3825,16 @@ "get-func-name": "^2.0.2" } }, + "checkit": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/checkit/-/checkit-0.7.0.tgz", + "integrity": "sha512-QgiWB8gMdF/CbmWyuxCk+f2MPQe0G1DfJfHCTbrfZlY3FnJWdnW+EGsRJctcYz/IrXxPYJmjRjdgmKUkyIZl/Q==", + "dev": true, + "requires": { + "inherits": "^2.0.1", + "lodash": "^4.0.0" + } + }, "chokidar": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.2.tgz", @@ -4081,6 +4136,21 @@ } } }, + "codeceptjs-chai": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/codeceptjs-chai/-/codeceptjs-chai-2.3.5.tgz", + "integrity": "sha512-njoLonJdUWBLIpUvyXWhAAzxoYfr2cfBu5R1m0vr0U0VvsZCgAPKYhRJsxlk+5QVYwEequ2rK1ISH3BLoxB+WQ==", + "dev": true, + "requires": { + "ajv": "^6.12.2", + "chai": "^4.2.0", + "chai-exclude": "^2.1.0", + "chai-json-schema": "^1.5.1", + "chai-json-schema-ajv": "^5.1.0", + "chai-match-pattern": "^1.3.0", + "chai-string": "^1.5.0" + } + }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -5448,6 +5518,12 @@ "bser": "2.1.1" } }, + "fclone": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/fclone/-/fclone-1.0.11.tgz", + "integrity": "sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw==", + "dev": true + }, "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -6085,7 +6161,8 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", - "dev": true + "dev": true, + "requires": {} }, "ieee754": { "version": "1.2.1", @@ -7239,7 +7316,8 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true + "dev": true, + "requires": {} }, "jest-regex-util": { "version": "28.0.2", @@ -8106,6 +8184,12 @@ "graceful-fs": "^4.1.6" } }, + "jsonpointer.js": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/jsonpointer.js/-/jsonpointer.js-0.4.0.tgz", + "integrity": "sha512-2bf/1crAmPpsmj1I6rDT6W0SOErkrNBpb555xNWcMVWYrX6VnXpG0GRMQ2shvOHwafpfse8q0gnzPFYVH6Tqdg==", + "dev": true + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -8189,6 +8273,78 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash-checkit": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/lodash-checkit/-/lodash-checkit-2.4.1.tgz", + "integrity": "sha512-OAg5CqY04/dnsO8izxXqlleuj7z/dOk6yV0pm0TVtRaUwG5v2PGw4XWSIG/dLK0UWYk7g0/TCk8OCf50oVwv6w==", + "dev": true, + "requires": { + "checkit": "^0.7.0", + "lodash": "^4.17.21" + } + }, + "lodash-match-pattern": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/lodash-match-pattern/-/lodash-match-pattern-2.3.1.tgz", + "integrity": "sha512-dpltpxoTqs94gGFm24VwHDyFh3/eNtqNjKrlnifIBLtnzYq0nAlNM6BIeLdGAfCWC/BwNtiLL1eKZTQpLVnY6A==", + "dev": true, + "requires": { + "chalk": "^4.1.0", + "he": "^1.2.0", + "lodash-checkit": "^2.4.1" + }, + "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "lodash-pickdeep": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/lodash-pickdeep/-/lodash-pickdeep-1.0.2.tgz", @@ -9338,7 +9494,8 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", - "dev": true + "dev": true, + "requires": {} }, "postcss-modules-local-by-default": { "version": "4.0.0", @@ -9578,7 +9735,8 @@ "version": "8.8.0", "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.0.tgz", "integrity": "sha512-JDAgSYQ1ksuwqfChJusw1LSJ8BizJ2e/vVu5Lxjq3YvNJNlROv1ui4i+c/kUUrPheBvQl4c5UbERhTwKa6QBJQ==", - "dev": true + "dev": true, + "requires": {} } } }, @@ -10218,6 +10376,23 @@ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", "dev": true }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + } + } + }, "string-length": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.2.tgz", @@ -10275,23 +10450,6 @@ "define-properties": "^1.1.3" } }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - } - } - }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -10717,6 +10875,12 @@ } } }, + "tv4": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tv4/-/tv4-1.3.0.tgz", + "integrity": "sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw==", + "dev": true + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -10748,6 +10912,13 @@ "mime-types": "~2.1.24" } }, + "typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "peer": true + }, "uglify-js": { "version": "3.17.4", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", @@ -11000,7 +11171,8 @@ "version": "1.8.0", "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true + "dev": true, + "requires": {} }, "schema-utils": { "version": "3.1.1", @@ -11245,7 +11417,8 @@ "version": "8.8.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", - "dev": true + "dev": true, + "requires": {} }, "xml-name-validator": { "version": "4.0.0", diff --git a/package.json b/package.json index 010032e..f99b0e2 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "dist", "src/scss/datepicker.scss" ], - "version": "1.3.0", + "version": "1.3.1", "description": "GOV.UK datepicker", "scripts": { "build": "webpack --config ./bin/webpack.dev.js", @@ -29,6 +29,7 @@ "babel-loader": "^8.2.3", "clean-webpack-plugin": "^4.0.0", "codeceptjs": "^3.3.4", + "codeceptjs-chai": "^2.3.5", "css-loader": "^6.5.1", "eslint": "^8.2.0", "eslint-config-airbnb-base": "^15.0.0", diff --git a/src/__tests__/e2e/date-picker.test.js b/src/__tests__/e2e/date-picker.test.js index 3b8e819..d4649cc 100644 --- a/src/__tests__/e2e/date-picker.test.js +++ b/src/__tests__/e2e/date-picker.test.js @@ -160,6 +160,67 @@ Scenario('it should allow for navigation to the next month using down arrow key }); }); +Scenario('it should allow for navigation from the close button to the previous month button using tab key only', async ({ I }) => { + const currentMonth = DateTime.fromObject({ year: 2021, month: 11, day: 1 }); + let focusedElementText; + await within('.date-picker-default', async () => { + await I.seeElement('#example-1-day'); + await I.fillField('#example-1-day', currentMonth.day.toString()); + await I.fillField('#example-1-month', currentMonth.month.toString()); + await I.fillField('#example-1-year', currentMonth.year.toString()); + + I.click('Choose date'); + I.see(getFormattedMonthAndYear(currentMonth)); + + I.pressKey('Tab'); + await I.usePuppeteerTo('Get the active element', async ({ page }) => { + const focusedElementHandle = await page.evaluateHandle(() => document.activeElement); + focusedElementText = await page.evaluate((el) => el.innerText, focusedElementHandle); + }); + + I.assertEqual(focusedElementText, 'Cancel'); + + I.pressKey('Tab'); + await I.usePuppeteerTo('Get the active element', async ({ page }) => { + const focusedElementHandle = await page.evaluateHandle(() => document.activeElement); + focusedElementText = await page.evaluate((el) => el.innerText, focusedElementHandle); + }); + + I.assertEqual(focusedElementText, 'Previous month'); + }); +}); + +Scenario('it should allow for navigation from the previous month button to close button using shift tab keys only', async ({ I }) => { + const currentMonth = DateTime.fromObject({ year: 2021, month: 11, day: 1 }); + let focusedElementText; + await within('.date-picker-default', async () => { + await I.seeElement('#example-1-day'); + await I.fillField('#example-1-day', currentMonth.day.toString()); + await I.fillField('#example-1-month', currentMonth.month.toString()); + await I.fillField('#example-1-year', currentMonth.year.toString()); + + I.click('Choose date'); + I.see(getFormattedMonthAndYear(currentMonth)); + + I.pressKey(['Shift', 'Tab']); + I.pressKey(['Shift', 'Tab']); + await I.usePuppeteerTo('Get the active element', async ({ page }) => { + const focusedElementHandle = await page.evaluateHandle(() => document.activeElement); + focusedElementText = await page.evaluate((el) => el.innerText, focusedElementHandle); + }); + + I.assertEqual(focusedElementText, 'Previous month'); + + I.pressKey(['Shift', 'Tab']); + await I.usePuppeteerTo('Get the active element', async ({ page }) => { + const focusedElementHandle = await page.evaluateHandle(() => document.activeElement); + focusedElementText = await page.evaluate((el) => el.innerText, focusedElementHandle); + }); + + I.assertEqual(focusedElementText, 'Cancel'); + }); +}); + Scenario('it should not change the value of another date picker when multiple present', async ({ I }) => { const today = DateTime.now(); const baseDate = DateTime.fromObject({ year: 2021, month: 11, day: 15 }); diff --git a/src/js/datepicker.js b/src/js/datepicker.js index 0b1e369..91a0447 100644 --- a/src/js/datepicker.js +++ b/src/js/datepicker.js @@ -487,7 +487,11 @@ function datePicker(datePickerElement, options = {}, callbacks = {}) { if (state.isOpen && event.target !== elements.dialog && !elements.dialog.contains(event.target)) { event.stopPropagation(); - elements.dialog.focus(); + if (event.relatedTarget === elements.buttons.closeButton) { + elements.buttons.previousMonthButton.focus(); + } else { + elements.buttons.closeButton.focus(); + } } }