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/README.md b/README.md
index 56ea97f2b1afc129c0221d36ce6978802a250d89..10a4debc16e5edc64dea58216139c2913884a0cd 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,3 @@
-![coverage report](https://gitlab.liu.se/tddd96-grupp11/teknikattan-scoring-system/badges/dev/coverage.svg?job=client:test&key_text=Client+Coverage&key_width=110)
-![coverage report](https://gitlab.liu.se/tddd96-grupp11/teknikattan-scoring-system/badges/dev/coverage.svg?job=server:test&key_text=Server+Coverage&key_width=115)
-
-
-
-
 # Scoring system for Teknikåttan
 
 This is the scoring system for Teknikåttan!
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/enum/ComponentTypes.ts b/client/src/enum/ComponentTypes.ts
index c0aa738479a2c081ef0529bb6b4a317570b6841d..7ff75bd8d867762550dd9bcb6997038ac8d4b233 100644
--- a/client/src/enum/ComponentTypes.ts
+++ b/client/src/enum/ComponentTypes.ts
@@ -1,5 +1,5 @@
 export enum ComponentTypes {
   Text = 1,
   Image,
-  QuestionAlternative,
+  Question,
 }
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/interfaces/ApiModels.ts b/client/src/interfaces/ApiModels.ts
index 2734884b7783b662113dc00f61d112f8a58f5dd6..9bcd24c1b916ff925a9472dffd8b2fabd5395bfb 100644
--- a/client/src/interfaces/ApiModels.ts
+++ b/client/src/interfaces/ApiModels.ts
@@ -94,6 +94,16 @@ export interface TextComponent extends Component {
   font: string
 }
 
-export interface QuestionAlternativeComponent extends Component {
+export interface QuestionComponent extends Component {
+  id: number
+  x: number
+  y: number
+  w: number
+  h: number
+  slide_id: number
+  type_id: number
+  view_type_id: number
+  text: string
+  media: Media
   question_id: number
 }
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 b6af32793c9eac86553408fc742430eaeddea7a6..9b72ae04055e5dccea5cbb6cd0826577a0f5068b 100644
--- a/client/src/pages/admin/competitions/CompetitionManager.tsx
+++ b/client/src/pages/admin/competitions/CompetitionManager.tsx
@@ -131,6 +131,7 @@ const CompetitionManager: React.FC = (props: any) => {
     }
   }
 
+  /** Start the competition by redirecting with URL with Code */
   const handleStartCompetition = () => {
     const operatorCode = codes.find((code) => code.view_type_id === 4)?.code
     if (operatorCode) {
@@ -138,21 +139,22 @@ const CompetitionManager: React.FC = (props: any) => {
     }
   }
 
+  /** Fetch all the connection codes from the server */
   const getCodes = async (id: number) => {
     await axios
       .get(`/api/competitions/${id}/codes`)
       .then((response) => {
-        console.log(response.data)
         setCodes(response.data.items)
       })
       .catch(console.log)
   }
 
+  /** Fetch all the teams from the server that is connected to a specific competition*/
   const getTeams = async (id: number) => {
     await axios
       .get(`/api/competitions/${id}/teams`)
       .then((response) => {
-        console.log(response.data.items)
+        // console.log(response.data.items)
         setTeams(response.data.items)
       })
       .catch((err) => {
@@ -160,11 +162,12 @@ const CompetitionManager: React.FC = (props: any) => {
       })
   }
 
+  /** Fetch the copetition name from the server */
   const getCompetitionName = async () => {
     await axios
       .get(`/api/competitions/${activeId}`)
       .then((response) => {
-        console.log(response.data.name)
+        // console.log(response.data.name)
         setCompetitionName(response.data.name)
       })
       .catch((err) => {
@@ -199,16 +202,18 @@ const CompetitionManager: React.FC = (props: any) => {
     return typeName
   }
 
+  /** Handles the opening of the code dialog box */
   const handleOpenDialog = async () => {
     await getCompetitionName()
     setDialogIsOpen(true)
   }
-
+  /** Handles the closing of the code dialog box */
   const handleCloseDialog = () => {
     setDialogIsOpen(false)
     setAnchorEl(null)
   }
 
+  /** Function that copies an existing competition */
   const handleDuplicateCompetition = async () => {
     if (activeId) {
       await axios
@@ -246,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
@@ -309,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>
@@ -334,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/Dashboard.tsx b/client/src/pages/admin/dashboard/Dashboard.tsx
index a0f569b066a8073a14f9342dcb7d988be63b93b9..51aa4f4084ebf016fefbb740dcb081ac20f0e7fe 100644
--- a/client/src/pages/admin/dashboard/Dashboard.tsx
+++ b/client/src/pages/admin/dashboard/Dashboard.tsx
@@ -6,6 +6,11 @@ import NumberOfCompetitions from './components/NumberOfCompetitions'
 import NumberOfRegions from './components/NumberOfRegions'
 import NumberOfUsers from './components/NumberOfUsers'
 
+/**
+ * This is the first page that is shown after a user logs in. It shows som statistics about the site.
+ *
+ */
+
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
     root: {
diff --git a/client/src/pages/admin/dashboard/components/CurrentUser.tsx b/client/src/pages/admin/dashboard/components/CurrentUser.tsx
index 0dfdf557bff55224f513fb742ba59bdc8c8b1f56..933a320b30e637d8100e29226f4bd41a49a48572 100644
--- a/client/src/pages/admin/dashboard/components/CurrentUser.tsx
+++ b/client/src/pages/admin/dashboard/components/CurrentUser.tsx
@@ -2,6 +2,8 @@ import { Box, Typography } from '@material-ui/core'
 import React from 'react'
 import { useAppSelector } from '../../../../hooks'
 
+/** This component show information about the currently logged in user */
+
 const CurrentUser: React.FC = () => {
   const currentUser = useAppSelector((state: { user: { userInfo: any } }) => state.user.userInfo)
   return (
@@ -13,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/dashboard/components/NumberOfCompetitions.tsx b/client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx
index eb667ebd690f6013c184ac13e2038ce1a7726de9..1ebe9dfba3e888e9bf07b998cff321613ba035d3 100644
--- a/client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx
+++ b/client/src/pages/admin/dashboard/components/NumberOfCompetitions.tsx
@@ -2,6 +2,8 @@ import { Box, Typography } from '@material-ui/core'
 import React from 'react'
 import { useAppSelector } from '../../../../hooks'
 
+/** Shows how many competitions is on the system */
+
 const NumberOfCompetitions: React.FC = () => {
   const competitions = useAppSelector((state) => state.statistics.competitions)
 
diff --git a/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx b/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx
index f3195f4c18969ed2ad1e4b185b2125ac2c15f782..5f9ecef5e60ff4fda27d6a42771c043b83e9dc8e 100644
--- a/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx
+++ b/client/src/pages/admin/dashboard/components/NumberOfRegions.tsx
@@ -3,6 +3,8 @@ import React, { useEffect } from 'react'
 import { getCities } from '../../../../actions/cities'
 import { useAppDispatch, useAppSelector } from '../../../../hooks'
 
+/** Shows how many regions is on the system */
+
 const NumberOfRegions: React.FC = () => {
   const regions = useAppSelector((state) => state.statistics.regions)
   const dispatch = useAppDispatch()
diff --git a/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx b/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx
index 0e75cf1b1069814801fadc010f30ac393dfe3b25..91291e689529c8c65a54ed63c7ae77ce489ebe2f 100644
--- a/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx
+++ b/client/src/pages/admin/dashboard/components/NumberOfUsers.tsx
@@ -3,6 +3,8 @@ import React, { useEffect } from 'react'
 import { getSearchUsers } from '../../../../actions/searchUser'
 import { useAppDispatch, useAppSelector } from '../../../../hooks'
 
+/** Shows how many users are on the system */
+
 const NumberOfUsers: React.FC = () => {
   const usersTotal = useAppSelector((state) => state.statistics.users)
   const dispatch = useAppDispatch()
diff --git a/client/src/pages/admin/regions/AddRegion.tsx b/client/src/pages/admin/regions/AddRegion.tsx
index b00961384f73246365e60023af04ed7567adf311..10cb22bc5c4b7527b05d8dfeb9b0f748dd6f0c65 100644
--- a/client/src/pages/admin/regions/AddRegion.tsx
+++ b/client/src/pages/admin/regions/AddRegion.tsx
@@ -30,6 +30,7 @@ const useStyles = makeStyles((theme: Theme) =>
 
 type formType = FormModel<AddCityModel>
 
+/** add a region form with some constraints. */
 const schema: Yup.SchemaOf<formType> = Yup.object({
   model: Yup.object()
     .shape({
@@ -78,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)}
@@ -85,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 436da6f69429ca6711452d7babf477f8da593eee..5e2531bb44b6e2e6d7d6a3ce65941ecdc03a3ee3 100644
--- a/client/src/pages/admin/regions/Regions.tsx
+++ b/client/src/pages/admin/regions/Regions.tsx
@@ -14,6 +14,9 @@ import { getCities } from '../../../actions/cities'
 import { useAppDispatch, useAppSelector } from '../../../hooks'
 import { RemoveMenuItem, TopBar } from '../styledComp'
 import AddRegion from './AddRegion'
+
+/** shows all the regions in a list */
+
 const useStyles = makeStyles((theme: Theme) =>
   createStyles({
     table: {
@@ -96,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>
@@ -107,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 9f1511eac460c661ebd35dc995f6606e66080359..63549edcd3180033e897c8bfb3924e6a691225bd 100644
--- a/client/src/pages/admin/users/AddUser.tsx
+++ b/client/src/pages/admin/users/AddUser.tsx
@@ -1,3 +1,5 @@
+/** Add a user component */
+
 import { Button, FormControl, InputLabel, MenuItem, Popover, TextField } from '@material-ui/core'
 import PersonAddIcon from '@material-ui/icons/PersonAdd'
 import { Alert, AlertTitle } from '@material-ui/lab'
@@ -16,6 +18,7 @@ type formType = FormModel<AddUserModel>
 const noRoleSelected = 'Välj roll'
 const noCitySelected = 'Välj stad'
 
+/** Form when adding a user with some constraints */
 const userSchema: Yup.SchemaOf<formType> = Yup.object({
   model: Yup.object()
     .shape({
@@ -84,6 +87,7 @@ const AddUser: React.FC = (props: any) => {
   return (
     <div>
       <AddButton
+        data-testid="addUserButton"
         style={{ backgroundColor: '#4caf50', color: '#fcfcfc' }}
         color="default"
         variant="contained"
@@ -111,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 : ''}
@@ -120,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 : ''}
@@ -129,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 : ''}
@@ -143,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}
@@ -157,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>
                       ))}
@@ -170,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}
@@ -184,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>
                       ))}
@@ -193,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/LoginPage.tsx b/client/src/pages/login/LoginPage.tsx
index 8f8e967c435af9c2611bc9ac3c1365b114a7666a..34c9c5ae6c5344666dc383d7f2df7586ecc13e10 100644
--- a/client/src/pages/login/LoginPage.tsx
+++ b/client/src/pages/login/LoginPage.tsx
@@ -1,3 +1,7 @@
+/** This is the login page, it contains two child components, one is
+ *   to log in as an admin, the other is to connect to a competition using a code
+ */
+
 import { AppBar, Tab, Tabs } from '@material-ui/core'
 import React from 'react'
 import AdminLogin from './components/AdminLogin'
diff --git a/client/src/pages/login/components/AdminLogin.tsx b/client/src/pages/login/components/AdminLogin.tsx
index 964fb8abd148a9a0735206e9dbe3288cbe72ea3a..c33f338da69a6d2f0d1733a6d5bb777461cdefe4 100644
--- a/client/src/pages/login/components/AdminLogin.tsx
+++ b/client/src/pages/login/components/AdminLogin.tsx
@@ -1,3 +1,5 @@
+/** Component that handles the log in when a user is an admin */
+
 import { Button, TextField, Typography } from '@material-ui/core'
 import { Alert, AlertTitle } from '@material-ui/lab'
 import { Formik, FormikHelpers } from 'formik'
@@ -55,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}
@@ -65,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}
@@ -73,6 +77,7 @@ const AdminLogin: React.FC = () => {
           />
           <Button
             type="submit"
+            data-testid="submit"
             fullWidth
             variant="contained"
             color="secondary"
diff --git a/client/src/pages/login/components/CompetitionLogin.tsx b/client/src/pages/login/components/CompetitionLogin.tsx
index 8dbbee33b6962c5bcd21f346ee594b224719a9a7..444634740fa361ad6deb1493c6673427f5fb8717 100644
--- a/client/src/pages/login/components/CompetitionLogin.tsx
+++ b/client/src/pages/login/components/CompetitionLogin.tsx
@@ -1,3 +1,5 @@
+/** Component that handles the log in when a user connects to a competition through a code */
+
 import { Button, TextField, Typography } from '@material-ui/core'
 import { Alert, AlertTitle } from '@material-ui/lab'
 import { Formik } from 'formik'
@@ -38,7 +40,7 @@ const CompetitionLogin: React.FC = () => {
   const handleCompetitionSubmit = async (values: CompetitionLoginFormModel) => {
     dispatch(loginCompetition(values.model.code, history, true))
   }
-  
+
   return (
     <Formik
       initialValues={competitionInitialValues}
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/QuestionComponentDisplay.tsx b/client/src/pages/presentationEditor/components/QuestionComponentDisplay.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..dd8e2fa85a8e2562595c85c7068b0073f744092a
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/QuestionComponentDisplay.tsx
@@ -0,0 +1,84 @@
+import { Card, Divider, ListItem, Typography } from '@material-ui/core'
+import React from 'react'
+import { useAppSelector } from '../../../hooks'
+import AnswerMultiple from './answerComponents/AnswerMultiple'
+import AnswerSingle from './answerComponents/AnswerSingle'
+import AnswerText from './answerComponents/AnswerText'
+import { Center } from './styled'
+
+type QuestionComponentProps = {
+  variant: 'editor' | 'presentation'
+}
+
+const QuestionComponentDisplay = ({ variant }: QuestionComponentProps) => {
+  const activeSlide = useAppSelector((state) => {
+    if (variant === 'editor')
+      return state.editor.competition.slides.find((slide) => slide.id === state.editor.activeSlideId)
+    return state.presentation.competition.slides.find((slide) => slide.id === state.presentation.slide?.id)
+  })
+
+  const timer = activeSlide?.timer
+  const total_score = activeSlide?.questions[0].total_score
+  const questionName = activeSlide?.questions[0].name
+
+  const questionTypeId = activeSlide?.questions[0].type_id
+  const questionTypeName = useAppSelector(
+    (state) => state.types.questionTypes.find((qType) => qType.id === questionTypeId)?.name
+  )
+
+  const getAlternatives = () => {
+    switch (questionTypeName) {
+      case 'Text':
+        if (activeSlide) {
+          return <AnswerText activeSlide={activeSlide} competitionId={activeSlide.competition_id.toString()} />
+        }
+        return
+
+      case 'Practical':
+        return
+
+      case 'Multiple':
+        if (activeSlide) {
+          return (
+            <AnswerMultiple
+              variant={variant}
+              activeSlide={activeSlide}
+              competitionId={activeSlide.competition_id.toString()}
+            />
+          )
+        }
+        return
+
+      case 'Single':
+        if (activeSlide) {
+          return (
+            <AnswerSingle
+              variant={variant}
+              activeSlide={activeSlide}
+              competitionId={activeSlide.competition_id.toString()}
+            />
+          )
+        }
+        return
+
+      default:
+        break
+    }
+  }
+
+  return (
+    <Card style={{ maxHeight: '100%', overflowY: 'auto' }}>
+      <ListItem>
+        <Center style={{ justifyContent: 'space-evenly' }}>
+          <Typography>Poäng: {total_score}</Typography>
+          <Typography>{questionName}</Typography>
+          <Typography>Timer: {timer}</Typography>
+        </Center>
+      </ListItem>
+      <Divider />
+      {getAlternatives()}
+    </Card>
+  )
+}
+
+export default QuestionComponentDisplay
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/RndComponent.tsx b/client/src/pages/presentationEditor/components/RndComponent.tsx
index a134ee14c9ac268159b6ae0013350012e638dc65..4c09280042bd038fee1e3adc4b5bf3b7aef8ef6a 100644
--- a/client/src/pages/presentationEditor/components/RndComponent.tsx
+++ b/client/src/pages/presentationEditor/components/RndComponent.tsx
@@ -9,6 +9,7 @@ import { Component, ImageComponent, TextComponent } from '../../../interfaces/Ap
 import { Position, Size } from '../../../interfaces/Components'
 import { RemoveMenuItem } from '../../admin/styledComp'
 import ImageComponentDisplay from './ImageComponentDisplay'
+import QuestionComponentDisplay from './QuestionComponentDisplay'
 import { HoverContainer } from './styled'
 import TextComponentDisplay from './TextComponentDisplay'
 //import NestedMenuItem from 'material-ui-nested-menu-item'
@@ -126,6 +127,12 @@ const RndComponent = ({ component, width, height, scale }: RndComponentProps) =>
             />
           </HoverContainer>
         )
+      case ComponentTypes.Question:
+        return (
+          <HoverContainer hover={hover}>
+            <QuestionComponentDisplay variant="editor" />
+          </HoverContainer>
+        )
       default:
         break
     }
diff --git a/client/src/pages/presentationEditor/components/SlideDisplay.tsx b/client/src/pages/presentationEditor/components/SlideDisplay.tsx
index aef4ca7c48d29bbfc47cac8f29b214ec6866f360..e6b60f290784627d9c5446428062f2e1042cacef 100644
--- a/client/src/pages/presentationEditor/components/SlideDisplay.tsx
+++ b/client/src/pages/presentationEditor/components/SlideDisplay.tsx
@@ -77,15 +77,7 @@ const SlideDisplay = ({ variant, activeViewTypeId }: SlideDisplayProps) => {
                       scale={scale}
                     />
                   )
-                return (
-                  <PresentationComponent
-                    height={height}
-                    width={width}
-                    key={component.id}
-                    component={component}
-                    scale={scale}
-                  />
-                )
+                return <PresentationComponent key={component.id} component={component} scale={scale} />
               })}
         </SlideEditorPaper>
       </SlideEditorContainerRatio>
diff --git a/client/src/pages/presentationEditor/components/SlideSettings.tsx b/client/src/pages/presentationEditor/components/SlideSettings.tsx
index d48b4565712d043d52d3b1e93e3633ec6b927d38..029187f49e06fcffbb147a82fc7e754ec09e3a0f 100644
--- a/client/src/pages/presentationEditor/components/SlideSettings.tsx
+++ b/client/src/pages/presentationEditor/components/SlideSettings.tsx
@@ -1,18 +1,19 @@
 /* This file compiles and renders the right hand slide settings bar, under the tab "SIDA".
  */
-import { Divider, List, ListItem, ListItemText, TextField, Typography } from '@material-ui/core'
-import React, { useState } from 'react'
+import { Divider } from '@material-ui/core'
+import React from 'react'
 import { useParams } from 'react-router-dom'
 import { useAppSelector } from '../../../hooks'
+import BackgroundImageSelect from './BackgroundImageSelect'
+import Images from './slideSettingsComponents/Images'
 import Instructions from './slideSettingsComponents/Instructions'
 import MultipleChoiceAlternatives from './slideSettingsComponents/MultipleChoiceAlternatives'
+import QuestionSettings from './slideSettingsComponents/QuestionSettings'
+import SingleChoiceAlternatives from './slideSettingsComponents/SingleChoiceAlternatives'
 import SlideType from './slideSettingsComponents/SlideType'
-import { Center, ImportedImage, SettingsList, PanelContainer } from './styled'
-import Timer from './slideSettingsComponents/Timer'
-import Images from './slideSettingsComponents/Images'
 import Texts from './slideSettingsComponents/Texts'
-import QuestionSettings from './slideSettingsComponents/QuestionSettings'
-import BackgroundImageSelect from './BackgroundImageSelect'
+import Timer from './slideSettingsComponents/Timer'
+import { PanelContainer, SettingsList } from './styled'
 
 interface CompetitionParams {
   competitionId: string
@@ -36,19 +37,21 @@ const SlideSettings: React.FC = () => {
       </SettingsList>
 
       {activeSlide?.questions[0] && <QuestionSettings activeSlide={activeSlide} competitionId={competitionId} />}
+
       {
-        // Choose answer alternatives depending on the slide type
+        // Choose answer alternatives, depending on the slide type
       }
-      {activeSlide?.questions[0]?.type_id === 1 && (
-        <Instructions activeSlide={activeSlide} competitionId={competitionId} />
-      )}
-      {activeSlide?.questions[0]?.type_id === 2 && (
+      {(activeSlide?.questions[0]?.type_id === 1 || activeSlide?.questions[0]?.type_id === 2) && (
         <Instructions activeSlide={activeSlide} competitionId={competitionId} />
       )}
       {activeSlide?.questions[0]?.type_id === 3 && (
         <MultipleChoiceAlternatives activeSlide={activeSlide} competitionId={competitionId} />
       )}
 
+      {activeSlide?.questions[0]?.type_id === 4 && (
+        <SingleChoiceAlternatives activeSlide={activeSlide} competitionId={competitionId} />
+      )}
+
       {activeSlide && (
         <Texts activeViewTypeId={activeViewTypeId} activeSlide={activeSlide} competitionId={competitionId} />
       )}
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/answerComponents/AnswerMultiple.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerMultiple.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..7a2b1c59acf0277197d6126a7aba5b81a8799a37
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerMultiple.tsx
@@ -0,0 +1,106 @@
+import { Checkbox, ListItem, ListItemText, Typography, withStyles } from '@material-ui/core'
+import { CheckboxProps } from '@material-ui/core/Checkbox'
+import { green, grey } from '@material-ui/core/colors'
+import axios from 'axios'
+import React from 'react'
+import { getEditorCompetition } from '../../../../actions/editor'
+import { getPresentationCompetition } from '../../../../actions/presentation'
+import { useAppDispatch, useAppSelector } from '../../../../hooks'
+import { QuestionAlternative } from '../../../../interfaces/ApiModels'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import { Center } from '../styled'
+
+type AnswerMultipleProps = {
+  variant: 'editor' | 'presentation'
+  activeSlide: RichSlide | undefined
+  competitionId: string
+}
+
+const AnswerMultiple = ({ variant, activeSlide, competitionId }: AnswerMultipleProps) => {
+  const dispatch = useAppDispatch()
+  const teamId = useAppSelector((state) => state.competitionLogin.data?.team_id)
+  const team = useAppSelector((state) => state.presentation.competition.teams.find((team) => team.id === teamId))
+  const answer = team?.question_answers.find((answer) => answer.question_id === activeSlide?.questions[0].id)
+
+  const decideChecked = (alternative: QuestionAlternative) => {
+    const teamAnswer = team?.question_answers.find((answer) => answer.answer === alternative.text)?.answer
+    if (alternative.text === teamAnswer) return true
+    else return false
+  }
+
+  const updateAnswer = async (alternative: QuestionAlternative) => {
+    // TODO: fix. Make list of alternatives and delete & post instead of put to allow multiple boxes checked.
+    if (activeSlide) {
+      if (team?.question_answers.find((answer) => answer.question_id === activeSlide.questions[0].id)) {
+        if (answer?.answer === alternative.text) {
+          // Uncheck checkbox
+          deleteAnswer()
+        } else {
+          // Check another box
+          // TODO
+        }
+      } else {
+        // Check first checkbox
+        await axios
+          .post(`/api/competitions/${competitionId}/teams/${teamId}/answers`, {
+            answer: alternative.text,
+            score: 0,
+            question_id: activeSlide.questions[0].id,
+          })
+          .then(() => {
+            if (variant === 'editor') {
+              dispatch(getEditorCompetition(competitionId))
+            } else {
+              dispatch(getPresentationCompetition(competitionId))
+            }
+          })
+          .catch(console.log)
+      }
+    }
+  }
+
+  const deleteAnswer = async () => {
+    await axios
+      .delete(`/api/competitions/${competitionId}/teams/${teamId}/answers`) // TODO: fix
+      .then(() => {
+        dispatch(getEditorCompetition(competitionId))
+      })
+      .catch(console.log)
+  }
+
+  const GreenCheckbox = withStyles({
+    root: {
+      color: grey[900],
+      '&$checked': {
+        color: green[600],
+      },
+    },
+    checked: {},
+  })((props: CheckboxProps) => <Checkbox color="default" {...props} />)
+
+  return (
+    <div>
+      <ListItem divider>
+        <Center>
+          <ListItemText primary="Välj ett eller flera svar:" />
+        </Center>
+      </ListItem>
+      {activeSlide &&
+        activeSlide.questions[0] &&
+        activeSlide.questions[0].alternatives &&
+        activeSlide.questions[0].alternatives.map((alt) => (
+          <div key={alt.id}>
+            <ListItem divider>
+              {
+                //<GreenCheckbox checked={checkbox} onChange={(event) => updateAnswer(alt, event.target.checked)} />
+              }
+              <GreenCheckbox checked={decideChecked(alt)} onChange={() => updateAnswer(alt)} />
+              <Typography style={{ wordBreak: 'break-all' }}>{alt.text}</Typography>
+            </ListItem>
+          </div>
+        ))}
+    </div>
+  )
+}
+
+export default AnswerMultiple
diff --git a/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..8c2fad17395e9c602a7108a72301739064ab4533
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerSingle.tsx
@@ -0,0 +1,132 @@
+import { Checkbox, ListItem, ListItemText, Typography, withStyles } from '@material-ui/core'
+import { CheckboxProps } from '@material-ui/core/Checkbox'
+import { green, grey } from '@material-ui/core/colors'
+import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonCheckedOutlined'
+import RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUncheckedOutlined'
+import axios from 'axios'
+import React from 'react'
+import { getEditorCompetition } from '../../../../actions/editor'
+import { getPresentationCompetition } from '../../../../actions/presentation'
+import { useAppDispatch, useAppSelector } from '../../../../hooks'
+import { QuestionAlternative } from '../../../../interfaces/ApiModels'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import { Center, Clickable } from '../styled'
+
+type AnswerSingleProps = {
+  variant: 'editor' | 'presentation'
+  activeSlide: RichSlide | undefined
+  competitionId: string
+}
+
+const AnswerSingle = ({ variant, activeSlide, competitionId }: AnswerSingleProps) => {
+  const dispatch = useAppDispatch()
+  const teamId = useAppSelector((state) => state.competitionLogin.data?.team_id)
+  const team = useAppSelector((state) => {
+    if (variant === 'editor') return state.editor.competition.teams.find((team) => team.id === teamId)
+    return state.presentation.competition.teams.find((team) => team.id === teamId)
+  })
+  const answerId = team?.question_answers.find((answer) => answer.question_id === activeSlide?.questions[0].id)?.id
+
+  const decideChecked = (alternative: QuestionAlternative) => {
+    const teamAnswer = team?.question_answers.find((answer) => answer.answer === alternative.text)?.answer
+    if (teamAnswer) return true
+    else return false
+  }
+
+  const updateAnswer = async (alternative: QuestionAlternative) => {
+    if (activeSlide) {
+      // TODO: ignore API calls when an answer is already checked
+      if (team?.question_answers[0]) {
+        await axios
+          .put(`/api/competitions/${competitionId}/teams/${teamId}/answers/${answerId}`, {
+            answer: alternative.text,
+          })
+          .then(() => {
+            if (variant === 'editor') {
+              dispatch(getEditorCompetition(competitionId))
+            } else {
+              dispatch(getPresentationCompetition(competitionId))
+            }
+          })
+          .catch(console.log)
+      } else {
+        await axios
+          .post(`/api/competitions/${competitionId}/teams/${teamId}/answers`, {
+            answer: alternative.text,
+            score: 0,
+            question_id: activeSlide.questions[0].id,
+          })
+          .then(() => {
+            if (variant === 'editor') {
+              dispatch(getEditorCompetition(competitionId))
+            } else {
+              dispatch(getPresentationCompetition(competitionId))
+            }
+          })
+          .catch(console.log)
+      }
+    }
+  }
+
+  const deleteAnswer = async () => {
+    await axios
+      .delete(`/api/competitions/${competitionId}/teams/${teamId}/answers`)
+      .then(() => {
+        dispatch(getEditorCompetition(competitionId))
+      })
+      .catch(console.log)
+  }
+
+  const GreenCheckbox = withStyles({
+    root: {
+      color: grey[900],
+      '&$checked': {
+        color: green[600],
+      },
+    },
+    checked: {},
+  })((props: CheckboxProps) => <Checkbox color="default" {...props} />)
+
+  const renderRadioButton = (alt: QuestionAlternative) => {
+    if (variant === 'presentation') {
+      if (decideChecked(alt)) {
+        return (
+          <Clickable>
+            <RadioButtonCheckedIcon onClick={() => updateAnswer(alt)} />
+          </Clickable>
+        )
+      } else {
+        return (
+          <Clickable>
+            <RadioButtonUncheckedIcon onClick={() => updateAnswer(alt)} />
+          </Clickable>
+        )
+      }
+    } else {
+      return <RadioButtonUncheckedIcon onClick={() => updateAnswer(alt)} />
+    }
+  }
+
+  return (
+    <div>
+      <ListItem divider>
+        <Center>
+          <ListItemText primary="Välj ett svar:" />
+        </Center>
+      </ListItem>
+      {activeSlide &&
+        activeSlide.questions[0] &&
+        activeSlide.questions[0].alternatives &&
+        activeSlide.questions[0].alternatives.map((alt) => (
+          <div key={alt.id}>
+            <ListItem divider>
+              {renderRadioButton(alt)}
+              <Typography style={{ wordBreak: 'break-all' }}>{alt.text}</Typography>
+            </ListItem>
+          </div>
+        ))}
+    </div>
+  )
+}
+
+export default AnswerSingle
diff --git a/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx b/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e54b0dd2fe56f3ebf96b65fa8a05e6af4467da9f
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/answerComponents/AnswerText.tsx
@@ -0,0 +1,80 @@
+import { ListItem, ListItemText, TextField } from '@material-ui/core'
+import axios from 'axios'
+import React from 'react'
+import { getEditorCompetition } from '../../../../actions/editor'
+import { useAppDispatch, useAppSelector } from '../../../../hooks'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import { Center } from '../styled'
+import { AnswerTextFieldContainer } from './styled'
+
+type AnswerTextProps = {
+  activeSlide: RichSlide | undefined
+  competitionId: string
+}
+
+const AnswerText = ({ activeSlide, competitionId }: AnswerTextProps) => {
+  const [timerHandle, setTimerHandle] = React.useState<number | undefined>(undefined)
+  const dispatch = useAppDispatch()
+  const teamId = useAppSelector((state) => state.competitionLogin.data?.team_id)
+  const team = useAppSelector((state) => state.presentation.competition.teams.find((team) => team.id === teamId))
+  const answerId = team?.question_answers.find((answer) => answer.question_id === activeSlide?.questions[0].id)?.id
+  const onAnswerChange = (answer: string) => {
+    if (timerHandle) {
+      clearTimeout(timerHandle)
+      setTimerHandle(undefined)
+    }
+    //Only updates answer 100ms after last input was made
+    setTimerHandle(window.setTimeout(() => updateAnswer(answer), 100))
+  }
+
+  const updateAnswer = async (answer: string) => {
+    if (activeSlide && team) {
+      if (team?.question_answers.find((answer) => answer.question_id === activeSlide.questions[0].id)) {
+        await axios
+          .put(`/api/competitions/${competitionId}/teams/${teamId}/answers/${answerId}`, {
+            answer,
+          })
+          .then(() => {
+            dispatch(getEditorCompetition(competitionId))
+          })
+          .catch(console.log)
+      } else {
+        await axios
+          .post(`/api/competitions/${competitionId}/teams/${teamId}/answers`, {
+            answer,
+            score: 0,
+            question_id: activeSlide.questions[0].id,
+          })
+          .then(() => {
+            dispatch(getEditorCompetition(competitionId))
+          })
+          .catch(console.log)
+      }
+    }
+  }
+
+  return (
+    <AnswerTextFieldContainer>
+      <ListItem divider>
+        <Center>
+          <ListItemText primary="Skriv ditt svar nedan" />
+        </Center>
+      </ListItem>
+      <ListItem style={{ height: '100%' }}>
+        <TextField
+          disabled={team === undefined}
+          defaultValue={
+            team?.question_answers.find((questionAnswer) => questionAnswer.id === answerId)?.answer || 'Svar...'
+          }
+          style={{ height: '100%' }}
+          variant="outlined"
+          fullWidth={true}
+          multiline
+          onChange={(event) => onAnswerChange(event.target.value)}
+        />
+      </ListItem>
+    </AnswerTextFieldContainer>
+  )
+}
+
+export default AnswerText
diff --git a/client/src/pages/presentationEditor/components/answerComponents/styled.tsx b/client/src/pages/presentationEditor/components/answerComponents/styled.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..9140e3c2776f5bda3d2f317740bb4bbb67f657ac
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/answerComponents/styled.tsx
@@ -0,0 +1,5 @@
+import styled from 'styled-components'
+
+export const AnswerTextFieldContainer = styled.div`
+  height: calc(100% - 90px);
+`
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/SingleChoiceAlternatives.tsx b/client/src/pages/presentationEditor/components/slideSettingsComponents/SingleChoiceAlternatives.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..6e38c39fe443b5c1e78810cb5731690870f8f614
--- /dev/null
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SingleChoiceAlternatives.tsx
@@ -0,0 +1,131 @@
+import { ListItem, ListItemText } from '@material-ui/core'
+import CloseIcon from '@material-ui/icons/Close'
+import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonCheckedOutlined'
+import RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUncheckedOutlined'
+import axios from 'axios'
+import React from 'react'
+import { getEditorCompetition } from '../../../../actions/editor'
+import { useAppDispatch, useAppSelector } from '../../../../hooks'
+import { QuestionAlternative } from '../../../../interfaces/ApiModels'
+import { RichSlide } from '../../../../interfaces/ApiRichModels'
+import { AddButton, AlternativeTextField, Center, Clickable, SettingsList } from '../styled'
+
+type SingleChoiceAlternativeProps = {
+  activeSlide: RichSlide
+  competitionId: string
+}
+
+const SingleChoiceAlternatives = ({ activeSlide, competitionId }: SingleChoiceAlternativeProps) => {
+  const dispatch = useAppDispatch()
+  const activeSlideId = useAppSelector((state) => state.editor.activeSlideId)
+
+  const updateAlternativeValue = async (alternative: QuestionAlternative) => {
+    if (activeSlide && activeSlide.questions[0]) {
+      // Remove check from previously checked alternative
+      const previousCheckedAltId = activeSlide.questions[0].alternatives.find((alt) => alt.value === 1)?.id
+      if (previousCheckedAltId !== alternative.id) {
+        if (previousCheckedAltId) {
+          axios.put(
+            `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${previousCheckedAltId}`,
+            { value: 0 }
+          )
+        }
+        // Set new checked alternative
+        await axios
+          .put(
+            `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative.id}`,
+            { value: 1 }
+          )
+          .then(() => {
+            dispatch(getEditorCompetition(competitionId))
+          })
+          .catch(console.log)
+      }
+    }
+  }
+
+  const updateAlternativeText = async (alternative_id: number, newText: string) => {
+    if (activeSlide && activeSlide.questions[0]) {
+      await axios
+        .put(
+          `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`,
+          { text: newText }
+        )
+        .then(() => {
+          dispatch(getEditorCompetition(competitionId))
+        })
+        .catch(console.log)
+    }
+  }
+
+  const addAlternative = async () => {
+    if (activeSlide && activeSlide.questions[0]) {
+      await axios
+        .post(
+          `/api/competitions/${competitionId}/slides/${activeSlide?.id}/questions/${activeSlide?.questions[0].id}/alternatives`,
+          { text: '', value: 0 }
+        )
+        .then(() => {
+          dispatch(getEditorCompetition(competitionId))
+        })
+        .catch(console.log)
+    }
+  }
+
+  const handleCloseAnswerClick = async (alternative_id: number) => {
+    if (activeSlide && activeSlide.questions[0]) {
+      await axios
+        .delete(
+          `/api/competitions/${competitionId}/slides/${activeSlideId}/questions/${activeSlide?.questions[0].id}/alternatives/${alternative_id}`
+        )
+        .then(() => {
+          dispatch(getEditorCompetition(competitionId))
+        })
+        .catch(console.log)
+    }
+  }
+
+  const renderRadioButton = (alt: QuestionAlternative) => {
+    if (alt.value) return <RadioButtonCheckedIcon onClick={() => updateAlternativeValue(alt)} />
+    else return <RadioButtonUncheckedIcon onClick={() => updateAlternativeValue(alt)} />
+  }
+
+  return (
+    <SettingsList>
+      <ListItem divider>
+        <Center>
+          <ListItemText
+            primary="Svarsalternativ"
+            secondary="(Fyll i cirkeln höger om textfältet för att markera korrekt svar)"
+          />
+        </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"
+              />
+              <Clickable>{renderRadioButton(alt)}</Clickable>
+              <Clickable>
+                <CloseIcon onClick={() => handleCloseAnswerClick(alt.id)} />
+              </Clickable>
+            </ListItem>
+          </div>
+        ))}
+      <ListItem button onClick={addAlternative}>
+        <Center>
+          <AddButton variant="button">Lägg till svarsalternativ</AddButton>
+        </Center>
+      </ListItem>
+    </SettingsList>
+  )
+}
+
+export default SingleChoiceAlternatives
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 bc251b91e092c05457db1b64bb8bbe566d76dd55..8fbae43816a55a2be1f1dcc838afffd9f3cf0b9e 100644
--- a/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx
+++ b/client/src/pages/presentationEditor/components/slideSettingsComponents/SlideType.tsx
@@ -15,7 +15,7 @@ import {
 import axios from 'axios'
 import React, { useState } from 'react'
 import { getEditorCompetition } from '../../../../actions/editor'
-import { useAppDispatch } from '../../../../hooks'
+import { useAppDispatch, useAppSelector } from '../../../../hooks'
 import { RichSlide } from '../../../../interfaces/ApiRichModels'
 import { Center, FirstItem } from '../styled'
 
@@ -30,6 +30,11 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
   // For "slide type" dialog
   const [selectedSlideType, setSelectedSlideType] = useState(0)
   const [slideTypeDialog, setSlideTypeDialog] = useState(false)
+  const components = useAppSelector(
+    (state) => state.editor.competition.slides.find((slide) => slide.id === state.editor.activeSlideId)?.components
+  )
+  const questionComponentId = components?.find((qCompId) => qCompId.type_id === 3)?.id
+
   const openSlideTypeDialog = (type_id: number) => {
     setSelectedSlideType(type_id)
     setSlideTypeDialog(true)
@@ -41,7 +46,8 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
   const updateSlideType = async () => {
     closeSlideTypeDialog()
     if (activeSlide) {
-      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
@@ -67,6 +73,7 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
             })
             .then(() => {
               dispatch(getEditorCompetition(competitionId))
+              createQuestionComponent()
             })
             .catch(console.log)
         }
@@ -80,17 +87,44 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
           })
           .then(() => {
             dispatch(getEditorCompetition(competitionId))
+            createQuestionComponent()
           })
           .catch(console.log)
       }
     }
   }
+
+  const createQuestionComponent = () => {
+    axios
+      .post(`/api/competitions/${competitionId}/slides/${activeSlide.id}/components`, {
+        x: 0,
+        y: 0,
+        w: 400,
+        h: 250,
+        type_id: 3,
+        view_type_id: 1,
+        question_id: activeSlide.questions[0].id,
+      })
+      .then(() => {
+        dispatch(getEditorCompetition(competitionId))
+      })
+      .catch(console.log)
+  }
+
+  const deleteQuestionComponent = (componentId: number | undefined) => {
+    if (componentId) {
+      axios
+        .delete(`/api/competitions/${competitionId}/slides/${activeSlide.id}/components/${componentId}`)
+        .catch(console.log)
+    }
+  }
+
   return (
     <FirstItem>
       <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
@@ -108,7 +142,12 @@ const SlideType = ({ activeSlide, competitionId }: SlideTypeProps) => {
             </MenuItem>
             <MenuItem value={3}>
               <Typography variant="button" onClick={() => openSlideTypeDialog(3)}>
-                Flervalsfråga
+                Kryssfråga
+              </Typography>
+            </MenuItem>
+            <MenuItem value={4}>
+              <Typography variant="button" onClick={() => openSlideTypeDialog(4)}>
+                Alternativfråga
               </Typography>
             </MenuItem>
           </Select>
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/presentationEditor/components/styled.tsx b/client/src/pages/presentationEditor/components/styled.tsx
index 605972d2c4ee798388f9437f373cbabeab777d3c..31e40d51de8a4ecb45fbddf1cda9b7e96d4151f5 100644
--- a/client/src/pages/presentationEditor/components/styled.tsx
+++ b/client/src/pages/presentationEditor/components/styled.tsx
@@ -1,16 +1,4 @@
-import {
-  FormControl,
-  List,
-  Tab,
-  TextField,
-  Typography,
-  Button,
-  Card,
-  ListItem,
-  Select,
-  InputLabel,
-  ListItemText,
-} from '@material-ui/core'
+import { Button, Card, List, ListItemText, Tab, TextField, Typography } from '@material-ui/core'
 import styled from 'styled-components'
 
 export const SettingsTab = styled(Tab)`
@@ -148,3 +136,7 @@ export const HoverContainer = styled.div<HoverContainerProps>`
 export const ImageNameText = styled(ListItemText)`
   word-break: break-all;
 `
+
+export const QuestionComponent = styled.div`
+  outline-style: double;
+`
diff --git a/client/src/pages/views/OperatorViewPage.tsx b/client/src/pages/views/OperatorViewPage.tsx
index 5b75d2d2f505f05d18e797d8ef039a9a06b2f067..36aa64d23d16363e2f842a3958bdc30d309faaa1 100644
--- a/client/src/pages/views/OperatorViewPage.tsx
+++ b/client/src/pages/views/OperatorViewPage.tsx
@@ -27,6 +27,7 @@ import axios from 'axios'
 import React, { useEffect } from 'react'
 import { useHistory } from 'react-router-dom'
 import { useAppSelector } from '../../hooks'
+import { RichTeam } from '../../interfaces/ApiRichModels'
 import {
   socketConnect,
   socketEndPresentation,
@@ -59,7 +60,6 @@ import {
  *  TODO:
  *  - Instead of copying code for others to join the competition, copy URL.
  *
- *  - Make code popup less code by using .map instead
  *
  *  - Fix scoreboard
  *
@@ -113,14 +113,13 @@ const OperatorViewPage: React.FC = () => {
 
   useEffect(() => {
     socketConnect()
-    socketSetSlide // Behövs denna?
+    socketSetSlide
     handleOpenCodes()
-    setTimeout(startCompetition, 1000) // Ghetto, wait for everything to load
-    // console.log(id)
+    setTimeout(startCompetition, 1000) // Wait for socket to connect
   }, [])
 
+  /** Handles the browsers back button and if pressed cancels the ongoing competition */
   window.onpopstate = () => {
-    //Handle browser back arrow
     alert('Tävlingen avslutas för alla')
     endCompetition()
   }
@@ -136,11 +135,12 @@ const OperatorViewPage: React.FC = () => {
   }
 
   const startCompetition = () => {
-    socketStartPresentation()
+    socketStartPresentation() // Calls the socket to start competition
     console.log('started competition for')
     console.log(competitionId)
   }
 
+  /** Making sure the user wants to exit the competition by displaying a dialog box */
   const handleVerifyExit = () => {
     setOpen(true)
   }
@@ -155,7 +155,7 @@ const OperatorViewPage: React.FC = () => {
     setOpen(false)
     socketEndPresentation()
     history.push('/admin/tävlingshanterare')
-    window.location.reload(false) // TODO: fix this ugly hack, we "need" to refresh site to be able to run the competition correctly again
+    window.location.reload(false) // TODO: fix this, we "need" to refresh site to be able to run the competition correctly again
   }
 
   const getCodes = async () => {
@@ -205,6 +205,15 @@ const OperatorViewPage: React.FC = () => {
     return typeName
   }
 
+  /** Sums the scores for the teams. */
+  const addScore = (team: RichTeam) => {
+    let totalScore = 0
+    for (let j = 0; j < team.question_answers.length; j++) {
+      totalScore = totalScore + team.question_answers[j].score
+    }
+    return totalScore
+  }
+
   return (
     <OperatorContainer>
       <Dialog
@@ -296,31 +305,6 @@ const OperatorViewPage: React.FC = () => {
             </OperatorButton>
           </Tooltip>
 
-          {/* 
-          // Manual start button
-          <Tooltip title="Start Presentation" arrow>
-            <OperatorButton onClick={startCompetition} variant="contained">
-              start
-            </OperatorButton>
-          </Tooltip>
-
-          
-          // This creates a join button, but Operator should not join others, others should join Operator
-          <Tooltip title="Join Presentation" arrow>
-            <OperatorButton onClick={socketJoinPresentation} variant="contained">
-              <GroupAddIcon fontSize="large" />
-            </OperatorButton>
-          </Tooltip>
-          
-
-          // This creates another end button, it might not be needed since we already have one
-          <Tooltip title="End Presentation" arrow>
-            <OperatorButton onClick={socketEndPresentation} variant="contained">
-              <CancelIcon fontSize="large" />
-            </OperatorButton>
-          </Tooltip>
-          */}
-
           <Tooltip title="Starta Timer" arrow>
             <OperatorButton onClick={socketStartTimer} variant="contained">
               <TimerIcon fontSize="large" />
@@ -364,7 +348,7 @@ const OperatorViewPage: React.FC = () => {
           {teams &&
             teams.map((team) => (
               <ListItem key={team.id}>
-                {team.name} score: {'666'}
+                {team.name} score:{addScore(team)}
               </ListItem>
             ))}
         </List>
diff --git a/client/src/pages/views/components/PresentationComponent.tsx b/client/src/pages/views/components/PresentationComponent.tsx
index a41f7912469256a6e946522790f4f8203f8da60f..0a688dc1c1bec3373cbf7a4e782b57fdab58d411 100644
--- a/client/src/pages/views/components/PresentationComponent.tsx
+++ b/client/src/pages/views/components/PresentationComponent.tsx
@@ -1,21 +1,17 @@
-import { Typography } from '@material-ui/core'
 import React from 'react'
 import { Rnd } from 'react-rnd'
 import { ComponentTypes } from '../../../enum/ComponentTypes'
-import { useAppSelector } from '../../../hooks'
 import { Component, ImageComponent, TextComponent } from '../../../interfaces/ApiModels'
 import ImageComponentDisplay from '../../presentationEditor/components/ImageComponentDisplay'
+import QuestionComponentDisplay from '../../presentationEditor/components/QuestionComponentDisplay'
 import TextComponentDisplay from '../../presentationEditor/components/TextComponentDisplay'
-import { SlideContainer } from './styled'
 
 type PresentationComponentProps = {
   component: Component
-  width: number
-  height: number
   scale: number
 }
 
-const PresentationComponent = ({ component, width, height, scale }: PresentationComponentProps) => {
+const PresentationComponent = ({ component, scale }: PresentationComponentProps) => {
   const renderInnerComponent = () => {
     switch (component.type_id) {
       case ComponentTypes.Text:
@@ -28,6 +24,8 @@ const PresentationComponent = ({ component, width, height, scale }: Presentation
             component={component as ImageComponent}
           />
         )
+      case ComponentTypes.Question:
+        return <QuestionComponentDisplay variant="presentation" />
       default:
         break
     }
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/citiesReducer.ts b/client/src/reducers/citiesReducer.ts
index 7f5555b3eb002559df3952dfbac31d178ffd6271..4fbc5a1cc559fe11994b41fed5073dde304472aa 100644
--- a/client/src/reducers/citiesReducer.ts
+++ b/client/src/reducers/citiesReducer.ts
@@ -2,11 +2,14 @@ import { AnyAction } from 'redux'
 import Types from '../actions/types'
 import { City } from '../interfaces/ApiModels'
 
+// Define a type for the city state
 interface CityState {
   cities: City[]
   total: number
   count: number
 }
+
+// Define initial values for the city state
 const initialState: CityState = {
   cities: [],
   total: 0,
diff --git a/client/src/reducers/competitionLoginReducer.ts b/client/src/reducers/competitionLoginReducer.ts
index 72e5e22ee81aa98b735f670a581ad0c39a810ea9..d06f8e3029f11298dfe905bfd71041d0a06bf8b9 100644
--- a/client/src/reducers/competitionLoginReducer.ts
+++ b/client/src/reducers/competitionLoginReducer.ts
@@ -1,16 +1,18 @@
 import { AnyAction } from 'redux'
 import Types from '../actions/types'
 
+// Define a type for the competition login data
 interface CompetitionLoginData {
   competition_id: number
   team_id: number | null
   view: string
 }
-
+// Define a type for UI error
 interface UIError {
   message: string
 }
 
+// Define a type for the competition login state
 interface CompetitionLoginState {
   loading: boolean
   errors: null | UIError
@@ -19,6 +21,7 @@ interface CompetitionLoginState {
   initialized: boolean
 }
 
+// Define the initial values for the competition login state
 const initialState: CompetitionLoginState = {
   loading: false,
   errors: null,
diff --git a/client/src/reducers/competitionsReducer.ts b/client/src/reducers/competitionsReducer.ts
index bb788da5439874f8f9963dfbb756a222012697e6..8a99b29bfcea7f10ab7e6b83393b76bc16756fa5 100644
--- a/client/src/reducers/competitionsReducer.ts
+++ b/client/src/reducers/competitionsReducer.ts
@@ -10,6 +10,7 @@ interface CompetitionState {
   filterParams: CompetitionFilterParams
 }
 
+// Define the initial values for the competition state
 const initialState: CompetitionState = {
   competitions: [],
   total: 0,
diff --git a/client/src/reducers/mediaReducer.ts b/client/src/reducers/mediaReducer.ts
deleted file mode 100644
index ad5f3b46547f2e9a20135537ff814b196ca22331..0000000000000000000000000000000000000000
--- a/client/src/reducers/mediaReducer.ts
+++ /dev/null
@@ -1,39 +0,0 @@
-import { AnyAction } from 'redux'
-import Types from '../actions/types'
-
-interface MediaState {
-  id: number
-  filename: string
-  mediatype_id: number
-  user_id: number
-}
-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/reducers/presentationReducer.ts b/client/src/reducers/presentationReducer.ts
index b0c4791e9ab1f0ef05c931caa1fc4e9fe8268917..578b0a279c824210892be87bd69e0f62b74d7c89 100644
--- a/client/src/reducers/presentationReducer.ts
+++ b/client/src/reducers/presentationReducer.ts
@@ -1,9 +1,10 @@
 import { AnyAction } from 'redux'
 import Types from '../actions/types'
-import { Slide, Team } from '../interfaces/ApiModels'
+import { Slide } from '../interfaces/ApiModels'
 import { Timer } from '../interfaces/Timer'
 import { RichCompetition } from './../interfaces/ApiRichModels'
 
+// Define a type for the presentation state
 interface PresentationState {
   competition: RichCompetition
   slide: Slide
@@ -11,6 +12,7 @@ interface PresentationState {
   timer: Timer
 }
 
+// Define the initial values for the presentation state
 const initialState: PresentationState = {
   competition: {
     name: '',
diff --git a/client/src/reducers/rolesReducer.ts b/client/src/reducers/rolesReducer.ts
index 5028ae04cb13a4b1bf44536cd42bf3f8935b268c..8fc1465ae10f50dbf25d52b0f013c72647f5321a 100644
--- a/client/src/reducers/rolesReducer.ts
+++ b/client/src/reducers/rolesReducer.ts
@@ -2,9 +2,12 @@ import { AnyAction } from 'redux'
 import Types from '../actions/types'
 import { Role } from '../interfaces/ApiModels'
 
+// Define a type for the role state
 interface RoleState {
   roles: Role[]
 }
+
+// Define the initial values for the role state
 const initialState: RoleState = {
   roles: [],
 }
diff --git a/client/src/reducers/searchUserReducer.ts b/client/src/reducers/searchUserReducer.ts
index e0c1250683ae273318a4bcd6e4a3f5ae5f6324bd..38269dfb604ddd5072293f8c101088012ce8f7f5 100644
--- a/client/src/reducers/searchUserReducer.ts
+++ b/client/src/reducers/searchUserReducer.ts
@@ -3,6 +3,7 @@ import Types from '../actions/types'
 import { User } from '../interfaces/ApiModels'
 import { UserFilterParams } from '../interfaces/FilterParams'
 
+// Define a type for the search user state
 interface SearchUserState {
   users: User[]
   total: number
@@ -10,6 +11,7 @@ interface SearchUserState {
   filterParams: UserFilterParams
 }
 
+// Define the initial values for the search user state
 const initialState: SearchUserState = {
   users: [],
   total: 0,
diff --git a/client/src/reducers/statisticsReducer.ts b/client/src/reducers/statisticsReducer.ts
index 78a06e1157f6428c10ea17073afbdf46cf8609e5..bc957ccb97f2fde20eca5d44bf818650a7a01eb7 100644
--- a/client/src/reducers/statisticsReducer.ts
+++ b/client/src/reducers/statisticsReducer.ts
@@ -1,12 +1,14 @@
 import { AnyAction } from 'redux'
 import Types from '../actions/types'
 
+// Define a type for the statistics state
 interface StatisticsState {
   users: number
   competitions: number
   regions: number
 }
 
+// Define the initial values for the statistics state
 const initialState: StatisticsState = {
   users: 0,
   competitions: 0,
diff --git a/client/src/reducers/typesReducer.ts b/client/src/reducers/typesReducer.ts
index 3540ef86fbd4a921738d896b2b0bebb14b3216e0..10ea1c63b3f9f8f2b47f997d1036f71bc51450ac 100644
--- a/client/src/reducers/typesReducer.ts
+++ b/client/src/reducers/typesReducer.ts
@@ -2,12 +2,14 @@ import { AnyAction } from 'redux'
 import Types from '../actions/types'
 import { ComponentType, MediaType, QuestionType, ViewType } from '../interfaces/ApiModels'
 
+// Define a type for the Types state
 interface TypesState {
   componentTypes: ComponentType[]
   viewTypes: ViewType[]
   questionTypes: QuestionType[]
   mediaTypes: MediaType[]
 }
+// Define the initial values for the types state
 const initialState: TypesState = {
   componentTypes: [],
   viewTypes: [],
diff --git a/client/src/reducers/uiReducer.ts b/client/src/reducers/uiReducer.ts
index 4d06d1e298bab88cbfe2f41a54284a2557660a33..350f7b8e8aaa08752252343a95f4703770d2bb77 100644
--- a/client/src/reducers/uiReducer.ts
+++ b/client/src/reducers/uiReducer.ts
@@ -1,15 +1,18 @@
 import { AnyAction } from 'redux'
 import Types from '../actions/types'
 
+// Define a type for the UI error
 interface UIError {
   message: string
 }
 
+// Define a type for the UI state
 interface UIState {
   loading: boolean
   errors: null | UIError
 }
 
+// Define the initial values for the UI state
 const initialState: UIState = {
   loading: false,
   errors: null,
diff --git a/client/src/reducers/userReducer.ts b/client/src/reducers/userReducer.ts
index 91c056d3f0a55383fed0a0e40d0e94240d14a947..6b4f985b43a80a8f2cd91213a6b4c938f6543560 100644
--- a/client/src/reducers/userReducer.ts
+++ b/client/src/reducers/userReducer.ts
@@ -1,6 +1,7 @@
 import { AnyAction } from 'redux'
 import Types from '../actions/types'
 
+// Define a type for users info
 interface UserInfo {
   name: string
   email: string
@@ -9,12 +10,14 @@ interface UserInfo {
   id: number
 }
 
+// Define a type for the users state
 interface UserState {
   authenticated: boolean
   userInfo: UserInfo | null
   loading: boolean
 }
 
+// Define the initial values for the users state
 const initialState: UserState = {
   authenticated: false,
   loading: false,
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)
+  }
+})
diff --git a/client/src/utils/renderSlideIcon.tsx b/client/src/utils/renderSlideIcon.tsx
index ba1eafcb8052469dd8b627b95d2eee518bfd5fe8..22f6405abbc8574ae17bf86dd8b745ba98e3b4cf 100644
--- a/client/src/utils/renderSlideIcon.tsx
+++ b/client/src/utils/renderSlideIcon.tsx
@@ -1,9 +1,10 @@
-import { RichSlide } from '../interfaces/ApiRichModels'
-import React from 'react'
 import BuildOutlinedIcon from '@material-ui/icons/BuildOutlined'
+import CheckBoxOutlinedIcon from '@material-ui/icons/CheckBoxOutlined'
 import CreateOutlinedIcon from '@material-ui/icons/CreateOutlined'
-import DnsOutlinedIcon from '@material-ui/icons/DnsOutlined'
 import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'
+import RadioButtonCheckedIcon from '@material-ui/icons/RadioButtonChecked'
+import React from 'react'
+import { RichSlide } from '../interfaces/ApiRichModels'
 
 export const renderSlideIcon = (slide: RichSlide) => {
   if (slide.questions && slide.questions[0] && slide.questions[0].type_id) {
@@ -13,7 +14,9 @@ export const renderSlideIcon = (slide: RichSlide) => {
       case 2:
         return <BuildOutlinedIcon /> // practical qustion
       case 3:
-        return <DnsOutlinedIcon /> // multiple choice question
+        return <CheckBoxOutlinedIcon /> // multiple choice question
+      case 4:
+        return <RadioButtonCheckedIcon /> // single choice question
     }
   } else {
     return <InfoOutlinedIcon /> // information slide
diff --git a/server/app/apis/alternatives.py b/server/app/apis/alternatives.py
index 94f6d6b1725106a3cf5994570e4d3d5829ba2dbe..1827b358769441fd5d4748fabacf4221573c3346 100644
--- a/server/app/apis/alternatives.py
+++ b/server/app/apis/alternatives.py
@@ -1,10 +1,14 @@
+"""
+All API calls concerning question alternatives.
+Default route: /api/competitions/<competition_id>/slides/<slide_id>/questions/<question_id>/alternatives
+"""
+
 import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import QuestionAlternativeDTO
-from flask_restx import Resource
-from flask_restx import reqparse
 from app.core.parsers import sentinel
+from flask_restx import Resource, reqparse
 
 api = QuestionAlternativeDTO.api
 schema = QuestionAlternativeDTO.schema
@@ -24,11 +28,22 @@ alternative_parser_edit.add_argument("value", type=int, default=sentinel, locati
 class QuestionAlternativeList(Resource):
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
     def get(self, competition_id, slide_id, question_id):
-        items = dbc.get.question_alternative_list(competition_id, slide_id, question_id)
+        """ Gets the all question alternatives to the specified question. """
+
+        items = dbc.get.question_alternative_list(
+            competition_id,
+            slide_id,
+            question_id,
+        )
         return list_response(list_schema.dump(items))
 
     @protect_route(allowed_roles=["*"])
     def post(self, competition_id, slide_id, question_id):
+        """
+        Posts a new question alternative to the specified
+        question using the provided arguments.
+        """
+
         args = alternative_parser_add.parse_args(strict=True)
         item = dbc.add.question_alternative(**args, question_id=question_id)
         return item_response(schema.dump(item))
@@ -39,18 +54,41 @@ class QuestionAlternativeList(Resource):
 class QuestionAlternatives(Resource):
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
     def get(self, competition_id, slide_id, question_id, alternative_id):
-        items = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id)
+        """ Gets the specified question alternative. """
+
+        items = dbc.get.question_alternative(
+            competition_id,
+            slide_id,
+            question_id,
+            alternative_id,
+        )
         return item_response(schema.dump(items))
 
     @protect_route(allowed_roles=["*"])
     def put(self, competition_id, slide_id, question_id, alternative_id):
+        """
+        Edits the specified question alternative using the provided arguments.
+        """
+
         args = alternative_parser_edit.parse_args(strict=True)
-        item = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id)
+        item = dbc.get.question_alternative(
+            competition_id,
+            slide_id,
+            question_id,
+            alternative_id,
+        )
         item = dbc.edit.default(item, **args)
         return item_response(schema.dump(item))
 
     @protect_route(allowed_roles=["*"])
     def delete(self, competition_id, slide_id, question_id, alternative_id):
-        item = dbc.get.question_alternative(competition_id, slide_id, question_id, alternative_id)
+        """ Deletes the specified question alternative. """
+
+        item = dbc.get.question_alternative(
+            competition_id,
+            slide_id,
+            question_id,
+            alternative_id,
+        )
         dbc.delete.default(item)
         return {}, codes.NO_CONTENT
diff --git a/server/app/apis/answers.py b/server/app/apis/answers.py
index 6d0490a9328af2b903522fbeefe5224dcc645e06..c1ccd68b94d8d86faa0c214b5248670db260a73d 100644
--- a/server/app/apis/answers.py
+++ b/server/app/apis/answers.py
@@ -1,9 +1,13 @@
+"""
+All API calls concerning question answers.
+Default route: /api/competitions/<competition_id>/teams/<team_id>/answers
+"""
+
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import QuestionAnswerDTO
-from flask_restx import Resource
-from flask_restx import reqparse
 from app.core.parsers import sentinel
+from flask_restx import Resource, reqparse
 
 api = QuestionAnswerDTO.api
 schema = QuestionAnswerDTO.schema
@@ -24,11 +28,18 @@ answer_parser_edit.add_argument("score", type=int, default=sentinel, location="j
 class QuestionAnswerList(Resource):
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
     def get(self, competition_id, team_id):
+        """ Gets all question answers that the specified team has given. """
+
         items = dbc.get.question_answer_list(competition_id, team_id)
         return list_response(list_schema.dump(items))
 
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
     def post(self, competition_id, team_id):
+        """
+        Posts a new question answer to the specified
+        question using the provided arguments.
+        """
+
         args = answer_parser_add.parse_args(strict=True)
         item = dbc.add.question_answer(**args, team_id=team_id)
         return item_response(schema.dump(item))
@@ -39,12 +50,19 @@ class QuestionAnswerList(Resource):
 class QuestionAnswers(Resource):
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
     def get(self, competition_id, team_id, answer_id):
+        """ Gets the specified question answer. """
+
         item = dbc.get.question_answer(competition_id, team_id, answer_id)
         return item_response(schema.dump(item))
 
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
     def put(self, competition_id, team_id, answer_id):
+        """ Edits the specified question answer with the provided arguments. """
+
         args = answer_parser_edit.parse_args(strict=True)
         item = dbc.get.question_answer(competition_id, team_id, answer_id)
         item = dbc.edit.default(item, **args)
         return item_response(schema.dump(item))
+
+    # No need to delete an answer. It only needs to be deleted
+    # together with the question or the team.
diff --git a/server/app/apis/auth.py b/server/app/apis/auth.py
index bf9eeefde781f3fcacfd4bdafade8829db24acaa..e6a5345bd0346eb9959a94bb7d28f3a9e15f0097 100644
--- a/server/app/apis/auth.py
+++ b/server/app/apis/auth.py
@@ -1,4 +1,9 @@
-from datetime import timedelta
+"""
+All API calls concerning question answers.
+Default route: /api/auth
+"""
+
+from datetime import datetime, timedelta
 
 import app.core.http_codes as codes
 import app.database.controller as dbc
@@ -6,7 +11,8 @@ from app.apis import item_response, protect_route, text_response
 from app.core import sockets
 from app.core.codes import verify_code
 from app.core.dto import AuthDTO
-from app.database.models import Whitelist
+from app.database.models import User, Whitelist
+from flask import current_app
 from flask_jwt_extended import create_access_token, get_jti, get_raw_jwt
 from flask_jwt_extended.utils import get_jti
 from flask_restx import Resource, inputs, reqparse
@@ -26,12 +32,19 @@ create_user_parser.add_argument("role_id", type=int, required=True, location="js
 login_code_parser = reqparse.RequestParser()
 login_code_parser.add_argument("code", type=str, required=True, location="json")
 
+USER_LOGIN_LOCKED_ATTEMPTS = current_app.config["USER_LOGIN_LOCKED_ATTEMPTS"]
+USER_LOGIN_LOCKED_EXPIRES = current_app.config["USER_LOGIN_LOCKED_EXPIRES"]
+
 
 def get_user_claims(item_user):
+    """ Gets user details for jwt-token. """
+
     return {"role": item_user.role.name, "city_id": item_user.city_id}
 
 
 def get_code_claims(item_code):
+    """ Gets code details for jwt-token. """
+
     return {
         "view": item_code.view_type.name,
         "competition_id": item_code.competition_id,
@@ -44,6 +57,8 @@ def get_code_claims(item_code):
 class AuthSignup(Resource):
     @protect_route(allowed_roles=["Admin"], allowed_views=["*"])
     def get(self):
+        """ Tests that the user is an admin. """
+
         return "ok"
 
 
@@ -51,12 +66,16 @@ class AuthSignup(Resource):
 class AuthSignup(Resource):
     @protect_route(allowed_roles=["Admin"])
     def post(self):
+        """ Creates a new user if the user does not already exist. """
+
         args = create_user_parser.parse_args(strict=True)
         email = args.get("email")
 
+        # Check if email is already used
         if dbc.get.user_exists(email):
             api.abort(codes.BAD_REQUEST, "User already exists")
 
+        # Add user
         item_user = dbc.add.user(**args)
         return item_response(schema.dump(item_user))
 
@@ -66,28 +85,65 @@ class AuthSignup(Resource):
 class AuthDelete(Resource):
     @protect_route(allowed_roles=["Admin"])
     def delete(self, user_id):
-        item_user = dbc.get.user(user_id)
+        """ Deletes the specified user and adds their token to the blacklist. """
+
+        item_user = dbc.get.one(User, user_id)
+
+        # Blacklist all the whitelisted tokens
+        # in use for the user that will be deleted
         dbc.delete.whitelist_to_blacklist(Whitelist.user_id == user_id)
-        dbc.delete.default(item_user)
 
+        # Delete user
+        dbc.delete.default(item_user)
         return text_response(f"User {user_id} deleted")
 
 
 @api.route("/login")
 class AuthLogin(Resource):
     def post(self):
+        """ Logs in the specified user and creates a jwt-token. """
+
         args = login_parser.parse_args(strict=True)
         email = args.get("email")
         password = args.get("password")
+
         item_user = dbc.get.user_by_email(email)
 
-        if not item_user or not item_user.is_correct_password(password):
+        # Login with unkown email
+        if not item_user:
             api.abort(codes.UNAUTHORIZED, "Invalid email or password")
 
+        # Login with existing email but with wrong password
+        if not item_user.is_correct_password(password):
+            # Increase the login attempts every time the user tries to login with wrong password
+            item_user.login_attempts += 1
+
+            # Lock the user out for some time
+            if item_user.login_attempts == USER_LOGIN_LOCKED_ATTEMPTS:
+                item_user.locked = datetime.now() + USER_LOGIN_LOCKED_EXPIRES
+
+            dbc.utils.commit()
+            api.abort(codes.UNAUTHORIZED, "Invalid email or password")
+
+        # Otherwise if login was successful but the user is locked
+        if item_user.locked:
+            # Check if locked is greater than now
+            if item_user.locked > datetime.now():
+                api.abort(codes.UNAUTHORIZED, f"Try again in {item_user.locked} hours.")
+            else:
+                item_user.locked = None
+
+        # If everything else was successful, set login_attempts to 0
+        item_user.login_attempts = 0
+        dbc.utils.commit()
+
+        # Create the jwt with user.id as the identifier
         access_token = create_access_token(item_user.id, user_claims=get_user_claims(item_user))
-        # refresh_token = create_refresh_token(item_user.id)
 
+        # Login response includes the id and jwt for the user
         response = {"id": item_user.id, "access_token": access_token}
+
+        # Whitelist the created jwt
         dbc.add.whitelist(get_jti(access_token), item_user.id)
         return response
 
@@ -95,9 +151,12 @@ class AuthLogin(Resource):
 @api.route("/login/code")
 class AuthLoginCode(Resource):
     def post(self):
+        """ Logs in using the provided competition code. """
+
         args = login_code_parser.parse_args()
         code = args["code"]
 
+        # Check so the code string is valid
         if not verify_code(code):
             api.abort(codes.UNAUTHORIZED, "Invalid code")
 
@@ -107,10 +166,12 @@ class AuthLoginCode(Resource):
             if item_code.competition_id not in sockets.presentations:
                 api.abort(codes.UNAUTHORIZED, "Competition not active")
 
+        # Create jwt that is only valid for 8 hours
         access_token = create_access_token(
             item_code.id, user_claims=get_code_claims(item_code), expires_delta=timedelta(hours=8)
         )
 
+        # Whitelist the created jwt
         dbc.add.whitelist(get_jti(access_token), competition_id=item_code.competition_id)
         response = {
             "competition_id": item_code.competition_id,
@@ -125,9 +186,16 @@ class AuthLoginCode(Resource):
 class AuthLogout(Resource):
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
     def post(self):
+        """ Logs out. """
+
         jti = get_raw_jwt()["jti"]
+
+        # Blacklist the token so the user cannot access the api anymore
         dbc.add.blacklist(jti)
+
+        # Remove the the token from the whitelist since it's blacklisted now
         Whitelist.query.filter(Whitelist.jti == jti).delete()
+
         dbc.utils.commit()
         return text_response("Logout")
 
diff --git a/server/app/apis/codes.py b/server/app/apis/codes.py
index 6409109ef875efc13a5c12040a100b28822ed6ae..45f9578280e82c306ff716a646505dbffd62be3b 100644
--- a/server/app/apis/codes.py
+++ b/server/app/apis/codes.py
@@ -1,3 +1,8 @@
+"""
+All API calls concerning competition codes.
+Default route: /api/competitions/<competition_id>/codes
+"""
+
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import CodeDTO
@@ -14,6 +19,8 @@ list_schema = CodeDTO.list_schema
 class CodesList(Resource):
     @protect_route(allowed_roles=["*"], allowed_views=["Operator"])
     def get(self, competition_id):
+        """ Gets the all competition codes. """
+
         items = dbc.get.code_list(competition_id)
         return list_response(list_schema.dump(items), len(items))
 
@@ -23,6 +30,8 @@ class CodesList(Resource):
 class CodesById(Resource):
     @protect_route(allowed_roles=["*"])
     def put(self, competition_id, code_id):
+        """ Generates a new competition code. """
+
         item = dbc.get.one(Code, code_id)
         item.code = dbc.utils.generate_unique_code()
         dbc.utils.commit_and_refresh(item)
diff --git a/server/app/apis/competitions.py b/server/app/apis/competitions.py
index bfd06fac35c811fe276787b3027cad5d0d1f99a9..2d381425c9e5d3f41bba347128aab7b659bf8885 100644
--- a/server/app/apis/competitions.py
+++ b/server/app/apis/competitions.py
@@ -1,10 +1,14 @@
+"""
+All API calls concerning competitions.
+Default route: /api/competitions
+"""
+
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import CompetitionDTO
-from app.database.models import Competition
-from flask_restx import Resource
-from flask_restx import reqparse
 from app.core.parsers import search_parser, sentinel
+from app.database.models import Competition
+from flask_restx import Resource, reqparse
 
 api = CompetitionDTO.api
 schema = CompetitionDTO.schema
@@ -32,6 +36,8 @@ competition_parser_search.add_argument("city_id", type=int, default=sentinel, lo
 class CompetitionsList(Resource):
     @protect_route(allowed_roles=["*"])
     def post(self):
+        """ Posts a new competition. """
+
         args = competition_parser_add.parse_args(strict=True)
 
         # Add competition
@@ -47,12 +53,16 @@ class CompetitionsList(Resource):
 class Competitions(Resource):
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
     def get(self, competition_id):
+        """ Gets the specified competition. """
+
         item = dbc.get.competition(competition_id)
 
         return item_response(rich_schema.dump(item))
 
     @protect_route(allowed_roles=["*"])
     def put(self, competition_id):
+        """ Edits the specified competition with the specified arguments. """
+
         args = competition_parser_edit.parse_args(strict=True)
         item = dbc.get.one(Competition, competition_id)
         item = dbc.edit.default(item, **args)
@@ -61,6 +71,8 @@ class Competitions(Resource):
 
     @protect_route(allowed_roles=["*"])
     def delete(self, competition_id):
+        """ Deletes the specified competition. """
+
         item = dbc.get.one(Competition, competition_id)
         dbc.delete.competition(item)
 
@@ -71,6 +83,8 @@ class Competitions(Resource):
 class CompetitionSearch(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self):
+        """ Finds a specific competition based on the provided arguments. """
+
         args = competition_parser_search.parse_args(strict=True)
         items, total = dbc.search.competition(**args)
         return list_response(list_schema.dump(items), total)
@@ -81,6 +95,8 @@ class CompetitionSearch(Resource):
 class SlidesOrder(Resource):
     @protect_route(allowed_roles=["*"])
     def post(self, competition_id):
+        """ Creates a deep copy of the specified competition. """
+
         item_competition = dbc.get.competition(competition_id)
 
         item_competition_copy = dbc.copy.competition(item_competition)
diff --git a/server/app/apis/components.py b/server/app/apis/components.py
index 38227a46d90fe2afb5ea4a2fc7924412661c5259..39e1932824f6e6ab103f773402bb1c3feb331e43 100644
--- a/server/app/apis/components.py
+++ b/server/app/apis/components.py
@@ -1,17 +1,21 @@
+"""
+All API calls concerning competitions.
+Default route: /api/competitions/<competition_id>/slides/<slide_id>/components
+"""
+
 import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import ComponentDTO
-from flask_restx import Resource
-from flask_restx import reqparse
 from app.core.parsers import sentinel
+from flask_restx import Resource, reqparse
 
 api = ComponentDTO.api
 schema = ComponentDTO.schema
 list_schema = ComponentDTO.list_schema
 
 component_parser_add = reqparse.RequestParser()
-component_parser_add.add_argument("x", type=str, default=0, location="json")
+component_parser_add.add_argument("x", type=int, default=0, location="json")
 component_parser_add.add_argument("y", type=int, default=0, location="json")
 component_parser_add.add_argument("w", type=int, default=1, location="json")
 component_parser_add.add_argument("h", type=int, default=1, location="json")
@@ -22,7 +26,7 @@ component_parser_add.add_argument("media_id", type=int, default=None, location="
 component_parser_add.add_argument("question_id", type=int, default=None, location="json")
 
 component_parser_edit = reqparse.RequestParser()
-component_parser_edit.add_argument("x", type=str, default=sentinel, location="json")
+component_parser_edit.add_argument("x", type=int, default=sentinel, location="json")
 component_parser_edit.add_argument("y", type=int, default=sentinel, location="json")
 component_parser_edit.add_argument("w", type=int, default=sentinel, location="json")
 component_parser_edit.add_argument("h", type=int, default=sentinel, location="json")
@@ -31,16 +35,39 @@ component_parser_edit.add_argument("media_id", type=int, default=sentinel, locat
 component_parser_edit.add_argument("question_id", type=int, default=sentinel, location="json")
 
 
+@api.route("")
+@api.param("competition_id, slide_id")
+class ComponentList(Resource):
+    @protect_route(allowed_roles=["*"], allowed_views=["*"])
+    def get(self, competition_id, slide_id):
+        """ Gets all components in the specified slide and competition. """
+
+        items = dbc.get.component_list(competition_id, slide_id)
+        return list_response(list_schema.dump(items))
+
+    @protect_route(allowed_roles=["*"])
+    def post(self, competition_id, slide_id):
+        """ Posts a new component to the specified slide. """
+
+        args = component_parser_add.parse_args()
+        item = dbc.add.component(slide_id=slide_id, **args)
+        return item_response(schema.dump(item))
+
+
 @api.route("/<component_id>")
 @api.param("competition_id, slide_id, component_id")
 class ComponentByID(Resource):
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
     def get(self, competition_id, slide_id, component_id):
+        """ Gets the specified component. """
+
         item = dbc.get.component(competition_id, slide_id, component_id)
         return item_response(schema.dump(item))
 
     @protect_route(allowed_roles=["*"])
     def put(self, competition_id, slide_id, component_id):
+        """ Edits the specified component using the provided arguments. """
+
         args = component_parser_edit.parse_args(strict=True)
         item = dbc.get.component(competition_id, slide_id, component_id)
         args_without_sentinel = {key: value for key, value in args.items() if value is not sentinel}
@@ -49,6 +76,8 @@ class ComponentByID(Resource):
 
     @protect_route(allowed_roles=["*"])
     def delete(self, competition_id, slide_id, component_id):
+        """ Deletes the specified component. """
+
         item = dbc.get.component(competition_id, slide_id, component_id)
         dbc.delete.component(item)
         return {}, codes.NO_CONTENT
@@ -59,21 +88,12 @@ class ComponentByID(Resource):
 class ComponentList(Resource):
     @protect_route(allowed_roles=["*"])
     def post(self, competition_id, slide_id, component_id, view_type_id):
-        item_component = dbc.get.component(competition_id, slide_id, component_id)
-        item = dbc.copy.component(item_component, slide_id, view_type_id)
-        return item_response(schema.dump(item))
-
-
-@api.route("")
-@api.param("competition_id, slide_id")
-class ComponentList(Resource):
-    @protect_route(allowed_roles=["*"], allowed_views=["*"])
-    def get(self, competition_id, slide_id):
-        items = dbc.get.component_list(competition_id, slide_id)
-        return list_response(list_schema.dump(items))
+        """ Creates a deep copy of the specified component. """
 
-    @protect_route(allowed_roles=["*"])
-    def post(self, competition_id, slide_id):
-        args = component_parser_add.parse_args()
-        item = dbc.add.component(slide_id=slide_id, **args)
+        item_component = dbc.get.component(
+            competition_id,
+            slide_id,
+            component_id,
+        )
+        item = dbc.copy.component(item_component, slide_id, view_type_id)
         return item_response(schema.dump(item))
diff --git a/server/app/apis/media.py b/server/app/apis/media.py
index 49d20608840e320ef3d73d9df848748e6da33617..c59589a60afb5944fdd45fea0b230187d8d8da8b 100644
--- a/server/app/apis/media.py
+++ b/server/app/apis/media.py
@@ -1,16 +1,20 @@
+"""
+All API calls concerning media.
+Default route: /api/media
+"""
+
+import app.core.files as files
 import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import MediaDTO
-from app.core.parsers import search_parser
+from app.core.parsers import search_parser, sentinel
 from app.database.models import Media
 from flask import request
 from flask_jwt_extended import get_jwt_identity
 from flask_restx import Resource
 from flask_uploads import UploadNotAllowed
 from sqlalchemy import exc
-import app.core.files as files
-from app.core.parsers import sentinel
 
 api = MediaDTO.api
 image_set = MediaDTO.image_set
@@ -25,12 +29,16 @@ media_parser_search.add_argument("filename", type=str, default=sentinel, locatio
 class ImageList(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self):
+        """ Gets a list of all images with the specified filename. """
+
         args = media_parser_search.parse_args(strict=True)
         items, total = dbc.search.image(**args)
         return list_response(list_schema.dump(items), total)
 
     @protect_route(allowed_roles=["*"])
     def post(self):
+        """ Posts the specified image. """
+
         if "image" not in request.files:
             api.abort(codes.BAD_REQUEST, "Missing image in request.files")
         try:
@@ -51,11 +59,15 @@ class ImageList(Resource):
 class ImageList(Resource):
     @protect_route(allowed_roles=["*"], allowed_views=["*"])
     def get(self, ID):
+        """ Gets the specified image. """
+
         item = dbc.get.one(Media, ID)
         return item_response(schema.dump(item))
 
     @protect_route(allowed_roles=["*"])
     def delete(self, ID):
+        """ Deletes the specified image. """
+
         item = dbc.get.one(Media, ID)
         try:
             files.delete_image_and_thumbnail(item.filename)
diff --git a/server/app/apis/misc.py b/server/app/apis/misc.py
index a9069f6dd916af90b764b6703f83bb227fbcc2a4..552a0ad0c4848c33a08bcb63ccc3e870acd6af48 100644
--- a/server/app/apis/misc.py
+++ b/server/app/apis/misc.py
@@ -1,10 +1,14 @@
+"""
+All misc API calls.
+Default route: /api/misc
+"""
+
 import app.database.controller as dbc
 from app.apis import list_response, protect_route
 from app.core import http_codes
 from app.core.dto import MiscDTO
 from app.database.models import City, Competition, ComponentType, MediaType, QuestionType, Role, User, ViewType
 from flask_restx import Resource, reqparse
-from flask_restx import reqparse
 
 api = MiscDTO.api
 
@@ -24,6 +28,8 @@ name_parser.add_argument("name", type=str, required=True, location="json")
 @api.route("/types")
 class TypesList(Resource):
     def get(self):
+        """ Gets a list of all types. """
+
         result = {}
         result["media_types"] = media_type_schema.dump(dbc.get.all(MediaType))
         result["component_types"] = component_type_schema.dump(dbc.get.all(ComponentType))
@@ -36,6 +42,8 @@ class TypesList(Resource):
 class RoleList(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self):
+        """ Gets a list of all roles. """
+
         items = dbc.get.all(Role)
         return list_response(role_schema.dump(items))
 
@@ -44,11 +52,15 @@ class RoleList(Resource):
 class CitiesList(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self):
+        """ Gets a list of all cities. """
+
         items = dbc.get.all(City)
         return list_response(city_schema.dump(items))
 
     @protect_route(allowed_roles=["Admin"])
     def post(self):
+        """ Posts the specified city. """
+
         args = name_parser.parse_args(strict=True)
         dbc.add.city(args["name"])
         items = dbc.get.all(City)
@@ -60,6 +72,8 @@ class CitiesList(Resource):
 class Cities(Resource):
     @protect_route(allowed_roles=["Admin"])
     def put(self, ID):
+        """ Edits the specified city with the provided arguments. """
+
         item = dbc.get.one(City, ID)
         args = name_parser.parse_args(strict=True)
         item.name = args["name"]
@@ -69,6 +83,8 @@ class Cities(Resource):
 
     @protect_route(allowed_roles=["Admin"])
     def delete(self, ID):
+        """ Deletes the specified city. """
+
         item = dbc.get.one(City, ID)
         dbc.delete.default(item)
         items = dbc.get.all(City)
@@ -79,6 +95,8 @@ class Cities(Resource):
 class Statistics(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self):
+        """ Gets statistics. """
+
         user_count = User.query.count()
         competition_count = Competition.query.count()
         region_count = City.query.count()
diff --git a/server/app/apis/questions.py b/server/app/apis/questions.py
index 94df2a36456baf6e8a492250944dc4156b5aa21f..6ba32382fa524180c76eef35bd02a68382e552c6 100644
--- a/server/app/apis/questions.py
+++ b/server/app/apis/questions.py
@@ -1,10 +1,14 @@
+"""
+All API calls concerning question answers.
+Default route: /api/competitions/<competition_id>
+"""
+
 import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import QuestionDTO
-from flask_restx import Resource
-from flask_restx import reqparse
 from app.core.parsers import sentinel
+from flask_restx import Resource, reqparse
 
 api = QuestionDTO.api
 schema = QuestionDTO.schema
@@ -28,6 +32,8 @@ question_parser_edit.add_argument("correcting_instructions", type=str, default=s
 class QuestionList(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self, competition_id):
+        """ Gets all questions in the specified competition. """
+
         items = dbc.get.question_list_for_competition(competition_id)
         return list_response(list_schema.dump(items))
 
@@ -37,11 +43,15 @@ class QuestionList(Resource):
 class QuestionListForSlide(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self, competition_id, slide_id):
+        """ Gets all questions in the specified competition and slide. """
+
         items = dbc.get.question_list(competition_id, slide_id)
         return list_response(list_schema.dump(items))
 
     @protect_route(allowed_roles=["*"])
     def post(self, competition_id, slide_id):
+        """ Posts a new question to the specified slide using the provided arguments. """
+
         args = question_parser_add.parse_args(strict=True)
         item = dbc.add.question(slide_id=slide_id, **args)
         return item_response(schema.dump(item))
@@ -52,11 +62,17 @@ class QuestionListForSlide(Resource):
 class QuestionById(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self, competition_id, slide_id, question_id):
+        """
+        Gets the specified question using the specified competition and slide.
+        """
+
         item_question = dbc.get.question(competition_id, slide_id, question_id)
         return item_response(schema.dump(item_question))
 
     @protect_route(allowed_roles=["*"])
     def put(self, competition_id, slide_id, question_id):
+        """ Edits the specified question with the provided arguments. """
+
         args = question_parser_edit.parse_args(strict=True)
 
         item_question = dbc.get.question(competition_id, slide_id, question_id)
@@ -66,6 +82,8 @@ class QuestionById(Resource):
 
     @protect_route(allowed_roles=["*"])
     def delete(self, competition_id, slide_id, question_id):
+        """ Deletes the specified question. """
+
         item_question = dbc.get.question(competition_id, slide_id, question_id)
         dbc.delete.question(item_question)
         return {}, codes.NO_CONTENT
diff --git a/server/app/apis/slides.py b/server/app/apis/slides.py
index e0edcf6de100dbeb27658f036713a9cbd631b876..7f322d5231f063eecd287248928d85f945469a25 100644
--- a/server/app/apis/slides.py
+++ b/server/app/apis/slides.py
@@ -1,3 +1,8 @@
+"""
+All API calls concerning question alternatives.
+Default route: /api/competitions/<competition_id>/slides
+"""
+
 import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
@@ -24,25 +29,33 @@ slide_parser_edit.add_argument("background_image_id", default=sentinel, type=int
 class SlidesList(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self, competition_id):
+        """ Gets all slides from the specified competition. """
+
         items = dbc.get.slide_list(competition_id)
         return list_response(list_schema.dump(items))
 
     @protect_route(allowed_roles=["*"])
     def post(self, competition_id):
+        """ Posts a new slide to the specified competition. """
+
         item_slide = dbc.add.slide(competition_id)
         return item_response(schema.dump(item_slide))
 
 
 @api.route("/<slide_id>")
-@api.param("competition_id,slide_id")
+@api.param("competition_id, slide_id")
 class Slides(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self, competition_id, slide_id):
+        """ Gets the specified slide. """
+
         item_slide = dbc.get.slide(competition_id, slide_id)
         return item_response(schema.dump(item_slide))
 
     @protect_route(allowed_roles=["*"])
     def put(self, competition_id, slide_id):
+        """ Edits the specified slide using the provided arguments. """
+
         args = slide_parser_edit.parse_args(strict=True)
 
         item_slide = dbc.get.slide(competition_id, slide_id)
@@ -52,6 +65,8 @@ class Slides(Resource):
 
     @protect_route(allowed_roles=["*"])
     def delete(self, competition_id, slide_id):
+        """ Deletes the specified slide. """
+
         item_slide = dbc.get.slide(competition_id, slide_id)
 
         dbc.delete.slide(item_slide)
@@ -59,10 +74,12 @@ class Slides(Resource):
 
 
 @api.route("/<slide_id>/order")
-@api.param("competition_id,slide_id")
+@api.param("competition_id, slide_id")
 class SlideOrder(Resource):
     @protect_route(allowed_roles=["*"])
     def put(self, competition_id, slide_id):
+        """ Edits the specified slide order using the provided arguments. """
+
         args = slide_parser_edit.parse_args(strict=True)
         new_order = args.get("order")
 
@@ -84,8 +101,9 @@ class SlideOrder(Resource):
 class SlideCopy(Resource):
     @protect_route(allowed_roles=["*"])
     def post(self, competition_id, slide_id):
-        item_slide = dbc.get.slide(competition_id, slide_id)
+        """ Creates a deep copy of the specified slide. """
 
+        item_slide = dbc.get.slide(competition_id, slide_id)
         item_slide_copy = dbc.copy.slide(item_slide)
 
         return item_response(schema.dump(item_slide_copy))
diff --git a/server/app/apis/teams.py b/server/app/apis/teams.py
index 71ca715d55d21de75f07db4241e5ef0bb14e1050..913deeb789340939c3dcb37f4a3edefbd7d06e95 100644
--- a/server/app/apis/teams.py
+++ b/server/app/apis/teams.py
@@ -1,9 +1,14 @@
+"""
+All API calls concerning question alternatives.
+Default route: /api/competitions/<competition_id>/teams
+"""
+
 import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import TeamDTO
-from flask_restx import Resource, reqparse
 from app.core.parsers import sentinel
+from flask_restx import Resource, reqparse
 
 api = TeamDTO.api
 schema = TeamDTO.schema
@@ -21,11 +26,15 @@ team_parser_edit.add_argument("name", type=str, default=sentinel, location="json
 class TeamsList(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self, competition_id):
+        """ Gets all teams to the specified competition. """
+
         items = dbc.get.team_list(competition_id)
         return list_response(list_schema.dump(items))
 
     @protect_route(allowed_roles=["*"])
     def post(self, competition_id):
+        """ Posts a new team to the specified competition. """
+
         args = team_parser_add.parse_args(strict=True)
         item_team = dbc.add.team(args["name"], competition_id)
         return item_response(schema.dump(item_team))
@@ -36,18 +45,15 @@ class TeamsList(Resource):
 class Teams(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self, competition_id, team_id):
+        """ Gets the specified team. """
+
         item = dbc.get.team(competition_id, team_id)
         return item_response(schema.dump(item))
 
-    @protect_route(allowed_roles=["*"])
-    def delete(self, competition_id, team_id):
-        item_team = dbc.get.team(competition_id, team_id)
-
-        dbc.delete.team(item_team)
-        return {}, codes.NO_CONTENT
-
     @protect_route(allowed_roles=["*"])
     def put(self, competition_id, team_id):
+        """ Edits the specified team using the provided arguments. """
+
         args = team_parser_edit.parse_args(strict=True)
         name = args.get("name")
 
@@ -55,3 +61,12 @@ class Teams(Resource):
 
         item_team = dbc.edit.default(item_team, name=name, competition_id=competition_id)
         return item_response(schema.dump(item_team))
+
+    @protect_route(allowed_roles=["*"])
+    def delete(self, competition_id, team_id):
+        """ Deletes the specified team. """
+
+        item_team = dbc.get.team(competition_id, team_id)
+
+        dbc.delete.team(item_team)
+        return {}, codes.NO_CONTENT
diff --git a/server/app/apis/users.py b/server/app/apis/users.py
index dc26ac5b64e9a4270215374de84f3c71cae66f9e..bf393a89abcc0f85d4885e8333afab1e0df8b56f 100644
--- a/server/app/apis/users.py
+++ b/server/app/apis/users.py
@@ -1,11 +1,16 @@
+"""
+All API calls concerning question alternatives.
+Default route: /api/users
+"""
+
 import app.core.http_codes as codes
 import app.database.controller as dbc
 from app.apis import item_response, list_response, protect_route
 from app.core.dto import UserDTO
-from flask_jwt_extended import get_jwt_identity
-from flask_restx import Resource
-from flask_restx import inputs, reqparse
 from app.core.parsers import search_parser, sentinel
+from app.database.models import User
+from flask_jwt_extended import get_jwt_identity
+from flask_restx import Resource, inputs, reqparse
 
 api = UserDTO.api
 schema = UserDTO.schema
@@ -25,13 +30,14 @@ user_search_parser.add_argument("role_id", type=int, default=sentinel, location=
 
 
 def _edit_user(item_user, args):
+    """ Edits a user using the provided arguments. """
+
     email = args.get("email")
     name = args.get("name")
 
     if email:
         if dbc.get.user_exists(email):
             api.abort(codes.BAD_REQUEST, "Email is already in use")
-
     if name:
         args["name"] = args["name"].title()
 
@@ -42,13 +48,17 @@ def _edit_user(item_user, args):
 class UsersList(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self):
-        item = dbc.get.user(get_jwt_identity())
+        """ Gets all users. """
+
+        item = dbc.get.one(User, get_jwt_identity())
         return item_response(schema.dump(item))
 
     @protect_route(allowed_roles=["*"])
     def put(self):
+        """ Posts a new user using the specified arguments. """
+
         args = user_parser_edit.parse_args(strict=True)
-        item = dbc.get.user(get_jwt_identity())
+        item = dbc.get.one(User, get_jwt_identity())
         item = _edit_user(item, args)
         return item_response(schema.dump(item))
 
@@ -58,13 +68,17 @@ class UsersList(Resource):
 class Users(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self, ID):
-        item = dbc.get.user(ID)
+        """ Gets the specified user. """
+
+        item = dbc.get.one(User, ID)
         return item_response(schema.dump(item))
 
     @protect_route(allowed_roles=["Admin"])
     def put(self, ID):
+        """ Edits the specified team using the provided arguments. """
+
         args = user_parser_edit.parse_args(strict=True)
-        item = dbc.get.user(ID)
+        item = dbc.get.one(User, ID)
         item = _edit_user(item, args)
         return item_response(schema.dump(item))
 
@@ -73,6 +87,8 @@ class Users(Resource):
 class UserSearch(Resource):
     @protect_route(allowed_roles=["*"])
     def get(self):
+        """ Finds a specific user based on the provided arguments. """
+
         args = user_search_parser.parse_args(strict=True)
         items, total = dbc.search.user(**args)
         return list_response(list_schema.dump(items), total)
diff --git a/server/app/database/controller/add.py b/server/app/database/controller/add.py
index 5f9aec28f9043523b1f41f1d3b07b56c619c760b..d0adf5a00e9bdbd610b4892837edc345e3dc20be 100644
--- a/server/app/database/controller/add.py
+++ b/server/app/database/controller/add.py
@@ -29,13 +29,12 @@ from app.database.models import (
     ViewType,
     Whitelist,
 )
+from app.database.types import ID_IMAGE_COMPONENT, ID_QUESTION_COMPONENT, ID_TEXT_COMPONENT
+from flask import current_app
 from flask.globals import current_app
 from flask_restx import abort
 from PIL import Image
 from sqlalchemy import exc
-from flask import current_app
-
-from app.database.types import ID_IMAGE_COMPONENT, ID_QUESTION_COMPONENT, ID_TEXT_COMPONENT
 
 
 def db_add(item):
@@ -50,25 +49,34 @@ def db_add(item):
     except (exc.SQLAlchemyError, exc.DBAPIError):
         db.session.rollback()
         # SQL errors such as item already exists
-        abort(codes.INTERNAL_SERVER_ERROR, f"Item of type {type(item)} could not be created")
+        abort(
+            codes.INTERNAL_SERVER_ERROR,
+            f"Item of type {type(item)} could not be created",
+        )
     except:
         db.session.rollback()
         # Catching other errors
-        abort(codes.INTERNAL_SERVER_ERROR, f"Something went wrong when creating {type(item)}")
+        abort(
+            codes.INTERNAL_SERVER_ERROR,
+            f"Something went wrong when creating {type(item)}",
+        )
 
     return item
 
 
 def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, **data):
     """
-    Adds a component to the slide at the specified coordinates with the
-    provided size and data .
+    Adds a component to the slide at the specified
+    coordinates with the provided size and data.
     """
 
     if type_id == 2:  # 2 is image
         item_image = get.one(Media, data["media_id"])
         filename = item_image.filename
-        path = os.path.join(current_app.config["UPLOADED_PHOTOS_DEST"], filename)
+        path = os.path.join(
+            current_app.config["UPLOADED_PHOTOS_DEST"],
+            filename,
+        )
         with Image.open(path) as im:
             h = im.height
             w = im.width
@@ -80,13 +88,19 @@ def component(type_id, slide_id, view_type_id, x=0, y=0, w=0, h=0, **data):
         h *= ratio
 
     if type_id == ID_TEXT_COMPONENT:
-        item = db_add(TextComponent(slide_id, type_id, view_type_id, x, y, w, h))
+        item = db_add(
+            TextComponent(slide_id, type_id, view_type_id, x, y, w, h),
+        )
         item.text = data.get("text")
     elif type_id == ID_IMAGE_COMPONENT:
-        item = db_add(ImageComponent(slide_id, type_id, view_type_id, x, y, w, h))
+        item = db_add(
+            ImageComponent(slide_id, type_id, view_type_id, x, y, w, h),
+        )
         item.media_id = data.get("media_id")
     elif type_id == ID_QUESTION_COMPONENT:
-        item = db_add(QuestionComponent(slide_id, type_id, view_type_id, x, y, w, h))
+        item = db_add(
+            QuestionComponent(slide_id, type_id, view_type_id, x, y, w, h),
+        )
         item.question_id = data.get("question_id")
     else:
         abort(codes.BAD_REQUEST, f"Invalid type_id{type_id}")
@@ -123,7 +137,7 @@ def slide(competition_id):
     item_slide = db_add(Slide(order, competition_id))
 
     # Add default question
-    question(f"Fråga {item_slide.order + 1}", 10, 0, item_slide.id)
+    question(f"Fråga {item_slide.order + 1}", 10, 1, item_slide.id)
 
     item_slide = utils.refresh(item_slide)
     return item_slide
@@ -259,8 +273,18 @@ def question(name, total_score, type_id, slide_id, correcting_instructions=None)
 
 
 def question_alternative(text, value, question_id):
+    """
+    Adds a question alternative to the specified
+    question using the provided arguments.
+    """
+
     return db_add(QuestionAlternative(text, value, question_id))
 
 
 def question_answer(answer, score, question_id, team_id):
+    """
+    Adds a question answer to the specified team
+    and question using the provided arguments.
+    """
+
     return db_add(QuestionAnswer(answer, score, question_id, team_id))
diff --git a/server/app/database/controller/copy.py b/server/app/database/controller/copy.py
index 48dab2db79c33cb1ca819a59f0e934ab23dcc191..dd8073342ecfb0460e54783e70e50c0e194d437a 100644
--- a/server/app/database/controller/copy.py
+++ b/server/app/database/controller/copy.py
@@ -8,7 +8,9 @@ from app.database.types import ID_IMAGE_COMPONENT, ID_QUESTION_COMPONENT, ID_TEX
 
 
 def _alternative(item_old, question_id):
-    """Internal function. Makes a copy of the provided question alternative"""
+    """
+    Internal function. Makes a copy of the provided question alternative.
+    """
 
     return add.question_alternative(item_old.text, item_old.value, question_id)
 
@@ -73,7 +75,7 @@ def component(item_component, slide_id_new, view_type_id):
 def slide(item_slide_old):
     """
     Deep copies a slide to the same competition.
-    Does not copy team, question answers.
+    Does not copy team and question answers.
     """
 
     item_competition = get.competition(item_slide_old.competition_id)
@@ -98,7 +100,6 @@ def slide_to_competition(item_slide_old, item_competition):
 
     for item_component in item_slide_old.components:
         _component(item_component, item_slide_new)
-
     for item_question in item_slide_old.questions:
         _question(item_question, item_slide_new.id)
 
@@ -123,7 +124,7 @@ def competition(item_competition_old):
         item_competition_old.city_id,
         item_competition_old.font,
     )
-    # TODO: Add background image
+
     item_competition_new.background_image_id = item_competition_old.background_image_id
 
     for item_slide in item_competition_old.slides:
diff --git a/server/app/database/controller/delete.py b/server/app/database/controller/delete.py
index b0b36fbaf79843eac05bd4340d0a633a61e325d0..65737cb69d3ace8af493d71d245805b8f4869446 100644
--- a/server/app/database/controller/delete.py
+++ b/server/app/database/controller/delete.py
@@ -11,19 +11,25 @@ from flask_restx import abort
 
 def default(item):
     """ Deletes item and commits. """
+
     try:
         db.session.delete(item)
         db.session.commit()
     except:
         db.session.rollback()
-        abort(codes.INTERNAL_SERVER_ERROR, f"Item of type {type(item)} could not be deleted")
+        abort(
+            codes.INTERNAL_SERVER_ERROR,
+            f"Item of type {type(item)} could not be deleted",
+        )
 
 
 def whitelist_to_blacklist(filters):
     """
-    Remove whitelist by condition(filters) and insert those into blacklist
-    Example: When delete user all whitelisted tokens for that user should be blacklisted
+    Remove whitelist by condition(filters) and insert those into blacklist.
+    Example: When delete user all whitelisted tokens for that user should
+             be blacklisted.
     """
+
     whitelist = Whitelist.query.filter(filters).all()
     for item in whitelist:
         dbc.add.blacklist(item.jti)
@@ -43,7 +49,6 @@ def _slide(item_slide):
 
     for item_question in item_slide.questions:
         question(item_question)
-
     for item_component in item_slide.components:
         default(item_component)
 
@@ -85,6 +90,7 @@ def question(item_question):
         question_answers(item_question_answer)
     for item_alternative in item_question.alternatives:
         alternatives(item_alternative)
+
     default(item_question)
 
 
diff --git a/server/app/database/controller/get.py b/server/app/database/controller/get.py
index b6701ca5579940c9e4545d5542f41cf4b5b431fc..abdb9e15d1d815d80a48d7e80256e6595c7d7620 100644
--- a/server/app/database/controller/get.py
+++ b/server/app/database/controller/get.py
@@ -2,7 +2,6 @@
 This file contains functionality to get data from the database.
 """
 
-from sqlalchemy.orm.util import with_polymorphic
 from app.core import db
 from app.core import http_codes as codes
 from app.database.models import (
@@ -18,11 +17,12 @@ from app.database.models import (
     TextComponent,
     User,
 )
-from sqlalchemy.orm import joinedload, subqueryload
+from sqlalchemy.orm import joinedload
+from sqlalchemy.orm.util import with_polymorphic
 
 
 def all(db_type):
-    """ Gets lazy db-item in the provided table. """
+    """ Gets a list of all lazy db-items in the provided table. """
 
     return db_type.query.all()
 
@@ -43,7 +43,10 @@ def code_by_code(code):
 
 
 def code_list(competition_id):
-    """ Gets a list of all code objects associated with a the provided competition. """
+    """
+    Gets a list of all code objects associated with the provided competition.
+    """
+
     # team_view_id = 1
     join_competition = Competition.id == Code.competition_id
     filters = Competition.id == competition_id
@@ -57,20 +60,18 @@ def user_exists(email):
     return User.query.filter(User.email == email).count() > 0
 
 
-def user(user_id):
-    """ Gets the user object associated with the provided id. """
-
-    return User.query.filter(User.id == user_id).first_extended()
-
-
 def user_by_email(email):
     """ Gets the user object associated with the provided email. """
+
     return User.query.filter(User.email == email).first_extended(error_code=codes.UNAUTHORIZED)
 
 
 ### Slides ###
 def slide(competition_id, slide_id):
-    """ Gets the slide object associated with the provided id and order. """
+    """
+    Gets the slide object associated with the provided competition and slide.
+    """
+
     join_competition = Competition.id == Slide.competition_id
     filters = (Competition.id == competition_id) & (Slide.id == slide_id)
 
@@ -78,7 +79,10 @@ def slide(competition_id, slide_id):
 
 
 def slide_list(competition_id):
-    """ Gets a list of all slide objects associated with a the provided competition. """
+    """
+    Gets a list of all slide objects associated with the provided competition.
+    """
+
     join_competition = Competition.id == Slide.competition_id
     filters = Competition.id == competition_id
 
@@ -91,15 +95,10 @@ def slide_count(competition_id):
     return Slide.query.filter(Slide.competition_id == competition_id).count()
 
 
-def slide_count(competition_id):
-    """ Gets the number of slides in the provided competition. """
-
-    return Slide.query.filter(Slide.competition_id == competition_id).count()
-
-
 ### Teams ###
 def team(competition_id, team_id):
-    """ Gets the team object associated with the provided id and competition id. """
+    """ Gets the team object associated with the competition and team. """
+
     join_competition = Competition.id == Team.competition_id
     filters = (Competition.id == competition_id) & (Team.id == team_id)
 
@@ -107,19 +106,22 @@ def team(competition_id, team_id):
 
 
 def team_list(competition_id):
-    """ Gets a list of all team objects associated with a the provided competition. """
+    """
+    Gets a list of all team objects associated with the provided competition.
+    """
 
     join_competition = Competition.id == Team.competition_id
     filters = Competition.id == competition_id
 
     return Team.query.join(Competition, join_competition).filter(filters).all()
 
-    return Team.query.join(Competition, join_competition).filter(filters).all()
-
 
 ### Questions ###
 def question(competition_id, slide_id, question_id):
-    """ Gets the question object associated with the provided id, slide order and competition id. """
+    """
+    Gets the question object associated with the
+    provided, competition, slide and question.
+    """
 
     join_competition = Competition.id == Slide.competition_id
     join_slide = Slide.id == Question.slide_id
@@ -129,7 +131,10 @@ def question(competition_id, slide_id, question_id):
 
 
 def question_list(competition_id, slide_id):
-    """ Gets a list of all question objects associated with a the provided competition and slide. """
+    """
+    Gets a list of all question objects associated
+    with the provided competition and slide.
+    """
 
     join_competition = Competition.id == Slide.competition_id
     join_slide = Slide.id == Question.slide_id
@@ -139,7 +144,10 @@ def question_list(competition_id, slide_id):
 
 
 def question_list_for_competition(competition_id):
-    """ Gets a list of all question objects associated with a the provided competition. """
+    """
+    Gets a list of all question objects associated
+    with the provided competition.
+    """
 
     join_competition = Competition.id == Slide.competition_id
     join_slide = Slide.id == Question.slide_id
@@ -149,8 +157,16 @@ def question_list_for_competition(competition_id):
 
 
 ### Question Alternative ###
-def question_alternative(competition_id, slide_id, question_id, alternative_id):
-    """ Get question alternative for a given question based on its competition and slide and ID. """
+def question_alternative(
+    competition_id,
+    slide_id,
+    question_id,
+    alternative_id,
+):
+    """
+    Get a question alternative for a given question
+    based on its competition, slide and question.
+    """
 
     join_competition = Competition.id == Slide.competition_id
     join_slide = Slide.id == Question.slide_id
@@ -172,7 +188,11 @@ def question_alternative(competition_id, slide_id, question_id, alternative_id):
 
 
 def question_alternative_list(competition_id, slide_id, question_id):
-    """ Get all question alternatives for a given question based on its competition and slide. """
+    """
+    Get a list of all question alternative objects for a
+    given question based on its competition and slide.
+    """
+
     join_competition = Competition.id == Slide.competition_id
     join_slide = Slide.id == Question.slide_id
     join_question = Question.id == QuestionAlternative.question_id
@@ -186,18 +206,13 @@ def question_alternative_list(competition_id, slide_id, question_id):
         .all()
     )
 
-    return (
-        QuestionAlternative.query.join(Competition, join_competition)
-        .join(Slide, join_slide)
-        .join(Question, join_question)
-        .filter(filters)
-        .all()
-    )
-
 
 ### Question Answers ###
 def question_answer(competition_id, team_id, answer_id):
-    """ Get question answer for a given team based on its competition and ID. """
+    """
+    Get question answer for a given team based on its competition.
+    """
+
     join_competition = Competition.id == Team.competition_id
     join_team = Team.id == QuestionAnswer.team_id
     filters = (Competition.id == competition_id) & (Team.id == team_id) & (QuestionAnswer.id == answer_id)
@@ -207,7 +222,10 @@ def question_answer(competition_id, team_id, answer_id):
 
 
 def question_answer_list(competition_id, team_id):
-    """ Get question answer for a given team based on its competition. """
+    """
+    Get a list of question answers for a given team based on its competition.
+    """
+
     join_competition = Competition.id == Team.competition_id
     join_team = Team.id == QuestionAnswer.team_id
     filters = (Competition.id == competition_id) & (Team.id == team_id)
@@ -216,7 +234,10 @@ def question_answer_list(competition_id, team_id):
 
 ### Components ###
 def component(competition_id, slide_id, component_id):
-    """ Gets a list of all component objects associated with a the provided competition id and slide order. """
+    """
+    Gets a component object associated with
+    the provided competition id and slide order.
+    """
 
     join_competition = Competition.id == Slide.competition_id
     join_slide = Slide.id == Component.slide_id
@@ -233,7 +254,10 @@ def component(competition_id, slide_id, component_id):
 
 
 def component_list(competition_id, slide_id):
-    """ Gets a list of all component objects associated with a the provided competition id and slide order. """
+    """
+    Gets a list of all component objects associated with
+    the provided competition and slide.
+    """
 
     join_competition = Competition.id == Slide.competition_id
     join_slide = Slide.id == Component.slide_id
@@ -243,7 +267,8 @@ def component_list(competition_id, slide_id):
 
 ### Competitions ###
 def competition(competition_id):
-    """ Get Competition and all it's sub-entities """
+    """ Get Competition and all it's sub-entities. """
+
     os1 = joinedload(Competition.slides).joinedload(Slide.components)
     os2 = joinedload(Competition.slides).joinedload(Slide.questions).joinedload(Question.alternatives)
     ot = joinedload(Competition.teams).joinedload(Team.question_answers)
diff --git a/server/app/database/controller/utils.py b/server/app/database/controller/utils.py
index 2d2e5701e62ffaf0db886f3fe4d12d799a77c756..70127a7302d91d3a725af4a7ae496dd3b1eba419 100644
--- a/server/app/database/controller/utils.py
+++ b/server/app/database/controller/utils.py
@@ -70,6 +70,7 @@ def generate_unique_code():
 
 def refresh(item):
     """ Refreshes the provided item. """
+
     try:
         db.session.refresh(item)
     except Exception as e:
@@ -79,7 +80,8 @@ def refresh(item):
 
 
 def commit():
-    """ Commits. """
+    """ Commits to the database. """
+
     try:
         db.session.commit()
     except Exception as e:
diff --git a/server/app/database/models.py b/server/app/database/models.py
index a59335d9f815a8a8b88d39cb9acb1697d8b824b8..97eb6097c2403cf4d32d01106e2ae906d9d788d5 100644
--- a/server/app/database/models.py
+++ b/server/app/database/models.py
@@ -5,9 +5,8 @@ each other.
 """
 
 from app.core import bcrypt, db
-from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
-
 from app.database.types import ID_IMAGE_COMPONENT, ID_QUESTION_COMPONENT, ID_TEXT_COMPONENT
+from sqlalchemy.ext.hybrid import hybrid_method, hybrid_property
 
 STRING_SIZE = 254
 
@@ -62,8 +61,9 @@ class User(db.Model):
     _password = db.Column(db.LargeBinary(60), nullable=False)
 
     authenticated = db.Column(db.Boolean, default=False)
-    # twoAuthConfirmed = db.Column(db.Boolean, default=True)
-    # twoAuthCode = db.Column(db.String(STRING_SIZE), nullable=True)
+
+    login_attempts = db.Column(db.Integer, nullable=False, default=0)
+    locked = db.Column(db.DateTime(timezone=True), nullable=True, default=None)
 
     role_id = db.Column(db.Integer, db.ForeignKey("role.id"), nullable=False)
     city_id = db.Column(db.Integer, db.ForeignKey("city.id"), nullable=False)
@@ -201,8 +201,8 @@ class QuestionAnswer(db.Model):
     question_id = db.Column(db.Integer, db.ForeignKey("question.id"), nullable=False)
     team_id = db.Column(db.Integer, db.ForeignKey("team.id"), nullable=False)
 
-    def __init__(self, data, score, question_id, team_id):
-        self.data = data
+    def __init__(self, answer, score, question_id, team_id):
+        self.answer = answer
         self.score = score
         self.question_id = question_id
         self.team_id = team_id
diff --git a/server/configmodule.py b/server/configmodule.py
index 93d21cbe84a5847ed4927a4d2b3d002b487d1f69..e38c4e04cf1b90ffff4edbe0cbcffffb347c5b4a 100644
--- a/server/configmodule.py
+++ b/server/configmodule.py
@@ -17,6 +17,8 @@ class Config:
     THUMBNAIL_SIZE = (120, 120)
     SECRET_KEY = os.urandom(24)
     SQLALCHEMY_ECHO = False
+    USER_LOGIN_LOCKED_ATTEMPTS = 12
+    USER_LOGIN_LOCKED_EXPIRES = timedelta(hours=3)
 
 
 class DevelopmentConfig(Config):
@@ -34,6 +36,8 @@ class DevelopmentConfig(Config):
 class TestingConfig(Config):
     TESTING = True
     SQLALCHEMY_DATABASE_URI = "sqlite:///test.db"
+    USER_LOGIN_LOCKED_ATTEMPTS = 4
+    USER_LOGIN_LOCKED_EXPIRES = timedelta(seconds=4)
 
 
 class ProductionConfig(Config):
diff --git a/server/populate.py b/server/populate.py
index 92183b6cc87f8f2e5377fb934c3ff14219920a7f..9981f0c02f430d56c88738a9b67e89d5fc17af89 100644
--- a/server/populate.py
+++ b/server/populate.py
@@ -11,8 +11,8 @@ from app.database.models import City, QuestionType, Role
 
 def _add_items():
     media_types = ["Image", "Video"]
-    question_types = ["Boolean", "Multiple", "Text"]
-    component_types = ["Text", "Image"]
+    question_types = ["Text", "Practical", "Multiple", "Single"]
+    component_types = ["Text", "Image", "Question"]
     view_types = ["Team", "Judge", "Audience", "Operator"]
 
     roles = ["Admin", "Editor"]
@@ -43,8 +43,8 @@ def _add_items():
     city_id = City.query.filter(City.name == "Linköping").one().id
 
     # Add users
-    dbc.add.user("admin@test.se", "password", admin_id, city_id)
-    dbc.add.user("test@test.se", "password", editor_id, city_id)
+    dbc.add.user("admin@test.se", "password", admin_id, city_id, "Admina Denfina")
+    dbc.add.user("test@test.se", "password", editor_id, city_id, "Test Osteron")
 
     question_types_items = dbc.get.all(QuestionType)
 
@@ -105,6 +105,14 @@ def _add_items():
         for name in teams:
             dbc.add.team(f"{name}{i}", item_comp.id)
 
+    # question_answer(answer, score, question_id, team_id)
+    dbc.add.question_answer("ett svar som ger 2p", 2, 1, 1)
+    dbc.add.question_answer("ett svar som ger 10p", 10, 2, 1)
+    dbc.add.question_answer("ett svar som ger 6p", 6, 3, 1)
+
+    dbc.add.question_answer("ett svar som ger 2p", 2, 1, 2)
+    dbc.add.question_answer("ett svar som ger 3p", 3, 1, 3)
+
 
 if __name__ == "__main__":
     app, _ = create_app("configmodule.DevelopmentConfig")
diff --git a/server/tests/test_app.py b/server/tests/test_app.py
index 2152a6d4c7236f57d099f50dccec3a090ce94c5d..880a41bd82a3b6cac8795a3cf61eaed6569dbadb 100644
--- a/server/tests/test_app.py
+++ b/server/tests/test_app.py
@@ -2,15 +2,37 @@
 This file tests the api function calls.
 """
 
+import time
+
 import app.core.http_codes as codes
-from app.database.controller.add import competition
-from app.database.models import Slide
+import pytest
 from app.core import sockets
 
 from tests import app, client, db
 from tests.test_helpers import add_default_values, change_order_test, delete, get, post, put
 
 
+# @pytest.mark.skip(reason="Takes long time")
+def test_locked_api(client):
+    add_default_values()
+
+    # Login in with default user but wrong password until blocked
+    for i in range(4):
+        response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password1"})
+        assert response.status_code == codes.UNAUTHORIZED
+
+    # Login with right password, user should be locked
+    response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
+    assert response.status_code == codes.UNAUTHORIZED
+
+    # Sleep for 4 secounds
+    time.sleep(4)
+
+    # Check so the user is no longer locked
+    response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password"})
+    assert response.status_code == codes.OK
+
+
 def test_misc_api(client):
     add_default_values()
 
@@ -125,6 +147,10 @@ def test_auth_and_user_api(client):
     assert response.status_code == codes.OK
     headers = {"Authorization": "Bearer " + body["access_token"]}
 
+    # Login in with default user but wrong password
+    response, body = post(client, "/api/auth/login", {"email": "test@test.se", "password": "password1"})
+    assert response.status_code == codes.UNAUTHORIZED
+
     # Create user
     register_data = {"email": "test1@test.se", "password": "abc123", "role_id": 2, "city_id": 1}
     response, body = post(client, "/api/auth/signup", register_data, headers)
@@ -211,7 +237,6 @@ def test_auth_and_user_api(client):
     assert response.status_code == codes.OK
 
     # TODO: Check if current users jwt (jti) is in blacklist after logging out
-
     response, body = get(client, "/api/users", headers=headers)
     assert response.status_code == codes.UNAUTHORIZED
 
@@ -479,4 +504,4 @@ def test_authorization(client):
 
     # Also get antoher teams answers
     response, body = get(client, f"/api/competitions/{competition_id}/teams/{team_id+1}/answers", headers=headers)
-    assert response.status_code == codes.OK
\ No newline at end of file
+    assert response.status_code == codes.OK
diff --git a/server/tests/test_helpers.py b/server/tests/test_helpers.py
index 4f46622a977acad828b192b0e8aa510851133f82..7d5625385f01f81cc326bca9d5114322ad5f52e0 100644
--- a/server/tests/test_helpers.py
+++ b/server/tests/test_helpers.py
@@ -49,8 +49,8 @@ def add_default_values():
     db.session.add(Code("111111", 1, item_competition.id, item_team1.id))  # Team
     db.session.add(Code("222222", 2, item_competition.id))  # Judge
 
-    dbc.add.QuestionAnswer("hej", 5, item_question.id, item_team1)
-    dbc.add.QuestionAnswer("då", 5, item_question.id, item_team2)
+    dbc.add.question_answer("hej", 5, item_question.id, item_team1.id)
+    dbc.add.question_answer("då", 5, item_question.id, item_team2.id)
 
     db.session.commit()