diff --git a/.gitlab/client.gitlab-ci.yml b/.gitlab/client.gitlab-ci.yml
index f35e73aa6da4b396dda7ca7b5847b83158078a41..a64f1bd85c1bba81556cc1f8fb8052cf14ac40cd 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 8b7286d134089b693c07ce77c748073701865719..45f88f114c1419ec24f9794c6d2568ad61c61e1a 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 2655ad2e5dbc1fb024ba896a3701f49adbfe399d..d857520c196037dc01bf9583d906ddaf9ed11d00 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 a0c8b7f5c74a6587f48cdf6c7d478311f2529185..7426424b33775f3845151df9163b10cd94f6f494 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
Binary files a/client/public/logo192.png and /dev/null differ
diff --git a/client/public/logo512.png b/client/public/logo512.png
deleted file mode 100644
index a4e47a6545bc15971f8f63fba70e4013df88a664..0000000000000000000000000000000000000000
Binary files a/client/public/logo512.png and /dev/null differ
diff --git a/client/public/manifest.json b/client/public/manifest.json
index 080d6c77ac21bb2ef88a6992b2b73ad93daaca92..73371289ff660726868cc23f5f2ab7f275bba3cc 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 0000000000000000000000000000000000000000..bf1825e7e02445c2b96b22b5bb0a169e723dc2a4
--- /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 d95d9db767b214e4e2583a1a903ed06b226eec16..095a352952680810cfcb3fc0f8f48a5eabc6b1a6 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 90b728c54aa18a483ed6ab000f718c407b46abfd..32e4d0a4334c4639624588f005b704dbca160ae9 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 7fa4aaeaab31902640232d60b25139a0c26f078e..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..c9c67580ba029c41c9849a6b057cfcdba25dd2a0
--- /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 0000000000000000000000000000000000000000..6a60ff6bd50aec0965f670126437b393774da38b
--- /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 0000000000000000000000000000000000000000..3122aa2126c17021211ddd88cf0936ec0111cdf9
--- /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 fdd9cf8e2bc1d9655a060c4270091fbf5f13e236..cb658ebf015a5f33f4844d280f358d348cf24a0e 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 9dfc1c058cebbef8b891c5062be6f31033d7d186..0000000000000000000000000000000000000000
--- 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 dc4e91ab6fd33627a7b19cb06be5ba20fee4598d..0000000000000000000000000000000000000000
--- 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 1b543083da7fbfb1e5c1a48d44a40a2d7fdcc17e..3a375210ae30a021d7a758e6ca9e3c9b18d4d45d 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 6053f2f8de9c3d13cde3403ec1a62317e477d047..e404b112a1df1e6ab4f9d8a8759fb946a20ccd52 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 7de930dceab457d43e6552bc5db2465a10a9bddd..9b72ae04055e5dccea5cbb6cd0826577a0f5068b 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 1d6afe732935bb3094ae1068179c7dc6ea58fb17..933a320b30e637d8100e29226f4bd41a49a48572 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 8bd5e562d2006962f5366722a207efe23c9cff55..10cb22bc5c4b7527b05d8dfeb9b0f748dd6f0c65 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 28a25cadf2cdc5785deb3c7de35f10dc45683939..5e2531bb44b6e2e6d7d6a3ce65941ecdc03a3ee3 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 d24bf130785110cb1dd039dde7dbab57107a4e9d..63549edcd3180033e897c8bfb3924e6a691225bd 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 9b4a5d1bd6aecd92a03f44221c2cb40d40e2ab70..5de773fe7a58ed8c0f252cda20f844c801819b32 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 e29a6786826e854bebf888be5b7cd904e0ec1063..0000000000000000000000000000000000000000
--- 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 4fe475245cb0539bd61c0987d8aa39fa4cc6e40d..c33f338da69a6d2f0d1733a6d5bb777461cdefe4 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 0000000000000000000000000000000000000000..17705308993ad89a8f7175549e9af3edd82e8010
--- /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 cb264712b0b4b57ef7938278471bc812085370cf..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..b024ab3f14d82f8fb55bf336938a2fa17f911b15
--- /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 0000000000000000000000000000000000000000..655b21bfed8ac33c433bd6b152736a344aeaf02b
--- /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 0000000000000000000000000000000000000000..917fa0c8642a308460e7fcc007c01ba1e91f5ad9
--- /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 49f41e7f87fa6c53dac59704540ca3c186ef708b..60c68fb2aa287620547b6b6df38a8ed9da211893 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 0000000000000000000000000000000000000000..fe745c839cc2bd9a59f07e4e97cbb85a90c19e98
--- /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 99b4b1d8d118e65595b38077e8fd15afa103ad05..de34bc205b37cc60d6c086388e4c4d0995933bcd 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 0000000000000000000000000000000000000000..75131257642df47d260ec795025e1c8bee759a6a
--- /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 58053995856730b22a16d4401af17da5628ab87a..cf803d3a14588d4a172f6bc452ce4cb07dda6e20 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 0000000000000000000000000000000000000000..c9f13bcdd7e7b76d0e90de61cfc2a86c52dca194
--- /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 e917afbd19f1aac2d7256c1f5913d8a63f323ee1..f714fe2343e427a281d4e08aac50b895257d6424 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 0000000000000000000000000000000000000000..b0b44c6439cf8d2f2aa3d4dd8eb94deb0ed3ef0e
--- /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 bef33d943883981ee62e9dc49dcd09902d907737..8fbae43816a55a2be1f1dcc838afffd9f3cf0b9e 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 0000000000000000000000000000000000000000..edd4a89f28c440abb20e406de84fb0121647c718
--- /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 0000000000000000000000000000000000000000..5ef0d2ed43af3fae53ad94816027d97091340a83
--- /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 01a0a6f29b51d9a194cb397fdc73c720202e0c1c..0000000000000000000000000000000000000000
--- 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 90cb24144612c5a45f611ad99871c54c51969bbd..038b172e3e308af71d5762ade8a644b67d7992d2 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 1d6f4ce5f5e67a15b6469d33ad838cf16f474f35..0000000000000000000000000000000000000000
--- 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 a832dfa7c24162e95463f27f3687de39a3c311bb..0000000000000000000000000000000000000000
--- 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 0000000000000000000000000000000000000000..0d0fdd90aea38f613a7a1b1347085a2201d793a6
--- /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 9877b674214fc5fae2899148fd9e57c9a0a3bc28..4d542dc34b5cb78cca7dbf134638a8b2649c48de 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 0000000000000000000000000000000000000000..ed82c2efcd8b880538a98176acead1b7221104b5
--- /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)
+  }
+})