From 6697d93cfc7558de553b8ba2781f04fe83443113 Mon Sep 17 00:00:00 2001
From: Albin Henriksson <albhe428@student.liu.se>
Date: Thu, 6 May 2021 20:06:20 +0000
Subject: [PATCH] Resolve "Increase client test coverage"

---
 .gitlab/client.gitlab-ci.yml                  |   2 +-
 .vscode/tasks.json                            | 209 +++++++++---------
 client/package-lock.json                      | 204 +++++++++++++++++
 client/package.json                           |   7 +-
 client/public/logo192.png                     | Bin 5347 -> 0 bytes
 client/public/logo512.png                     | Bin 9664 -> 0 bytes
 client/public/manifest.json                   |  14 +-
 client/src/actions/competitionLogin.test.ts   |  76 +++++++
 client/src/actions/presentation.test.ts       |   4 +-
 client/src/actions/presentation.ts            |   2 +-
 client/src/components/TestConnection.tsx      |  18 --
 client/src/e2e/AdminPage.test.tsx             | 176 +++++++++++++++
 client/src/e2e/LoginPage.test.tsx             |  70 ++++++
 client/src/e2e/TestingConstants.ts            |   6 +
 client/src/index.tsx                          |   6 -
 client/src/logo.svg                           |   1 -
 .../src/middleware/Middleware_Explanation.txt |   6 -
 client/src/pages/admin/AdminPage.tsx          |   1 +
 .../admin/competitions/AddCompetition.tsx     |  11 +-
 .../admin/competitions/CompetitionManager.tsx |  13 +-
 .../dashboard/components/CurrentUser.tsx      |   4 +-
 client/src/pages/admin/regions/AddRegion.tsx  |   4 +-
 client/src/pages/admin/regions/Regions.tsx    |   6 +-
 client/src/pages/admin/users/AddUser.tsx      |  21 +-
 client/src/pages/admin/users/EditUser.tsx     |   7 +-
 .../pages/admin/users/ResponsiveDialog.tsx    |  53 -----
 .../src/pages/login/components/AdminLogin.tsx |   3 +
 .../components/BackgroundImageSelect.test.tsx |  16 ++
 .../components/CheckboxComponent.tsx          |  31 ---
 .../components/RndComponent.test.tsx          |  17 ++
 .../components/TextComponentEdit.test.tsx     |  17 ++
 .../slideSettingsComponents/Images.test.tsx   |  14 ++
 .../slideSettingsComponents/Images.tsx        |  10 +-
 .../Instructions.test.tsx                     |  14 ++
 .../slideSettingsComponents/Instructions.tsx  |   4 +-
 .../MultipleChoiceAlternatives.test.tsx       |  14 ++
 .../MultipleChoiceAlternatives.tsx            |  43 ++--
 .../QuestionSettings.test.tsx                 |  14 ++
 .../QuestionSettings.tsx                      |   6 +-
 .../SlideType.test.tsx                        |  14 ++
 .../slideSettingsComponents/SlideType.tsx     |   6 +-
 .../slideSettingsComponents/Texts.test.tsx    |  14 ++
 .../slideSettingsComponents/Timer.test.tsx    |  14 ++
 .../src/pages/views/components/SocketTest.tsx |  61 -----
 client/src/reducers/allReducers.ts            |   2 -
 client/src/reducers/mediaReducer.ts           |  41 ----
 client/src/reportWebVitals.ts                 |  15 --
 .../checkAuthenticationCompetition.test.ts    |  79 +++++++
 .../utils/checkAuthenticationCompetition.ts   |   2 +-
 client/src/utils/renderSlideIcon.test.tsx     |  63 ++++++
 50 files changed, 1027 insertions(+), 408 deletions(-)
 delete mode 100644 client/public/logo192.png
 delete mode 100644 client/public/logo512.png
 create mode 100644 client/src/actions/competitionLogin.test.ts
 delete mode 100644 client/src/components/TestConnection.tsx
 create mode 100644 client/src/e2e/AdminPage.test.tsx
 create mode 100644 client/src/e2e/LoginPage.test.tsx
 create mode 100644 client/src/e2e/TestingConstants.ts
 delete mode 100644 client/src/logo.svg
 delete mode 100644 client/src/middleware/Middleware_Explanation.txt
 delete mode 100644 client/src/pages/admin/users/ResponsiveDialog.tsx
 create mode 100644 client/src/pages/presentationEditor/components/BackgroundImageSelect.test.tsx
 delete mode 100644 client/src/pages/presentationEditor/components/CheckboxComponent.tsx
 create mode 100644 client/src/pages/presentationEditor/components/RndComponent.test.tsx
 create mode 100644 client/src/pages/presentationEditor/components/TextComponentEdit.test.tsx
 create mode 100644 client/src/pages/presentationEditor/components/slideSettingsComponents/Images.test.tsx
 create mode 100644 client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.test.tsx
 create mode 100644 client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.test.tsx
 create mode 100644 client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.test.tsx
 create mode 100644 client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.test.tsx
 create mode 100644 client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.test.tsx
 create mode 100644 client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.test.tsx
 delete mode 100644 client/src/pages/views/components/SocketTest.tsx
 delete mode 100644 client/src/reducers/mediaReducer.ts
 delete mode 100644 client/src/reportWebVitals.ts
 create mode 100644 client/src/utils/checkAuthenticationCompetition.test.ts
 create mode 100644 client/src/utils/renderSlideIcon.test.tsx

diff --git a/.gitlab/client.gitlab-ci.yml b/.gitlab/client.gitlab-ci.yml
index f35e73aa..a64f1bd8 100644
--- a/.gitlab/client.gitlab-ci.yml
+++ b/.gitlab/client.gitlab-ci.yml
@@ -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:
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 8b7286d1..45f88f11 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -1,102 +1,109 @@
 {
-    "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": []
+    }
+  ]
+}
diff --git a/client/package-lock.json b/client/package-lock.json
index 2655ad2e..d857520c 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -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",
diff --git a/client/package.json b/client/package.json
index a0c8b7f5..7426424b 100644
--- a/client/package.json
+++ b/client/package.json
@@ -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"
     ],
diff --git a/client/public/logo192.png b/client/public/logo192.png
deleted file mode 100644
index fc44b0a3796c0e0a64c3d858ca038bd4570465d9..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 5347
zcmZWtbyO6NvR-oO24RV%BvuJ&=?+<7=`LvyB&A_#M7mSDYw1v6DJkiYl9X<guIKOG
zci*|^ymP*p?>jT!%$dLEBTQ8R9|wd3008in6lFF3GV-6mLi?MoP_y~}QUnaDCHI#t
z7w^m$@6DI)|C8_jrT?q=f8D?0AM?L)Z}xAo^e^W>t$*Y0KlT5=@bBjT9k<?nGGBhQ
zSbehEe6l@wQk?yk{Pz@AcMVld0M;GTCE?4p`2*7=c-2|99C89m^UO&?Z>xb%-KNdk
zeOS1tKO#ChhG7%{ApNBzE2ZVNcxbrin#E1TiAw#BlUhXllzhN$qWez5l;h<YdrI9P
zS<6GhD3leYXm+LY=TY4I>+t^q#Eav8PhR2|T}y5kkflaK`ba-eoE+Z2q@o6P$)=&`
z+(8}+-McnNO>e#$Rr{32ngsZIAX>GH??tqgwUuUz6kjns|LjsB37zUEWd|(&O!)DY
zQLrq%Y>)Y8G`yYbYCx&aVHi@-vZ3|ebG!f$sTQqMgi0hWRJ^Wc+Ibv!udh_r%2|U)
zPi|E^PK?UE!>_4`f`1k4hqqj_$+d!EB_#IYt;f9)fBOumGNyglU(ofY`yHq4Y?B%-
zp&G!MRY<~ajTgIHErMe(Z8JG*;D-PJhd@RX@QatggM7+G(Lz8eZ;73)72Hfx5KDOE
zkT(m}i2;@X2AT5fW?qVp?@WgN$aT+f_6eo?IsLh;jscNRp|8H}Z9p_UBO^SJXpZew
zEK8fz|0Th%(Wr|KZBGTM4yxkA5CFdAj8=QSrT$fKW#tweUFqr0TZ9D<AY0)k`aBx_
z>~a5lF{)%-tTGMK^2tz(y2v$i%V8XAxIywrZCp=)83p(zIk6@S5AWl|Oa2hF`~~^W
zI;KeOSkw1O#TiQ8;U7OPXjZM|KrnN}9arP)m0v$c|L)lF`j_rpG(zW1Qjv$=^|p*f
z>)Na{D&>n`jOWMwB^TM}slgTEcjxTlUby89j1)|6ydRfWERn3|7Zd2&e7?!K&5G$x
z`5U3uFtn4~SZq|LjFVrz$3iln-+ucY4q$BC{CSm7Xe5c1J<=%Oagztj{ifpaZk_bQ
z9Sb-LaQMKp-qJA*bP6DzgE3`}*i1o3GKmo2pn@dj0;He}F=BgINo};6gQF8!n0ULZ
zL>kC0nPSFzlcB7p4<H52f8=qMn2=dQ!;xXD`6jdiBJ2^oNyt+16A(f<i;0;6ddGE;
zQ_@XTca6wSK(vK5KIKHUgO;P>1doao2F7%6IUTi_+!L`MM4o*#Y#0v~WiO8<L#fHx
zI?x?k(&T-}!n%}LcF+uCp*>uSeAUNp=vA2KaR&=jNR2iVwG>7t%sG2x_~yXzY)7K&
zk3p+O0AFZ1eu^T3s};B<g5t4vVJN7*?kWOGhv$ru8HW)vzo*&RaaqNEl3s?|)YGKH
zo63kVeX8eiiI8)8TVI<9KtqUE{ofuaw7$nnPUt#2l$=IC;iDij;8{QXU+uLWA9c~M
z?KiTNfE|~IwacG?sFBRbqY&vgc~Yaopzd0{Lg`-WSBW2a@&8=tG<r`Ob?)2siT;lG
zPzbHtt{(VS9*a_>%6TpJ6h-Y%B^*zT&SN7C=N;g|#dGIVMSOru3iv^SvO>h4<o1)Q
ztk-z{yw|{Hc59vTba3&#6I)4@Z!Z{_&vNhxwseBQJk-micCb@PRsZ-yUF*D=BME?9
zv0H77d40W7BL-#9+(qd9=V7!I>M=t-N1GSLLDqVTcgurco6)3&XpU!FP6Hlrmj}f$
zp95;b)>M~`kxuZF3r~a!rMf4|&1=uMG$;h^g=Kl;H&Np-(pFT9FF@++MMEx3R<rS-
zuB^adWYC5}jnG`RBeLHUV`KdbUu)vW8p$<wk-gJklNpkTMH8;qgxUtn=hQw+aXu!!
z7L<V8=#FBERK(Iy;KSCGArNoBxI|R+%WaYJr`}%uyfu_sJ6N4<E%!ST6&8KTNUgT0
zc=|z>BsK?AU0fPk-#mdR)Wdkj)`>ZMl#^<80kM87VvsI3r_c@_vX=fdQ`_9-d(xiI
z4K;1y1TiPj_RPh*SpDI7U~^QQ?%0&!$Sh#?x_@;ag)P}ZkAik{_WPB4rHyW#%>|Gs
zdbhyt=qQPA7`?h2_8T;-E6HI#im9K>au*(j4;kzwMSLgo6u*}-K`$_Gzgu&XE)udQ
zmQ72^eZd|vzI)~!20JV-v-T|<4@7ruqrj|o4=JJPlybwMg;M$Ud7>h6g()CT@wXm`
zbq=A(t;RJ^{Xxi*Ff~!|3!-l_PS{AyNAU~t{h;(N(PXMEf^R<?TfDfq&c>(B+ZVX3
z8y0;0A8hJYp@g+c*`>eTA|3Tgv9U8#BDTO9@a@gVMDxr(fVaEqL1tl?md{v^j8aUv
zm&%PX4^|<cvLF*HzSDMGV0iHPD$KT$lv#8;LIw%pD|^3Sh^Dv=f=y*RKZlzMkH(pA
zj!TBU#${|io0kf9sBt#c(IUh^Nw?i5pPmkQDL8Jo`ihi{POC*hzPF#9gJ%+*%r~)G
z*hzHaRQu;^GSmtSWXj1<&y{<D%B-d(ca1<IOKZoU>rX|?E4^CkplWWNv*OKM>DxPa
z!RJ)U^0-WJMi)Ksc!^ixOtw^egoAZZ2Cg;X7(5xZG7yL_;UJ#yp*ZD-;I^Z9qkP`}
zwCTs0*%rIVF1sgLervtnUo&brwz?6?PXRuOCS*JI-WL6GKy7-~yi0giTEMmDs_-UX
zo=+nFrW_EfTg>oY72_4Z0*uG>MnXP=c0VpT&*|rvv1i<G)%__T#O;}Vf68{=uDg!&
z$^|uGJ##zrX6I7v^ea{ysV}DJ_zrf_yt8+T?W6jw=&>StW;*^={rP<Gps5k_;Ey{*
zO|;e5vGXQ@h1vJKGQ+`NMmYBKV~Sx1US+h>1y?Hv+6R6bxFMkxpWkJ>m7Ba{>zc_q
zEefC3jsXdyS5??Mz7IET$Kft|EMNJIv7Ny8ZOcKnzf`K5Cd)&`-fTY#W&jnV0l2vt
z?Gqhic}l}mCv1yUEy$%DP}4AN;36$=7aNI^*AzV(eYGeJ(Px-j<^gSDp5dBAv2#?;
zcM<nu%TB#lev5kX<apfcKZZ%hDDU3kXtK*%;R839$alV38VWT{NJnhjF0GL`9rM2k
zVexf3KgbIO)>Xv#aj>%;MiG^q^$0MSg-(uTl!xm49dH!{X0){Ew7ThWV~Gtj7h%ZD
zVN-R-^7Cf0VH!8O)uUHPL2mO2tmE*cecwQv_5CzWeh)ykX8r5Hi`ehYo)d{Jnh&3p
z9ndXT$OW51#H5cFKa76c<%nNkP~<gM?)^OX$gL^Ky|we;1(h|2M#l;#h2Tj`PPB<E
z!n=Eb`hcI+66~)eT{SBi;R$mV2KtH}>FU93b5h-|Cb}ScHs@4Q#|}byWg;KDMJ#|l
zE=MKD<?0c>*F@HDBcX@~QJH%56eh~jfPO-uKm}~t7Vk<jf*+P>HxHT;)4sd+?Wc4*
z>CyR*{w@4(gnYRdFq=^(#-ytb^5ESD?x<0Skhb%Pt?npNW1m+Nv`tr9+qN<3H1f<%
zZvNEqyK5F<KUONUP{U|Z&`@-OcU{=Mb%iZGj^d}>gPsQ`QIu9P0x_}wJR~^CotL|n
zk?dn;tLRw9jJTur4uWoX6iMm914f0AJfB@C74a;_qRrAP4E7l890P&{v<}>_&GLrW
z)klculcg`?zJO~4;BBAa=POU%aN|pmZJn2{hA!d!*lwO%YSIzv8bTJ}=nhC^n<w3-
z-v~(ZP6zhLQOa--Vj)F~k0Ob}euB(Y8{v*v$;WjNYg|Cj9;VkDLv+N+V{aW7CW=3<
z$l$KzIhY7gI#*j8`VKQqt@ea1=E#0c5IVICnVAH{bp_LL1iIVw*Itgfi#Sq7_Q<98
zA1cq2BqF{g9$p1@&gq>}g(ld^rn#kq9Z3)z`k9lvV>y#!F4e{5c$tnr9M{V)0m(Z<
z#88vX6-AW7T2UUwW`g<;8I$Jb!R%z@rCcGT)-2k7&x9kZZT66}Ztid~6t0jKb&9mm
zpa}LCb`bz`{MzpZR#E*QuBiZXI#<`5qxx=&LMr-UUf~@dRk}YI2hbMsAMWOmDzYtm
zjof16D=mc`^B$+_bCG$$@R0t;e?~UkF?7<(vkb70*EQB1rfUWXh$j)R2)+dNAH5%R
zEBs^?N;UMdy}V};59Gu#0$q53$}|+q7CIGg_w_WlvE}AdqoS<7DY1LWS9?TrfmcvT
zaypmplwn=P4;a8-%l^e?f`OpGb}%(_mFsL&GywhyN(-VROj`4~V~9bGv%UhcA|YW%
zs{;nh@aDX11y^HOF<O&mcM-|{L00A>XB$a7#Sr3cEtNd4eLm@Y#fc&j)TGvbbMwze
zXtekX_wJqxe4NhuW$r}cNy|L{V=t#$%SuWEW)YZTH|!iT79k#?632OFse{+BT_gau
zJwQcbH{b}dzKO?^dV&3nTILYlGw{27UJ72ZN){BILd_HV_s$WfI2DC<9LIHFmtyw?
zQ;?MuK7g%Ym+4e^W#5}WDLpko%jPOC=aN)3!=8)s#Rnercak&b3ESRX3z{xfKBF8L
z5%CGkFmGO@x?_mPGlpEej!3!AMddChabyf~nJNZxx!D&{@xEb!TDyvqSj%Y5@A{}9
zRzoBn0?x}=krh{ok3Nn%e)#~uh;6jpezhA)ySb^b#E>73e*frBFu6IZ^D7Ii&rsiU
z%jzygxT-n*joJpY4o&8UXr2s%j^Q{?e-<G_^{J76Mq?|eHl2Q}TIfLz1H}I9fvS=c
zm*oIlbD9$tAnOWfM^xYqm2?aavV7kSFN~t(hX*&jXwdT)(-yUc1(^4$bB@D*Rg4fF
zGv*BCBqRz8`^LRBWj98zY@aQ`B||0ovS-9b;m0T<TXj-Hh5;G|U%0o&CSKp)@EmW@
zChzrZU(8@!L%c_f>voloX`4DQyEK+DmrZh8A$)<mmOk^JRtKa)h*12TXYBu6*SOO3
ze#NvXs$UpPLNJLqoTpKTRV%K2qK9}L;hCtucS=cqUWJH}3K=Em3K@4&JHx{iSFa8E
zqVHD4$k0g3oTIYd{?wVF<(2=uTWaH@w6)NT<>iWL#NO9+Y@!sO2f@rI!@jN@>HOA<
z?q2l{^%mY*PNx2FoX+A7X3N}(RV$B`g&N=e0uvAvEN1W^{*W?zT1i#fxuw10%~))J
zjx#gxoVlXREWZf4hRkgdHx5V_S*;p-y%JtGgQ4}lnA~MBz-AFdxUxU1RIT$`sal|X
zPB6sEVRjGbXIP0U+?rT|y5+ev&OMX*5C$n2SBPZr`jqzrmpVrNciR0e*Wm?fK6DY&
zl(XQZ60yWXV-|Ps!A<n+?vbcQJG{k7=<p3~`+h4Kd_>{EF;=_z(YAF=T(-MkJXUoX
zI{UMQDAV2}Ya?EisdEW;@pE6dt;j0fg5oT2dxCi{wqWJ<)|SR6fxX~5CzblPGr8cb
zUBVJ2CQd~3L?7yfTpLNbt)He1D>*KXI^GK%<`bq^cUq$Q@uJifG>p3LU(!H=C)aEL
zenk7pVg}0{dKU}&l)Y2Y2eFMdS(<j~2+yHkUVn{?C5dsJXag$OUKP&Vl2lSAJL_uI
ztevY_DRGdi^2bgn=Ll@Km6Uk>JS0}oZUuVaf2+K*YFNGHB`^YGcIpnBlMhO7d4@vV
zv(@N}(k#REdul8~fP+^F@ky*wt@~&|(&&meNO>rKDEnB{ykAZ}k>e@lad7to>Ao$B
zz<1(L=#J*u4_LB=8w+*{KFK^u00NAmeNN7pr+Pf+N*Zl^dO{LM-hMHyP6N!~`24jd
zXYP|Ze;dRXKdF2iJG$U{k=S86l@pytLx}$JFFs8e)*Vi?aVBtGJ3JZUj!~c{<R$n(
ziv;4$OAR*24{KJ-u{Mz2C%|m?Lu8%akP2m-8t9?^hJ};KWux0$T6Zc6vmNj_(P^97
znxN8^Fl+G8f)9)fW?Qt`NcWoFLaagnygy3@TZ@Gu-ER?^vZ;^CT6NUUf@sIN!o*#I
zTQDxUq9IS<Y5j7ng8Y<xvPo+D=~nKpr2LflB|zg+Vlqg|&Z#IWz8CdW!h`-uDggJR
z+f9qRnZ^{3x$+Kifl~IZh)$X4>(rw5>vuRF$`^p!P8w1B=O!skwkO5yd4_XuG^QVF
z`-r5K7(IPSiKQ2|U9+`@Js!<HL1C{aO{H=}S{3p}_Edej>g6sfJwAHVd|s?|mnC*q
zp|B|z)(8+mxXyxQ{8Pg3F4|tdpgZZSoU4P&9I8)nHo1@)9_9u&NcT^FI)6|hsAZFk
zZ+arl&@*>RXBf-OZxhZerOr&dN5LW9@gV=oGFbK*J+m#R-|e6(Loz(;g@T^*oO)0R
zN`N=X46b{7yk5FZGr#5&n1!-@j@g02g|X>MOpF3#IjZ_4wg{dX+G9eqS+Es9@6nC7
zD9$NuVJI}6ZlwtUm5cCAiYv0(Yi{%eH+}t)!E^>^KxB5^L~a`4%1~5q6h>d;paC9c
zTj0wTCKrhWf+F#5>EgX<cLYfrtsHC5;@&1Tu=KIwHE|R;*1f&W24i_&2yx+Xe5N7V
z`hmH?m*G_>`sl%POl?oyCq0(w0xoL?L%)|Q7d|Hl92rUYAU#lc**I&^6p=4lNQPa0
znQ|A~i0ip@`B=FW-Q;zh?-wF;Wl5!+q3GXDu-x&}$gUO)NoO7^$BeEIrd~1Dh{Tr`
z8s<(Bn@gZ(mkIGnmYh_ehXnq78QL$pNDi)|QcT*|GtS%nz1uKE+E{7jdEBp%h0}%r
zD2|KmYGiPa4;md-t_m5YDz#c*oV_FqXd85d@eub?9N61QuYcb3CnVWpM(D-^|CmkL
z(F}L&N7qhL2PCq)fRh}XO@U`Yn<<Z#)X^Ij=#WjXr&snbL8Hbkya6{c!+Ay;w1Jlr
z9}X^@zhtUU>?TNGR4L(mF7#4u29{i~@k;pLsgl({YW5`Mo+p=zZn3L*4{JU;++dG9
X@eDJUQo;Ye2mwlRs<JiGX2Jghdw)}T

diff --git a/client/public/logo512.png b/client/public/logo512.png
deleted file mode 100644
index a4e47a6545bc15971f8f63fba70e4013df88a664..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001

literal 9664
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
b{|{}U^kRn2wo}j7|0ATu<;8xA7zX}7|B6mN

diff --git a/client/public/manifest.json b/client/public/manifest.json
index 080d6c77..73371289 100644
--- a/client/public/manifest.json
+++ b/client/public/manifest.json
@@ -1,21 +1,11 @@
 {
-  "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": ".",
diff --git a/client/src/actions/competitionLogin.test.ts b/client/src/actions/competitionLogin.test.ts
new file mode 100644
index 00000000..bf1825e7
--- /dev/null
+++ b/client/src/actions/competitionLogin.test.ts
@@ -0,0 +1,76 @@
+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()
+})
diff --git a/client/src/actions/presentation.test.ts b/client/src/actions/presentation.test.ts
index d95d9db7..095a3529 100644
--- a/client/src/actions/presentation.test.ts
+++ b/client/src/actions/presentation.test.ts
@@ -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)
diff --git a/client/src/actions/presentation.ts b/client/src/actions/presentation.ts
index 90b728c5..32e4d0a4 100644
--- a/client/src/actions/presentation.ts
+++ b/client/src/actions/presentation.ts
@@ -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)
       }
     })
diff --git a/client/src/components/TestConnection.tsx b/client/src/components/TestConnection.tsx
deleted file mode 100644
index 7fa4aaea..00000000
--- a/client/src/components/TestConnection.tsx
+++ /dev/null
@@ -1,18 +0,0 @@
-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
diff --git a/client/src/e2e/AdminPage.test.tsx b/client/src/e2e/AdminPage.test.tsx
new file mode 100644
index 00000000..c9c67580
--- /dev/null
+++ b/client/src/e2e/AdminPage.test.tsx
@@ -0,0 +1,176 @@
+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)
+})
diff --git a/client/src/e2e/LoginPage.test.tsx b/client/src/e2e/LoginPage.test.tsx
new file mode 100644
index 00000000..6a60ff6b
--- /dev/null
+++ b/client/src/e2e/LoginPage.test.tsx
@@ -0,0 +1,70 @@
+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)
+})
diff --git a/client/src/e2e/TestingConstants.ts b/client/src/e2e/TestingConstants.ts
new file mode 100644
index 00000000..3122aa21
--- /dev/null
+++ b/client/src/e2e/TestingConstants.ts
@@ -0,0 +1,6 @@
+/** 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/'
diff --git a/client/src/index.tsx b/client/src/index.tsx
index fdd9cf8e..cb658ebf 100644
--- a/client/src/index.tsx
+++ b/client/src/index.tsx
@@ -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()
diff --git a/client/src/logo.svg b/client/src/logo.svg
deleted file mode 100644
index 9dfc1c05..00000000
--- a/client/src/logo.svg
+++ /dev/null
@@ -1 +0,0 @@
-<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
diff --git a/client/src/middleware/Middleware_Explanation.txt b/client/src/middleware/Middleware_Explanation.txt
deleted file mode 100644
index dc4e91ab..00000000
--- a/client/src/middleware/Middleware_Explanation.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-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
diff --git a/client/src/pages/admin/AdminPage.tsx b/client/src/pages/admin/AdminPage.tsx
index 1b543083..3a375210 100644
--- a/client/src/pages/admin/AdminPage.tsx
+++ b/client/src/pages/admin/AdminPage.tsx
@@ -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}
diff --git a/client/src/pages/admin/competitions/AddCompetition.tsx b/client/src/pages/admin/competitions/AddCompetition.tsx
index 6053f2f8..e404b112 100644
--- a/client/src/pages/admin/competitions/AddCompetition.tsx
+++ b/client/src/pages/admin/competitions/AddCompetition.tsx
@@ -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"
diff --git a/client/src/pages/admin/competitions/CompetitionManager.tsx b/client/src/pages/admin/competitions/CompetitionManager.tsx
index 7de930dc..9b72ae04 100644
--- a/client/src/pages/admin/competitions/CompetitionManager.tsx
+++ b/client/src/pages/admin/competitions/CompetitionManager.tsx
@@ -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}
diff --git a/client/src/pages/admin/dashboard/components/CurrentUser.tsx b/client/src/pages/admin/dashboard/components/CurrentUser.tsx
index 1d6afe73..933a320b 100644
--- a/client/src/pages/admin/dashboard/components/CurrentUser.tsx
+++ b/client/src/pages/admin/dashboard/components/CurrentUser.tsx
@@ -15,7 +15,9 @@ const CurrentUser: React.FC = () => {
           </Typography>
         </div>
         <div>
-          <Typography variant="h6">Email: {currentUser && currentUser.email}</Typography>
+          <Typography data-testid="userEmail" variant="h6">
+            Email: {currentUser && currentUser.email}
+          </Typography>
         </div>
         <div>
           <Typography variant="h6">Region: {currentUser && currentUser.city && currentUser.city.name}</Typography>
diff --git a/client/src/pages/admin/regions/AddRegion.tsx b/client/src/pages/admin/regions/AddRegion.tsx
index 8bd5e562..10cb22bc 100644
--- a/client/src/pages/admin/regions/AddRegion.tsx
+++ b/client/src/pages/admin/regions/AddRegion.tsx
@@ -79,6 +79,7 @@ const AddRegion: React.FC = (props: any) => {
           <FormControl className={classes.margin}>
             <Grid container={true}>
               <TextField
+                data-testid="regionTextField"
                 className={classes.margin}
                 helperText={formik.touched.model?.name ? formik.errors.model?.name : ''}
                 error={Boolean(formik.touched.model?.name && formik.errors.model?.name)}
@@ -86,8 +87,9 @@ const AddRegion: React.FC = (props: any) => {
                 onBlur={formik.handleBlur}
                 name="model.name"
                 label="Region"
-              ></TextField>
+              />
               <AddButton
+                data-testid="regionSubmitButton"
                 style={{ backgroundColor: '#4caf50', color: '#fcfcfc' }}
                 className={classes.button}
                 color="default"
diff --git a/client/src/pages/admin/regions/Regions.tsx b/client/src/pages/admin/regions/Regions.tsx
index 28a25cad..5e2531bb 100644
--- a/client/src/pages/admin/regions/Regions.tsx
+++ b/client/src/pages/admin/regions/Regions.tsx
@@ -99,7 +99,7 @@ const RegionManager: React.FC = (props: any) => {
                 <TableRow key={row.name}>
                   <TableCell scope="row">{row.name}</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>
@@ -110,7 +110,9 @@ const RegionManager: React.FC = (props: any) => {
         {(!cities || cities.length === 0) && <Typography>Inga regioner hittades</Typography>}
       </TableContainer>
       <Menu id="simple-menu" anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
-        <RemoveMenuItem onClick={handleDeleteCity}>Ta bort</RemoveMenuItem>
+        <RemoveMenuItem onClick={handleDeleteCity} data-testid="removeRegionButton">
+          Ta bort
+        </RemoveMenuItem>
       </Menu>
     </div>
   )
diff --git a/client/src/pages/admin/users/AddUser.tsx b/client/src/pages/admin/users/AddUser.tsx
index d24bf130..63549edc 100644
--- a/client/src/pages/admin/users/AddUser.tsx
+++ b/client/src/pages/admin/users/AddUser.tsx
@@ -87,6 +87,7 @@ const AddUser: React.FC = (props: any) => {
   return (
     <div>
       <AddButton
+        data-testid="addUserButton"
         style={{ backgroundColor: '#4caf50', color: '#fcfcfc' }}
         color="default"
         variant="contained"
@@ -114,6 +115,7 @@ const AddUser: React.FC = (props: any) => {
             {(formik) => (
               <AddForm onSubmit={formik.handleSubmit}>
                 <TextField
+                  data-testid="addUserEmail"
                   label="Email"
                   name="model.email"
                   helperText={formik.touched.model?.email ? formik.errors.model?.email : ''}
@@ -123,6 +125,7 @@ const AddUser: React.FC = (props: any) => {
                   margin="normal"
                 />
                 <TextField
+                  data-testid="addUserPassword"
                   label="Lösenord"
                   name="model.password"
                   helperText={formik.touched.model?.password ? formik.errors.model?.password : ''}
@@ -132,6 +135,7 @@ const AddUser: React.FC = (props: any) => {
                   margin="normal"
                 />
                 <TextField
+                  data-testid="addUserName"
                   label="Namn"
                   name="model.name"
                   helperText={formik.touched.model?.name ? formik.errors.model?.name : ''}
@@ -146,6 +150,7 @@ const AddUser: React.FC = (props: any) => {
                   </InputLabel>
                   <TextField
                     select
+                    data-testid="userCitySelect"
                     name="model.city"
                     id="standard-select-currency"
                     value={selectedCity ? selectedCity.name : noCitySelected}
@@ -160,7 +165,12 @@ const AddUser: 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>
                       ))}
@@ -173,6 +183,7 @@ const AddUser: React.FC = (props: any) => {
                   </InputLabel>
                   <TextField
                     select
+                    data-testid="userRoleSelect"
                     name="model.role"
                     id="standard-select-currency"
                     value={selectedRole ? selectedRole.name : noRoleSelected}
@@ -187,7 +198,12 @@ const AddUser: React.FC = (props: any) => {
                     </MenuItem>
                     {roles &&
                       roles.map((role) => (
-                        <MenuItem key={role.name} value={role.name} onClick={() => setSelectedRole(role)}>
+                        <MenuItem
+                          key={role.name}
+                          value={role.name}
+                          onClick={() => setSelectedRole(role)}
+                          data-testid={role.name}
+                        >
                           {role.name}
                         </MenuItem>
                       ))}
@@ -196,6 +212,7 @@ const AddUser: React.FC = (props: any) => {
 
                 <Button
                   type="submit"
+                  data-testid="addUserSubmit"
                   fullWidth
                   variant="contained"
                   color="secondary"
diff --git a/client/src/pages/admin/users/EditUser.tsx b/client/src/pages/admin/users/EditUser.tsx
index 9b4a5d1b..5de773fe 100644
--- a/client/src/pages/admin/users/EditUser.tsx
+++ b/client/src/pages/admin/users/EditUser.tsx
@@ -142,7 +142,7 @@ const EditUser = ({ user }: UserIdProps) => {
     }
     await axios
       .put('/api/users/' + user.id, req)
-      .then((res) => {
+      .then(() => {
         setAnchorEl(null)
         dispatch(getSearchUsers())
       })
@@ -167,7 +167,7 @@ const EditUser = ({ user }: UserIdProps) => {
   }
   return (
     <div>
-      <Button onClick={handleClick}>
+      <Button onClick={handleClick} data-testid={`more-${user.email}`}>
         <MoreHorizIcon />
       </Button>
       <Popover
@@ -289,6 +289,7 @@ const EditUser = ({ user }: UserIdProps) => {
                   Ändra
                 </Button>
                 <Button
+                  data-testid="removeUser"
                   onClick={handleVerifyDelete}
                   className={classes.deleteButton}
                   fullWidth
@@ -313,7 +314,7 @@ const EditUser = ({ user }: UserIdProps) => {
                     <Button autoFocus onClick={handleClose} color="primary">
                       Avbryt
                     </Button>
-                    <Button onClick={handleDeleteUsers} color="primary" autoFocus>
+                    <Button data-testid="acceptRemoveUser" onClick={handleDeleteUsers} color="primary" autoFocus>
                       Ta bort
                     </Button>
                   </DialogActions>
diff --git a/client/src/pages/admin/users/ResponsiveDialog.tsx b/client/src/pages/admin/users/ResponsiveDialog.tsx
deleted file mode 100644
index e29a6786..00000000
--- a/client/src/pages/admin/users/ResponsiveDialog.tsx
+++ /dev/null
@@ -1,53 +0,0 @@
-import React from 'react';
-import Button from '@material-ui/core/Button';
-import Dialog from '@material-ui/core/Dialog';
-import DialogActions from '@material-ui/core/DialogActions';
-import DialogContent from '@material-ui/core/DialogContent';
-import DialogContentText from '@material-ui/core/DialogContentText';
-import DialogTitle from '@material-ui/core/DialogTitle';
-import useMediaQuery from '@material-ui/core/useMediaQuery';
-import { useTheme } from '@material-ui/core/styles';
-
-export default function ResponsiveDialog() {
-  const [open, setOpen] = React.useState(false);
-  const theme = useTheme();
-  const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
-
-  const handleClickOpen = () => {
-    setOpen(true);
-  };
-
-  const handleClose = () => {
-    setOpen(false);
-  };
-
-  return (
-    <div>
-      <Button variant="outlined" color="primary" onClick={handleClickOpen}>
-        Open responsive dialog
-      </Button>
-      <Dialog
-        fullScreen={fullScreen}
-        open={open}
-        onClose={handleClose}
-        aria-labelledby="responsive-dialog-title"
-      >
-        <DialogTitle id="responsive-dialog-title">{"Use Google's location service?"}</DialogTitle>
-        <DialogContent>
-          <DialogContentText>
-            Let Google help apps determine location. This means sending anonymous location data to
-            Google, even when no apps are running.
-          </DialogContentText>
-        </DialogContent>
-        <DialogActions>
-          <Button autoFocus onClick={handleClose} color="primary">
-            Disagree
-          </Button>
-          <Button onClick={handleClose} color="primary" autoFocus>
-            Agree
-          </Button>
-        </DialogActions>
-      </Dialog>
-    </div>
-  );
-}
\ No newline at end of file
diff --git a/client/src/pages/login/components/AdminLogin.tsx b/client/src/pages/login/components/AdminLogin.tsx
index 4fe47524..c33f338d 100644
--- a/client/src/pages/login/components/AdminLogin.tsx
+++ b/client/src/pages/login/components/AdminLogin.tsx
@@ -57,6 +57,7 @@ const AdminLogin: React.FC = () => {
           <TextField
             label="Email Adress"
             name="model.email"
+            data-testid="email"
             helperText={formik.touched.model?.email ? formik.errors.model?.email : ''}
             error={Boolean(formik.touched.model?.email && formik.errors.model?.email)}
             onChange={formik.handleChange}
@@ -67,6 +68,7 @@ const AdminLogin: React.FC = () => {
             label="Lösenord"
             name="model.password"
             type="password"
+            data-testid="password"
             helperText={formik.touched.model?.password ? formik.errors.model?.password : ''}
             error={Boolean(formik.touched.model?.password && formik.errors.model?.password)}
             onChange={formik.handleChange}
@@ -75,6 +77,7 @@ const AdminLogin: React.FC = () => {
           />
           <Button
             type="submit"
+            data-testid="submit"
             fullWidth
             variant="contained"
             color="secondary"
diff --git a/client/src/pages/presentationEditor/components/BackgroundImageSelect.test.tsx b/client/src/pages/presentationEditor/components/BackgroundImageSelect.test.tsx
new file mode 100644
index 00000000..17705308
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/BackgroundImageSelect.test.tsx
@@ -0,0 +1,16 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { BrowserRouter } from 'react-router-dom'
+import store from '../../../store'
+import BackgroundImageSelect from './BackgroundImageSelect'
+
+it('renders background image select', () => {
+  render(
+    <BrowserRouter>
+      <Provider store={store}>
+        <BackgroundImageSelect variant="competition" />
+      </Provider>
+    </BrowserRouter>
+  )
+})
diff --git a/client/src/pages/presentationEditor/components/CheckboxComponent.tsx b/client/src/pages/presentationEditor/components/CheckboxComponent.tsx
deleted file mode 100644
index cb264712..00000000
--- a/client/src/pages/presentationEditor/components/CheckboxComponent.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Checkbox } from '@material-ui/core'
-import React, { useState } from 'react'
-import { Rnd } from 'react-rnd'
-import { Component } from '../../../interfaces/ApiModels'
-import { Position } from '../../../interfaces/Components'
-
-type CheckboxComponentProps = {
-  component: Component
-}
-
-const CheckboxComponent = ({ component }: CheckboxComponentProps) => {
-  const [currentPos, setCurrentPos] = useState<Position>({ x: component.x, y: component.y })
-  return (
-    <Rnd
-      bounds="parent"
-      onDragStop={(e, d) => {
-        setCurrentPos({ x: d.x, y: d.y })
-      }}
-      position={{ x: currentPos.x, y: currentPos.y }}
-    >
-      <Checkbox
-        disableRipple
-        style={{
-          transform: 'scale(3)',
-        }}
-      />
-    </Rnd>
-  )
-}
-
-export default CheckboxComponent
diff --git a/client/src/pages/presentationEditor/components/RndComponent.test.tsx b/client/src/pages/presentationEditor/components/RndComponent.test.tsx
new file mode 100644
index 00000000..b024ab3f
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/RndComponent.test.tsx
@@ -0,0 +1,17 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { BrowserRouter } from 'react-router-dom'
+import { Component } from '../../../interfaces/ApiModels'
+import store from '../../../store'
+import RndComponent from './RndComponent'
+
+it('renders rnd component', () => {
+  render(
+    <BrowserRouter>
+      <Provider store={store}>
+        <RndComponent component={{ id: 2, x: 0, w: 15, h: 15 } as Component} width={50} height={50} scale={123} />
+      </Provider>
+    </BrowserRouter>
+  )
+})
diff --git a/client/src/pages/presentationEditor/components/TextComponentEdit.test.tsx b/client/src/pages/presentationEditor/components/TextComponentEdit.test.tsx
new file mode 100644
index 00000000..655b21bf
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/TextComponentEdit.test.tsx
@@ -0,0 +1,17 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { BrowserRouter } from 'react-router-dom'
+import { TextComponent } from '../../../interfaces/ApiModels'
+import store from '../../../store'
+import TextComponentEdit from './TextComponentEdit'
+
+it('renders text component edit', () => {
+  render(
+    <BrowserRouter>
+      <Provider store={store}>
+        <TextComponentEdit component={{ id: 2, text: 'testtext' } as TextComponent} />
+      </Provider>
+    </BrowserRouter>
+  )
+})
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.test.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.test.tsx
new file mode 100644
index 00000000..917fa0c8
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.test.tsx
@@ -0,0 +1,14 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import store from '../../../../store'
+import Images from './Images'
+
+it('renders images', () => {
+  render(
+    <Provider store={store}>
+      <Images activeSlide={{ id: 5 } as RichSlide} activeViewTypeId={5} competitionId="1" />
+    </Provider>
+  )
+})
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx
index 49f41e7f..60c68fb2 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Images.tsx
@@ -1,14 +1,14 @@
 /* This file handles creating and removing image components, and uploading and removing image files from the server.
  */
-import { ListItem, ListItemText, Typography } from '@material-ui/core'
+import { ListItem, ListItemText } from '@material-ui/core'
 import CloseIcon from '@material-ui/icons/Close'
-import React from 'react'
-import { Center, HiddenInput, SettingsList, AddImageButton, ImportedImage, AddButton } from '../styled'
 import axios from 'axios'
+import React from 'react'
 import { getEditorCompetition } from '../../../../actions/editor'
-import { RichSlide } from '../../../../interfaces/ApiRichModels'
-import { ImageComponent, Media } from '../../../../interfaces/ApiModels'
 import { useAppDispatch, useAppSelector } from '../../../../hooks'
+import { ImageComponent, Media } from '../../../../interfaces/ApiModels'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import { AddButton, AddImageButton, Center, HiddenInput, ImportedImage, SettingsList } from '../styled'
 
 type ImagesProps = {
   activeViewTypeId: number
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.test.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.test.tsx
new file mode 100644
index 00000000..fe745c83
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.test.tsx
@@ -0,0 +1,14 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import store from '../../../../store'
+import Instructions from './Instructions'
+
+it('renders instructions', () => {
+  render(
+    <Provider store={store}>
+      <Instructions activeSlide={{ id: 5 } as RichSlide} competitionId="1" />
+    </Provider>
+  )
+})
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx
index 99b4b1d8..de34bc20 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Instructions.tsx
@@ -23,7 +23,7 @@ const Instructions = ({ activeSlide, competitionId }: InstructionsProps) => {
     //Only updates 250ms after last input was made to not spam
     setTimerHandle(
       window.setTimeout(async () => {
-        if (activeSlide && activeSlide.questions[0]) {
+        if (activeSlide && activeSlide.questions?.[0]) {
           await axios
             // TODO: Implement instructions field in question and add put API
             .put(
@@ -56,7 +56,7 @@ const Instructions = ({ activeSlide, competitionId }: InstructionsProps) => {
           <TextField
             multiline
             id="outlined-basic"
-            defaultValue={activeSlide.questions[0].correcting_instructions}
+            defaultValue={activeSlide.questions?.[0].correcting_instructions}
             onChange={updateInstructionsText}
             variant="outlined"
             fullWidth={true}
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.test.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.test.tsx
new file mode 100644
index 00000000..75131257
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.test.tsx
@@ -0,0 +1,14 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import store from '../../../../store'
+import MultipleChoiceAlternatives from './MultipleChoiceAlternatives'
+
+it('renders multiple choice alternatives', () => {
+  render(
+    <Provider store={store}>
+      <MultipleChoiceAlternatives activeSlide={{ id: 5 } as RichSlide} competitionId="1" />
+    </Provider>
+  )
+})
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx
index 58053995..cf803d3a 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/MultipleChoiceAlternatives.tsx
@@ -34,7 +34,7 @@ const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoi
   }
 
   const updateAlternativeValue = async (alternative: QuestionAlternative) => {
-    if (activeSlide && activeSlide.questions[0]) {
+    if (activeSlide && activeSlide.questions?.[0]) {
       let newValue: number
       if (alternative.value === 0) {
         newValue = 1
@@ -52,7 +52,7 @@ const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoi
   }
 
   const updateAlternativeText = async (alternative_id: number, newText: string) => {
-    if (activeSlide && activeSlide.questions[0]) {
+    if (activeSlide && activeSlide.questions?.[0]) {
       await axios
         .put(
           `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`,
@@ -66,7 +66,7 @@ const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoi
   }
 
   const addAlternative = async () => {
-    if (activeSlide && activeSlide.questions[0]) {
+    if (activeSlide && activeSlide.questions?.[0]) {
       await axios
         .post(
           `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives`,
@@ -80,7 +80,7 @@ const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoi
   }
 
   const handleCloseAnswerClick = async (alternative_id: number) => {
-    if (activeSlide && activeSlide.questions[0]) {
+    if (activeSlide && activeSlide.questions?.[0]) {
       await axios
         .delete(
           `/api/competitions/${competitionId}/slides/${activeSlideId}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`
@@ -102,25 +102,22 @@ const MultipleChoiceAlternatives = ({ activeSlide, competitionId }: MultipleChoi
           />
         </Center>
       </ListItem>
-      {activeSlide &&
-        activeSlide.questions[0] &&
-        activeSlide.questions[0].alternatives &&
-        activeSlide.questions[0].alternatives.map((alt) => (
-          <div key={alt.id}>
-            <ListItem divider>
-              <AlternativeTextField
-                id="outlined-basic"
-                defaultValue={alt.text}
-                onChange={(event) => updateAlternativeText(alt.id, event.target.value)}
-                variant="outlined"
-              />
-              <GreenCheckbox checked={numberToBool(alt.value)} onChange={() => updateAlternativeValue(alt)} />
-              <Clickable>
-                <CloseIcon onClick={() => handleCloseAnswerClick(alt.id)} />
-              </Clickable>
-            </ListItem>
-          </div>
-        ))}
+      {activeSlide?.questions?.[0]?.alternatives?.map((alt) => (
+        <div key={alt.id}>
+          <ListItem divider>
+            <AlternativeTextField
+              id="outlined-basic"
+              defaultValue={alt.text}
+              onChange={(event) => updateAlternativeText(alt.id, event.target.value)}
+              variant="outlined"
+            />
+            <GreenCheckbox checked={numberToBool(alt.value)} onChange={() => updateAlternativeValue(alt)} />
+            <Clickable>
+              <CloseIcon onClick={() => handleCloseAnswerClick(alt.id)} />
+            </Clickable>
+          </ListItem>
+        </div>
+      ))}
       <ListItem button onClick={addAlternative}>
         <Center>
           <AddButton variant="button">Lägg till svarsalternativ</AddButton>
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.test.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.test.tsx
new file mode 100644
index 00000000..c9f13bcd
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.test.tsx
@@ -0,0 +1,14 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import store from '../../../../store'
+import QuestionSettings from './QuestionSettings'
+
+it('renders question settings', () => {
+  render(
+    <Provider store={store}>
+      <QuestionSettings activeSlide={{ id: 5 } as RichSlide} competitionId="1" />
+    </Provider>
+  )
+})
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx
index e917afbd..f714fe23 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/QuestionSettings.tsx
@@ -18,7 +18,7 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps)
     updateTitle: boolean,
     event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
   ) => {
-    if (activeSlide && activeSlide.questions[0]) {
+    if (activeSlide && activeSlide.questions?.[0]) {
       if (updateTitle) {
         await axios
           .put(`/api/competitions/${competitionId}/slides/${activeSlide.id}/questions/${activeSlide.questions[0].id}`, {
@@ -44,7 +44,7 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps)
 
   const [score, setScore] = useState<number | undefined>(0)
   useEffect(() => {
-    setScore(activeSlide?.questions[0]?.total_score)
+    setScore(activeSlide?.questions?.[0]?.total_score)
   }, [activeSlide])
 
   return (
@@ -74,7 +74,7 @@ const QuestionSettings = ({ activeSlide, competitionId }: QuestionSettingsProps)
             label="Poäng"
             type="number"
             InputProps={{ inputProps: { min: 0 } }}
-            value={score}
+            value={score || 0}
             onChange={(event) => updateQuestion(false, event)}
           />
         </Center>
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.test.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.test.tsx
new file mode 100644
index 00000000..b0b44c64
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.test.tsx
@@ -0,0 +1,14 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import store from '../../../../store'
+import SlideType from './SlideType'
+
+it('renders slidetype', () => {
+  render(
+    <Provider store={store}>
+      <SlideType activeSlide={{ id: 5 } as RichSlide} competitionId="1" />
+    </Provider>
+  )
+})
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx
index bef33d94..8fbae438 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx
@@ -46,8 +46,8 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
   const updateSlideType = async () => {
     closeSlideTypeDialog()
     if (activeSlide) {
-      deleteQuestionComponent(questionComponentId)
-      if (activeSlide.questions[0] && activeSlide.questions[0].type_id !== selectedSlideType) {
+      if (activeSlide.questions?.[0] && activeSlide.questions[0].type_id !== selectedSlideType) {
+        deleteQuestionComponent(questionComponentId)
         if (selectedSlideType === 0) {
           // Change slide type from a question type to information
           await axios
@@ -124,7 +124,7 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
       <ListItem>
         <FormControl fullWidth variant="outlined">
           <InputLabel>Sidtyp</InputLabel>
-          <Select fullWidth={true} value={activeSlide?.questions[0]?.type_id || 0} label="Sidtyp">
+          <Select fullWidth={true} value={activeSlide?.questions?.[0]?.type_id || 0} label="Sidtyp">
             <MenuItem value={0}>
               <Typography variant="button" onClick={() => openSlideTypeDialog(0)}>
                 Informationssida
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.test.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.test.tsx
new file mode 100644
index 00000000..edd4a89f
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Texts.test.tsx
@@ -0,0 +1,14 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import store from '../../../../store'
+import Texts from './Texts'
+
+it('renders texts', () => {
+  render(
+    <Provider store={store}>
+      <Texts activeSlide={{ id: 5 } as RichSlide} activeViewTypeId={5} competitionId="1" />
+    </Provider>
+  )
+})
diff --git a/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.test.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.test.tsx
new file mode 100644
index 00000000..5ef0d2ed
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/Timer.test.tsx
@@ -0,0 +1,14 @@
+import { render } from '@testing-library/react'
+import React from 'react'
+import { Provider } from 'react-redux'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import store from '../../../../store'
+import Timer from './Timer'
+
+it('renders timer', () => {
+  render(
+    <Provider store={store}>
+      <Timer activeSlide={{ id: 5 } as RichSlide} competitionId="1" />
+    </Provider>
+  )
+})
diff --git a/client/src/pages/views/components/SocketTest.tsx b/client/src/pages/views/components/SocketTest.tsx
deleted file mode 100644
index 01a0a6f2..00000000
--- a/client/src/pages/views/components/SocketTest.tsx
+++ /dev/null
@@ -1,61 +0,0 @@
-import React, { useEffect } from 'react'
-import { connect } from 'react-redux'
-import { useAppDispatch } from '../../../hooks'
-import {
-  socketConnect,
-  socketEndPresentation,
-  socketJoinPresentation,
-  socketSetSlideNext,
-  socketSetSlidePrev,
-  socketStartPresentation,
-  socketStartTimer,
-} from '../../../sockets'
-
-const mapStateToProps = (state: any) => {
-  return {
-    slide_order: state.presentation.slide.order,
-  }
-}
-
-const mapDispatchToProps = (dispatch: any) => {
-  return {
-    // tickTimer: () => dispatch(tickTimer(1)),
-  }
-}
-
-const SocketTest: React.FC = (props: any) => {
-  const dispatch = useAppDispatch()
-
-  useEffect(() => {
-    socketConnect()
-    // dispatch(getPresentationCompetition('1')) // TODO: Use ID of item_code gotten from auth/login/<code> api call
-    // dispatch(getPresentationTeams('1')) // TODO: Use ID of item_code gotten from auth/login/<code> api call
-  }, [])
-
-  return (
-    <>
-      <button onClick={socketStartPresentation}>Start presentation</button>
-      <button onClick={socketJoinPresentation}>Join presentation</button>
-      <button onClick={socketEndPresentation}>End presentation</button>
-      <button onClick={socketSetSlidePrev}>Prev slide</button>
-      <button onClick={socketSetSlideNext}>Next slide</button>
-      <button onClick={socketStartTimer}>Start timer</button>
-      <div>Current slide: {props.slide_order}</div>
-      {/* <div>Timer: {props.timer.value}</div>
-      <div>Enabled: {props.timer.enabled.toString()}</div>
-      <button onClick={syncTimer}>Sync</button>
-      <button onClick={() => dispatch(setTimer(5))}>5 Sec</button>
-      <button
-        onClick={() => {
-          dispatch(setTimer(5))
-          dispatch(setTimerEnabled(true))
-          syncTimer()
-        }}
-      >
-        Sync and 5 sec
-      </button> */}
-    </>
-  )
-}
-
-export default connect(mapStateToProps, mapDispatchToProps)(SocketTest)
diff --git a/client/src/reducers/allReducers.ts b/client/src/reducers/allReducers.ts
index 90cb2414..038b172e 100644
--- a/client/src/reducers/allReducers.ts
+++ b/client/src/reducers/allReducers.ts
@@ -5,7 +5,6 @@ import citiesReducer from './citiesReducer'
 import competitionLoginReducer from './competitionLoginReducer'
 import competitionsReducer from './competitionsReducer'
 import editorReducer from './editorReducer'
-import mediaReducer from './mediaReducer'
 import presentationReducer from './presentationReducer'
 import rolesReducer from './rolesReducer'
 import searchUserReducer from './searchUserReducer'
@@ -25,7 +24,6 @@ const allReducers = combineReducers({
   roles: rolesReducer,
   searchUsers: searchUserReducer,
   types: typesReducer,
-  media: mediaReducer,
   statistics: statisticsReducer,
   competitionLogin: competitionLoginReducer,
 })
diff --git a/client/src/reducers/mediaReducer.ts b/client/src/reducers/mediaReducer.ts
deleted file mode 100644
index 1d6f4ce5..00000000
--- a/client/src/reducers/mediaReducer.ts
+++ /dev/null
@@ -1,41 +0,0 @@
-import { AnyAction } from 'redux'
-import Types from '../actions/types'
-
-interface MediaState {
-  id: number
-  filename: string
-  mediatype_id: number
-  user_id: number
-}
-
-// Define the initial values for the media state
-const initialState: MediaState = {
-  id: 0,
-  filename: '',
-  mediatype_id: 1,
-  user_id: 0,
-}
-
-export default function (state = initialState, action: AnyAction) {
-  switch (action.type) {
-    case Types.SET_MEDIA_ID:
-      return { ...state, id: action.payload as number }
-    case Types.SET_MEDIA_FILENAME:
-      return {
-        ...state,
-        filename: action.payload as string,
-      }
-    case Types.SET_MEDIA_TYPE_ID:
-      return {
-        ...state,
-        mediatype_id: action.payload as number,
-      }
-    case Types.SET_MEDIA_USER_ID:
-      return {
-        ...state,
-        user_id: action.payload as number,
-      }
-    default:
-      return state
-  }
-}
diff --git a/client/src/reportWebVitals.ts b/client/src/reportWebVitals.ts
deleted file mode 100644
index a832dfa7..00000000
--- a/client/src/reportWebVitals.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { ReportHandler } from 'web-vitals'
-
-const reportWebVitals: () => void = (onPerfEntry?: ReportHandler) => {
-  if (onPerfEntry && onPerfEntry instanceof Function) {
-    import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
-      getCLS(onPerfEntry)
-      getFID(onPerfEntry)
-      getFCP(onPerfEntry)
-      getLCP(onPerfEntry)
-      getTTFB(onPerfEntry)
-    })
-  }
-}
-
-export default reportWebVitals
diff --git a/client/src/utils/checkAuthenticationCompetition.test.ts b/client/src/utils/checkAuthenticationCompetition.test.ts
new file mode 100644
index 00000000..0d0fdd90
--- /dev/null
+++ b/client/src/utils/checkAuthenticationCompetition.test.ts
@@ -0,0 +1,79 @@
+import mockedAxios from 'axios'
+import Types from '../actions/types'
+import store from '../store'
+import { CheckAuthenticationCompetition } from './checkAuthenticationCompetition'
+
+it('dispatches correct actions when auth token is ok', async () => {
+  const compRes = { data: { id: 3, slides: [{ id: 2 }] } }
+  ;(mockedAxios.get as jest.Mock).mockImplementation(() => {
+    return Promise.resolve(compRes)
+  })
+  ;(mockedAxios.post as jest.Mock).mockImplementation(() => {
+    return Promise.resolve({ data: {} })
+  })
+  const spy = jest.spyOn(store, 'dispatch')
+  const decodedToken = {
+    iat: 1620216181,
+    exp: 32514436993,
+    user_claims: { competition_id: 123123, team_id: 321321, view: 'Participant', code: 'ABCDEF' },
+  }
+
+  const testToken =
+    'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjMyNTE0NDM2OTkzLCJ1c2VyX2NsYWltcyI6eyJjb21wZXRpdGlvbl9pZCI6MTIzMTIzLCJ0ZWFtX2lkIjozMjEzMjEsInZpZXciOiJQYXJ0aWNpcGFudCIsImNvZGUiOiJBQkNERUYifX0.1gPRJcjn3xuPOcgUUffMngIQDoDtxS9RZczcbdyyaaA'
+  localStorage.setItem('competitionToken', testToken)
+  await CheckAuthenticationCompetition()
+  expect(spy).toBeCalledWith({
+    type: Types.SET_COMPETITION_LOGIN_DATA,
+    payload: {
+      competition_id: decodedToken.user_claims.competition_id,
+      team_id: decodedToken.user_claims.team_id,
+      view: decodedToken.user_claims.view,
+    },
+  })
+  expect(spy).toBeCalledWith({ type: Types.SET_PRESENTATION_CODE, payload: decodedToken.user_claims.code })
+  expect(spy).toBeCalledWith({
+    type: Types.SET_PRESENTATION_COMPETITION,
+    payload: compRes.data,
+  })
+  expect(spy).toBeCalledTimes(4)
+})
+
+it('dispatches correct actions when getting user data fails', async () => {
+  console.log = jest.fn()
+  ;(mockedAxios.get as jest.Mock).mockImplementation(() => {
+    return Promise.reject(new Error('failed getting user data'))
+  })
+  ;(mockedAxios.post as jest.Mock).mockImplementation(() => {
+    return Promise.resolve({ data: {} })
+  })
+  const spy = jest.spyOn(store, 'dispatch')
+  const testToken =
+    'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjMyNTE0NDM2OTkzLCJ1c2VyX2NsYWltcyI6eyJjb21wZXRpdGlvbl9pZCI6MTIzMTIzLCJ0ZWFtX2lkIjozMjEzMjEsInZpZXciOiJQYXJ0aWNpcGFudCIsImNvZGUiOiJBQkNERUYifX0.1gPRJcjn3xuPOcgUUffMngIQDoDtxS9RZczcbdyyaaA'
+  localStorage.setItem('competitionToken', testToken)
+  await CheckAuthenticationCompetition()
+  expect(spy).toBeCalledWith({ type: Types.SET_COMPETITION_LOGIN_UNAUTHENTICATED })
+  expect(spy).toBeCalledTimes(1)
+  expect(console.log).toHaveBeenCalled()
+})
+
+it('dispatches no actions when no token exists', async () => {
+  ;(mockedAxios.post as jest.Mock).mockImplementation(() => {
+    return Promise.resolve({ data: {} })
+  })
+  const spy = jest.spyOn(store, 'dispatch')
+  await CheckAuthenticationCompetition()
+  expect(spy).not.toBeCalled()
+})
+
+it('dispatches correct actions when token is expired', async () => {
+  ;(mockedAxios.post as jest.Mock).mockImplementation(() => {
+    return Promise.resolve({ data: {} })
+  })
+  const testToken =
+    'Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2MjAyMjE1OTgsImV4cCI6OTU3NTMzNTk4LCJhdWQiOiJ3d3cuZXhhbXBsZS5jb20iLCJzdWIiOiJqcm9ja2V0QGV4YW1wbGUuY29tIn0.uFXtkAsf-cTlKrTIdZ3E-gXnHkzS08iPrhS8iNCGV2E'
+  localStorage.setItem('competitionToken', testToken)
+  const spy = jest.spyOn(store, 'dispatch')
+  await CheckAuthenticationCompetition()
+  expect(spy).toBeCalledWith({ type: Types.SET_COMPETITION_LOGIN_UNAUTHENTICATED })
+  expect(spy).toBeCalledTimes(1)
+})
diff --git a/client/src/utils/checkAuthenticationCompetition.ts b/client/src/utils/checkAuthenticationCompetition.ts
index 9877b674..4d542dc3 100644
--- a/client/src/utils/checkAuthenticationCompetition.ts
+++ b/client/src/utils/checkAuthenticationCompetition.ts
@@ -17,7 +17,7 @@ export const CheckAuthenticationCompetition = async () => {
       axios.defaults.headers.common['Authorization'] = authToken
       await axios
         .get('/api/auth/test')
-        .then((res) => {
+        .then(() => {
           store.dispatch({
             type: Types.SET_COMPETITION_LOGIN_DATA,
             payload: {
diff --git a/client/src/utils/renderSlideIcon.test.tsx b/client/src/utils/renderSlideIcon.test.tsx
new file mode 100644
index 00000000..ed82c2ef
--- /dev/null
+++ b/client/src/utils/renderSlideIcon.test.tsx
@@ -0,0 +1,63 @@
+import BuildOutlinedIcon from '@material-ui/icons/BuildOutlined'
+import CheckBoxOutlinedIcon from '@material-ui/icons/CheckBoxOutlined'
+import CreateOutlinedIcon from '@material-ui/icons/CreateOutlined'
+import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'
+import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked'
+import { shallow } from 'enzyme'
+import { RichSlide } from '../interfaces/ApiRichModels'
+import { renderSlideIcon } from './renderSlideIcon'
+
+it('returns CreateOutlinedIcon correctly ', async () => {
+  const testSlide = { questions: [{ id: 5, type_id: 1 }] } as RichSlide
+  const icon = renderSlideIcon(testSlide)
+  expect(icon).toBeDefined()
+  if (icon) {
+    const actualResult = shallow(icon)
+    const expectedResult = shallow(<CreateOutlinedIcon />)
+    expect(actualResult).toEqual(expectedResult)
+  }
+})
+
+it('returns BuildOutlinedIcon  correctly ', async () => {
+  const testSlide = { questions: [{ id: 5, type_id: 2 }] } as RichSlide
+  const icon = renderSlideIcon(testSlide)
+  expect(icon).toBeDefined()
+  if (icon) {
+    const actualResult = shallow(icon)
+    const expectedResult = shallow(<BuildOutlinedIcon />)
+    expect(actualResult).toEqual(expectedResult)
+  }
+})
+
+it('returns DnsOutlinedIcon  correctly ', async () => {
+  const testSlide = { questions: [{ id: 5, type_id: 3 }] } as RichSlide
+  const icon = renderSlideIcon(testSlide)
+  expect(icon).toBeDefined()
+  if (icon) {
+    const actualResult = shallow(icon)
+    const expectedResult = shallow(<CheckBoxOutlinedIcon />)
+    expect(actualResult).toEqual(expectedResult)
+  }
+})
+
+it('returns DnsOutlinedIcon  correctly ', async () => {
+  const testSlide = { questions: [{ id: 5, type_id: 4 }] } as RichSlide
+  const icon = renderSlideIcon(testSlide)
+  expect(icon).toBeDefined()
+  if (icon) {
+    const actualResult = shallow(icon)
+    const expectedResult = shallow(<RadioButtonCheckedIcon />)
+    expect(actualResult).toEqual(expectedResult)
+  }
+})
+
+it('defaults to InfoOutlinedIcon', async () => {
+  const testSlide = {} as RichSlide
+  const icon = renderSlideIcon(testSlide)
+  expect(icon).toBeDefined()
+  if (icon) {
+    const actualResult = shallow(icon)
+    const expectedResult = shallow(<InfoOutlinedIcon />)
+    expect(actualResult).toEqual(expectedResult)
+  }
+})
-- 
GitLab