Skip to content
Snippets Groups Projects
Commit 6697d93c authored by Albin Henriksson's avatar Albin Henriksson
Browse files

Resolve "Increase client test coverage"

parent 40ae09c4
No related branches found
No related tags found
1 merge request!131Resolve "Increase client test coverage"
Pipeline #44422 passed with warnings
Showing
with 667 additions and 159 deletions
......@@ -46,7 +46,7 @@ client:test:
- merge_requests
script:
- cd client
- npm run test:coverage
- npm run unit-test:coverage
coverage: /All files\s*\|\s*([\d\.]+)/
artifacts:
paths:
......
{
"version": "2.0.0",
"tasks": [
{
"label": "Start client",
"type": "npm",
"script": "start",
"path": "client/",
"group": "build",
"problemMatcher": [],
"presentation": {
"group": "Client/Server"
}
},
{
"label": "Test client",
"type": "npm",
"script": "test:coverage:html",
"path": "client/",
"group": "build",
"problemMatcher": [],
},
{
"label": "Open client coverage",
"type": "shell",
"command": "start ./output/coverage/jest/index.html",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/client"
},
},
{
"label": "Start server",
"type": "shell",
"group": "build",
"command": "env/Scripts/python main.py",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server"
},
"presentation": {
"group": "Client/Server"
}
},
{
"label": "Test server",
"type": "shell",
"group": "build",
"command": "env/Scripts/pytest.exe --cov-report html --cov app tests/",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server"
},
},
{
"label": "Populate database",
"type": "shell",
"group": "build",
"command": "env/Scripts/python populate.py",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server"
},
},
{
"label": "Open server coverage",
"type": "shell",
"command": "start ./htmlcov/index.html",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server"
},
},
{
"label": "Generate server documentation",
"type": "shell",
"command": "../env/Scripts/activate; ./make html",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server/docs"
},
},
{
"label": "Open server documentation",
"type": "shell",
"command": "start index.html",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server/docs/build/html"
},
},
{
"label": "Start client and server",
"group": "build",
"dependsOn": [
"Start server",
"Start client"
],
"problemMatcher": []
}
]
}
\ No newline at end of file
"version": "2.0.0",
"tasks": [
{
"label": "Start client",
"type": "npm",
"script": "start",
"path": "client/",
"group": "build",
"problemMatcher": [],
"presentation": {
"group": "Client/Server"
}
},
{
"label": "Unit tests",
"type": "npm",
"script": "unit-test:coverage:html",
"path": "client/",
"group": "build",
"detail": "Run unit tests on client",
"problemMatcher": []
},
{
"label": "Run e2e tests",
"type": "npm",
"script": "e2e-test",
"path": "client/",
"group": "build",
"problemMatcher": [],
"detail": "Make sure client and server is running before executing."
},
{
"label": "Open client coverage",
"type": "shell",
"command": "start ./output/coverage/jest/index.html",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/client"
}
},
{
"label": "Start server",
"type": "shell",
"group": "build",
"command": "env/Scripts/python main.py",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server"
},
"presentation": {
"group": "Client/Server"
}
},
{
"label": "Test server",
"type": "shell",
"group": "build",
"command": "env/Scripts/pytest.exe --cov-report html --cov app tests/",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server"
}
},
{
"label": "Populate database",
"type": "shell",
"group": "build",
"command": "env/Scripts/python populate.py",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server"
}
},
{
"label": "Open server coverage",
"type": "shell",
"command": "start ./htmlcov/index.html",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server"
}
},
{
"label": "Generate server documentation",
"type": "shell",
"command": "../env/Scripts/activate; ./make html",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server/docs"
}
},
{
"label": "Open server documentation",
"type": "shell",
"command": "start index.html",
"problemMatcher": [],
"options": {
"cwd": "${workspaceFolder}/server/docs/build/html"
}
},
{
"label": "Start client and server",
"group": "build",
"dependsOn": ["Start server", "Start client"],
"problemMatcher": []
}
]
}
......@@ -2705,6 +2705,15 @@
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz",
"integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA=="
},
"@types/yauzl": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
"integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==",
"optional": true,
"requires": {
"@types/node": "*"
}
},
"@typescript-eslint/eslint-plugin": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.2.0.tgz",
......@@ -3134,6 +3143,14 @@
"regex-parser": "^2.2.11"
}
},
"agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"requires": {
"debug": "4"
}
},
"aggregate-error": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
......@@ -4094,6 +4111,27 @@
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
"optional": true
},
"bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
},
"dependencies": {
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
}
}
},
"bluebird": {
"version": "3.7.2",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
......@@ -4295,6 +4333,11 @@
"isarray": "^1.0.0"
}
},
"buffer-crc32": {
"version": "0.2.13",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
"integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
},
"buffer-from": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
......@@ -5805,6 +5848,11 @@
}
}
},
"devtools-protocol": {
"version": "0.0.869402",
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.869402.tgz",
"integrity": "sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA=="
},
"diff-sequences": {
"version": "26.6.2",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz",
......@@ -7357,6 +7405,27 @@
}
}
},
"extract-zip": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
"integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
"requires": {
"@types/yauzl": "^2.9.1",
"debug": "^4.1.1",
"get-stream": "^5.1.0",
"yauzl": "^2.10.0"
},
"dependencies": {
"get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"requires": {
"pump": "^3.0.0"
}
}
}
},
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
......@@ -7425,6 +7494,14 @@
"bser": "2.1.1"
}
},
"fd-slicer": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
"integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
"requires": {
"pend": "~1.2.0"
}
},
"figgy-pudding": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz",
......@@ -7785,6 +7862,11 @@
}
}
},
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"fs-extra": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
......@@ -8497,6 +8579,15 @@
"resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz",
"integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM="
},
"https-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
"requires": {
"agent-base": "6",
"debug": "4"
}
},
"human-signals": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
......@@ -11349,6 +11440,11 @@
"minimist": "^1.2.5"
}
},
"mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"moo": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/moo/-/moo-0.5.1.tgz",
......@@ -11494,6 +11590,11 @@
}
}
},
"node-fetch": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
"integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
},
"node-forge": {
"version": "0.10.0",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.10.0.tgz",
......@@ -12133,6 +12234,11 @@
"sha.js": "^2.4.8"
}
},
"pend": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
"integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
......@@ -13434,6 +13540,11 @@
"ipaddr.js": "1.9.1"
}
},
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"prr": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
......@@ -13499,6 +13610,35 @@
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
},
"puppeteer": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-9.1.1.tgz",
"integrity": "sha512-W+nOulP2tYd/ZG99WuZC/I5ljjQQ7EUw/jQGcIb9eu8mDlZxNY2SgcJXTLG9h5gRvqA3uJOe4hZXYsd3EqioMw==",
"requires": {
"debug": "^4.1.0",
"devtools-protocol": "0.0.869402",
"extract-zip": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"node-fetch": "^2.6.1",
"pkg-dir": "^4.2.0",
"progress": "^2.0.1",
"proxy-from-env": "^1.1.0",
"rimraf": "^3.0.2",
"tar-fs": "^2.0.0",
"unbzip2-stream": "^1.3.3",
"ws": "^7.2.3"
},
"dependencies": {
"pkg-dir": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
"requires": {
"find-up": "^4.0.0"
}
}
}
},
"q": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
......@@ -15976,6 +16116,36 @@
}
}
},
"tar-fs": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
},
"dependencies": {
"chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
}
}
},
"tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"requires": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
}
},
"temp-dir": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz",
......@@ -16143,6 +16313,11 @@
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA=="
},
"through": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
"integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
},
"through2": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz",
......@@ -16419,6 +16594,26 @@
"which-boxed-primitive": "^1.0.1"
}
},
"unbzip2-stream": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
"integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
"requires": {
"buffer": "^5.2.1",
"through": "^2.3.8"
},
"dependencies": {
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
}
}
},
"unicode-canonical-property-names-ecmascript": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz",
......@@ -18280,6 +18475,15 @@
}
}
},
"yauzl": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
"integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
"requires": {
"buffer-crc32": "~0.2.3",
"fd-slicer": "~1.1.0"
}
},
"yeast": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
......
......@@ -22,6 +22,7 @@
"axios": "^0.21.1",
"formik": "^2.2.6",
"jwt-decode": "^3.1.2",
"puppeteer": "^9.1.1",
"react": "^17.0.1",
"react-axios": "^2.0.4",
"react-beautiful-dnd": "^13.1.0",
......@@ -70,8 +71,9 @@
"test": "react-scripts test",
"eject": "react-scripts eject",
"lint": "eslint \"./src/**/*.{ts,tsx}\"",
"test:coverage": "react-scripts test --coverage --coverageDirectory=output/coverage/jest",
"test:coverage:html": "npm test -- --coverage --watchAll=false --coverageDirectory=output/coverage/jest"
"unit-test:coverage": "react-scripts test --coverage --testPathIgnorePatterns=src/e2e --coverageDirectory=output/coverage/jest",
"unit-test:coverage:html": "npm test -- --testPathIgnorePatterns=src/e2e --coverage --watchAll=false --coverageDirectory=output/coverage/jest",
"e2e-test": "npm test -- --testPathPattern=src/e2e"
},
"browserslist": {
"production": [
......@@ -89,6 +91,7 @@
"collectCoverageFrom": [
"src/**/*.{tsx,ts}",
"!src/index.tsx",
"!src/e2e/*",
"!src/reportWebVitals.ts",
"!src/components/TestConnection.tsx"
],
......
client/public/logo192.png

5.22 KiB

client/public/logo512.png

9.44 KiB

{
"short_name": "React App",
"name": "Create React App Sample",
"short_name": "Teknikåttan",
"name": "Teknikåttan Scoring System",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
......
import mockedAxios from 'axios'
import expect from 'expect' // You can use any testing library
import { createMemoryHistory } from 'history'
import configureMockStore from 'redux-mock-store'
import thunk from 'redux-thunk'
import { loginCompetition, logoutCompetition } from './competitionLogin'
import Types from './types'
const middlewares = [thunk]
const mockStore = configureMockStore(middlewares)
it('dispatches correct actions when logging into competition', async () => {
const compRes: any = {
data: {
id: 5,
slides: [],
},
}
const compLoginDataRes: any = {
data: {
access_token: 'TEST_ACCESS_TOKEN',
competition_id: 'test_name',
team_id: 'test_team',
view: 'test_view',
},
}
;(mockedAxios.post as jest.Mock).mockImplementation(() => {
return Promise.resolve(compLoginDataRes)
})
;(mockedAxios.get as jest.Mock).mockImplementation(() => {
return Promise.resolve(compRes)
})
const expectedActions = [
{ type: Types.LOADING_COMPETITION_LOGIN },
{ type: Types.CLEAR_COMPETITION_LOGIN_ERRORS },
{
type: Types.SET_COMPETITION_LOGIN_DATA,
payload: {
competition_id: compLoginDataRes.data.competition_id,
team_id: compLoginDataRes.data.team_id,
view: compLoginDataRes.data.view,
},
},
{ type: Types.SET_PRESENTATION_COMPETITION, payload: compRes.data },
]
const store = mockStore({})
const history = createMemoryHistory()
await loginCompetition('code', history, true)(store.dispatch, store.getState as any)
expect(store.getActions()).toEqual(expectedActions)
})
it('dispatches correct action when logging out from competition', async () => {
;(mockedAxios.post as jest.Mock).mockImplementation(() => {
return Promise.resolve({ data: {} })
})
const store = mockStore({})
await logoutCompetition()(store.dispatch)
expect(store.getActions()).toEqual([{ type: Types.SET_COMPETITION_LOGIN_UNAUTHENTICATED }])
})
it('dispatches correct action when failing to log in user', async () => {
console.log = jest.fn()
const errorMessage = 'getting teams failed'
;(mockedAxios.post as jest.Mock).mockImplementation(() => {
return Promise.reject({ response: { data: errorMessage } })
})
const store = mockStore({})
const history = createMemoryHistory()
const expectedActions = [
{ type: Types.LOADING_COMPETITION_LOGIN },
{ type: Types.SET_COMPETITION_LOGIN_ERRORS, payload: errorMessage },
]
await loginCompetition('code', history, true)(store.dispatch, store.getState as any)
expect(store.getActions()).toEqual(expectedActions)
expect(console.log).toHaveBeenCalled()
})
......@@ -20,13 +20,13 @@ it('dispatches no actions when failing to get competitions', async () => {
return Promise.reject(new Error('getting competitions failed'))
})
const store = mockStore({ competitions: { filterParams: [] } })
await getPresentationCompetition('0')(store.dispatch)
await getPresentationCompetition('0')(store.dispatch, store.getState as any)
expect(store.getActions()).toEqual([])
expect(console.log).toHaveBeenCalled()
})
it('dispatches correct actions when setting slide', () => {
const testSlide: Slide = { competition_id: 0, id: 5, order: 5, timer: 20, title: '', background_image_id: 0 }
const testSlide: Slide = { competition_id: 0, id: 5, order: 5, timer: 20, title: '', background_image: undefined }
const expectedActions = [{ type: Types.SET_PRESENTATION_SLIDE, payload: testSlide }]
const store = mockStore({})
setCurrentSlide(testSlide)(store.dispatch)
......
......@@ -17,7 +17,7 @@ export const getPresentationCompetition = (id: string) => async (dispatch: AppDi
type: Types.SET_PRESENTATION_COMPETITION,
payload: res.data,
})
if (getState().presentation.slide.id === -1 && res.data.slides[0]) {
if (getState().presentation?.slide.id === -1 && res.data?.slides[0]) {
setCurrentSlideByOrder(0)(dispatch)
}
})
......
import axios from 'axios'
import React, { useEffect, useState } from 'react'
interface Message {
message: string
}
const TestConnection: React.FC = () => {
const [currentMessage, setCurrentMessage] = useState<Message>()
useEffect(() => {
axios.get<Message>('users/test').then((response) => {
setCurrentMessage(response.data)
})
}, [])
return <p>Connection with server is: {currentMessage?.message}</p>
}
export default TestConnection
import puppeteer from 'puppeteer'
import { CLIENT_URL, DEVTOOLS_ENABLED, HEADLESS_ENABLED, SLOW_DOWN_FACTOR } from './TestingConstants'
describe('Admin page', () => {
const userEmailSelector = '[data-testid="userEmail"]'
const buttonSelector = '[data-testid="submit"]'
const emailSelector = '[data-testid="email"]'
const passwordSelector = '[data-testid="password"]'
let browser: puppeteer.Browser
let page: puppeteer.Page
jest.setTimeout(10000)
beforeEach(async () => {
// Set up testing environment
browser = await puppeteer.launch({
headless: HEADLESS_ENABLED,
devtools: DEVTOOLS_ENABLED,
slowMo: SLOW_DOWN_FACTOR,
})
page = await browser.newPage()
//Navigate to login screen and log in
await page.goto(CLIENT_URL)
await page.waitForSelector('.MuiFormControl-root')
await page.click(emailSelector)
await page.keyboard.type('admin@test.se')
await page.click(passwordSelector)
await page.keyboard.type('password')
await page.click(buttonSelector)
await page.waitForTimeout(2000)
})
afterEach(async () => {
await browser.close()
})
it('Should show correct email on welcome screen', async () => {
const AdminTitle = await page.evaluate((sel) => {
return document.querySelector(sel).innerText
}, userEmailSelector)
expect(AdminTitle).toEqual('Email: admin@test.se')
}, 9000000)
it('Should be able to add and remove region', async () => {
const regionTabSelector = '[data-testid="Regioner"]'
const regionTextFieldSelector = '[data-testid="regionTextField"]'
const regionSubmitButton = '[data-testid="regionSubmitButton"]'
const testRegionName = 'New region test'
const testRegionSelector = `[data-testid="${testRegionName}"]`
const removeRegionButtonSelector = '[data-testid="removeRegionButton"]'
//Navigate to region tab
await page.click(regionTabSelector)
await page.waitForSelector('.MuiFormControl-root')
//Make sure the test region isnt already in the list
let regions = await page.$$eval('.MuiTableRow-root > td', (elList) => elList.map((p) => p.textContent))
expect(regions).not.toContain(testRegionName)
//Add the test region to the list and make sure it's present
await page.click(regionTextFieldSelector)
await page.keyboard.type(testRegionName)
await page.click(regionSubmitButton)
await page.waitForTimeout(1000)
regions = await page.$$eval('.MuiTableRow-root > td', (elList) => elList.map((p) => p.textContent))
expect(regions).toContain(testRegionName)
//Remove the test region from the list and make sure it's gone
await page.click(testRegionSelector)
await page.click(removeRegionButtonSelector)
await page.waitForTimeout(1000)
regions = await page.$$eval('.MuiTableRow-root > td', (elList) => elList.map((p) => p.textContent))
expect(regions).not.toContain(testRegionName)
}, 9000000)
it('Should be able to add and remove a user', async () => {
const userTabSelector = '[data-testid="Användare"]'
const addUserButtonSelector = '[data-testid="addUserButton"]'
const addUserEmailSelector = '[data-testid="addUserEmail"]'
const addUserPasswordSelector = '[data-testid="addUserPassword"]'
const addUserNameSelector = '[data-testid="addUserName"]'
const userCitySelectSelector = '[data-testid="userCitySelect"]'
const userRoleSelectSelector = '[data-testid="userRoleSelect"]'
const addUserSubmitSelector = '[data-testid="addUserSubmit"]'
const removeUserSelector = '[data-testid="removeUser"]'
const accceptRemoveUserSelector = '[data-testid="acceptRemoveUser"]'
const testUserEmail = 'NewUser@test.test'
const testUserPassword = 'TestPassword'
const testUserName = 'TestUserName'
const testUserCity = 'Linköping'
const testUserRole = 'Admin'
const userCitySelector = `[data-testid="${testUserCity}"]`
const userRoleSelector = `[data-testid="${testUserRole}"]`
const moreSelector = `[data-testid="more-${testUserEmail}"]`
//Navigate to user tab
await page.click(userTabSelector)
await page.waitForSelector(addUserButtonSelector)
//Make sure the test user isnt already in the list
let emails = await page.$$eval('.MuiTableRow-root > td', (elList) => elList.map((p) => p.textContent))
expect(emails).not.toContain(testUserEmail)
//Add the test user to the list and make sure it's present
await page.click(addUserButtonSelector)
await page.click(addUserEmailSelector)
await page.keyboard.type(testUserEmail)
await page.click(addUserPasswordSelector)
await page.keyboard.type(testUserPassword)
await page.click(addUserNameSelector)
await page.keyboard.type(testUserName)
await page.click(userCitySelectSelector)
await page.click(userCitySelector)
await page.waitForTimeout(100)
await page.click(userRoleSelectSelector)
await page.waitForTimeout(100)
await page.click(userRoleSelector)
await page.waitForTimeout(100)
await page.click(addUserSubmitSelector)
await page.waitForTimeout(1000)
emails = await page.$$eval('.MuiTableRow-root > td', (elList) => elList.map((p) => p.textContent))
expect(emails).toContain(testUserEmail)
//Remove the test user from the list and make sure it's gone
await page.click(moreSelector)
await page.click(removeUserSelector)
await page.click(accceptRemoveUserSelector)
await page.waitForTimeout(1000)
emails = await page.$$eval('.MuiTableRow-root > td', (elList) => elList.map((p) => p.textContent))
expect(emails).not.toContain(testUserEmail)
}, 9000000)
it('Should be able to add and remove competition', async () => {
const competitionsTabSelector = '[data-testid="Tävlingshanterare"]'
const addCompetitionsButtonSelector = '[data-testid="addCompetition"]'
const competitionNameSelector = '[data-testid="competitionName"]'
const competitionRegionSelectSelector = '[data-testid="competitionRegion"]'
const acceptAddCompetition = '[data-testid="acceptCompetition"]'
const removeCompetitionButtonSelector = '[data-testid="removeCompetitionButton"]'
const testCompetitionName = 'New test competition'
const testCompetitionRegion = 'Linköping'
const testCompetitionRegionSelector = `[data-testid="${testCompetitionRegion}"]`
const testCompetitionSelector = `[data-testid="${testCompetitionName}"]`
//Navigate to competitionManager tab
await page.click(competitionsTabSelector)
await page.waitForSelector('.MuiFormControl-root')
//Make sure the test region isnt already in the list
let competitions = await page.$$eval('.MuiTableRow-root > td', (elList) => elList.map((p) => p.textContent))
expect(competitions).not.toContain(testCompetitionName)
//Add the test region to the list and make sure it's present
await page.click(addCompetitionsButtonSelector)
await page.click(competitionNameSelector)
await page.keyboard.type(testCompetitionName)
await page.click(competitionRegionSelectSelector)
await page.waitForTimeout(100)
await page.click(testCompetitionRegionSelector)
await page.waitForTimeout(100)
await page.click(acceptAddCompetition)
await page.waitForTimeout(1000)
competitions = await page.$$eval('.MuiTableRow-root > td', (elList) => elList.map((p) => p.textContent))
expect(competitions).toContain(testCompetitionName)
//Remove the test region from the list and make sure it's gone
await page.click(testCompetitionSelector)
await page.waitForTimeout(100)
await page.click(removeCompetitionButtonSelector)
await page.waitForTimeout(1000)
competitions = await page.$$eval('.MuiTableRow-root > td', (elList) => elList.map((p) => p.textContent))
expect(competitions).not.toContain(testCompetitionName)
}, 9000000)
})
import puppeteer from 'puppeteer'
import { CLIENT_URL, DEVTOOLS_ENABLED, HEADLESS_ENABLED, SLOW_DOWN_FACTOR } from './TestingConstants'
describe('Login page', () => {
const buttonSelector = '[data-testid="submit"]'
const emailSelector = '[data-testid="email"]'
const passwordSelector = '[data-testid="password"]'
let browser: puppeteer.Browser
let page: puppeteer.Page
beforeEach(async () => {
// Set up testing environment
browser = await puppeteer.launch({
headless: HEADLESS_ENABLED,
devtools: DEVTOOLS_ENABLED,
slowMo: SLOW_DOWN_FACTOR,
})
page = await browser.newPage()
//Navigate to login screen
await page.goto(CLIENT_URL)
await page.waitForSelector('.MuiFormControl-root')
})
afterEach(async () => {
await browser.close()
})
it('Can submit login user with correct credentials', async () => {
await page.click(emailSelector)
await page.keyboard.type('admin@test.se')
await page.click(passwordSelector)
await page.keyboard.type('password')
await page.click(buttonSelector)
await page.waitForTimeout(4000)
const AdminTitle = await page.$eval('.MuiTypography-root', (el) => el.textContent)
expect(AdminTitle).toEqual('Startsida')
}, 9000000)
it('Shows correct error message when logging in user with incorrect credentials', async () => {
await page.click(emailSelector)
await page.keyboard.type('wrong@wrong.se')
await page.click(passwordSelector)
await page.keyboard.type('wrongPassword')
await page.click(buttonSelector)
await page.waitForTimeout(1000)
const errorMessages = await page.$$eval('.MuiAlert-message > p', (elList) => elList.map((p) => p.textContent))
// The error message is divided into two p elements
const errorMessageRow1 = errorMessages[0]
const errorMessageRow2 = errorMessages[1]
expect(errorMessageRow1).toEqual('Någonting gick fel. Kontrollera')
expect(errorMessageRow2).toEqual('dina användaruppgifter och försök igen')
}, 9000000)
it('Shows correct error message when email is in incorrect format', async () => {
await page.click(emailSelector)
await page.keyboard.type('email')
await page.click(passwordSelector)
await page.waitForTimeout(1000)
const helperText = await page.$eval('.MuiFormHelperText-root', (el) => el.textContent)
expect(helperText).toEqual('Email inte giltig')
}, 9000000)
it('Shows correct error message when password is too short (<6 chars)', async () => {
await page.click(passwordSelector)
await page.keyboard.type('short')
await page.click(emailSelector)
const helperText = await page.$eval('.MuiFormHelperText-root', (el) => el.textContent)
expect(helperText).toEqual('Lösenord måste vara minst 6 tecken')
}, 9000000)
})
/** This file includes constants for e2e testing */
export const HEADLESS_ENABLED = false
export const DEVTOOLS_ENABLED = false
export const SLOW_DOWN_FACTOR = 20
export const CLIENT_URL = 'http://localhost:3000/'
......@@ -3,7 +3,6 @@ import ReactDOM from 'react-dom'
import { Provider } from 'react-redux'
import App from './App'
import './index.css'
import reportWebVitals from './reportWebVitals'
import store from './store'
// Provider wraps the app component so that it can access store
......@@ -15,8 +14,3 @@ ReactDOM.render(
</Provider>,
document.getElementById('root')
)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
\ No newline at end of file
Redux middleware provides a third-party extension point between dispatching an action,
and the moment it reaches the reducer. People use Redux middleware for logging,
crash reporting, talking to an asynchronous API, routing, and more.
https://redux.js.org/tutorials/fundamentals/part-4-store
\ No newline at end of file
......@@ -91,6 +91,7 @@ const AdminView: React.FC = () => {
const menuItems = isAdmin ? menuAdminItems : menuEditorItems
return menuItems.map((value, index) => (
<ListItem
data-testid={value.text}
button
component={Link}
key={value.text}
......
......@@ -88,6 +88,7 @@ const AddCompetition: React.FC = (props: any) => {
return (
<div>
<AddButton
data-testid="addCompetition"
style={{ backgroundColor: '#4caf50', color: '#fcfcfc' }}
color="default"
variant="contained"
......@@ -124,6 +125,7 @@ const AddCompetition: React.FC = (props: any) => {
{(formik) => (
<AddForm onSubmit={formik.handleSubmit}>
<TextField
data-testid="competitionName"
label="Namn"
name="model.name"
helperText={formik.touched.model?.name ? formik.errors.model?.name : ''}
......@@ -137,6 +139,7 @@ const AddCompetition: React.FC = (props: any) => {
Region
</InputLabel>
<TextField
data-testid="competitionRegion"
select
name="model.city"
id="standard-select-currency"
......@@ -152,7 +155,12 @@ const AddCompetition: React.FC = (props: any) => {
</MenuItem>
{cities &&
cities.map((city) => (
<MenuItem key={city.name} value={city.name} onClick={() => setSelectedCity(city)}>
<MenuItem
key={city.name}
value={city.name}
onClick={() => setSelectedCity(city)}
data-testid={city.name}
>
{city.name}
</MenuItem>
))}
......@@ -170,6 +178,7 @@ const AddCompetition: React.FC = (props: any) => {
margin="normal"
/>
<Button
data-testid="acceptCompetition"
type="submit"
fullWidth
variant="contained"
......
......@@ -251,12 +251,7 @@ const CompetitionManager: React.FC = (props: any) => {
<div>
<TopBar>
<FilterContainer>
<TextField
className={classes.margin}
value={filterParams.name || ''}
onChange={onSearchChange}
label="Sök"
></TextField>
<TextField className={classes.margin} value={filterParams.name || ''} onChange={onSearchChange} label="Sök" />
<FormControl className={classes.margin}>
<InputLabel shrink id="demo-customized-select-native">
Region
......@@ -314,7 +309,7 @@ const CompetitionManager: React.FC = (props: any) => {
<TableCell align="right">{cities.find((city) => city.id === row.city_id)?.name || ''}</TableCell>
<TableCell align="right">{row.year}</TableCell>
<TableCell align="right">
<Button onClick={(event) => handleClick(event, row.id)}>
<Button onClick={(event) => handleClick(event, row.id)} data-testid={row.name}>
<MoreHorizIcon />
</Button>
</TableCell>
......@@ -339,7 +334,9 @@ const CompetitionManager: React.FC = (props: any) => {
<MenuItem onClick={handleStartCompetition}>Starta</MenuItem>
<MenuItem onClick={handleOpenDialog}>Visa koder</MenuItem>
<MenuItem onClick={handleDuplicateCompetition}>Duplicera</MenuItem>
<RemoveMenuItem onClick={handleDeleteCompetition}>Ta bort</RemoveMenuItem>
<RemoveMenuItem onClick={handleDeleteCompetition} data-testid="removeCompetitionButton">
Ta bort
</RemoveMenuItem>
</Menu>
<Dialog
open={dialogIsOpen}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment