Index: frontend/node_modules/eslint-plugin-jest/lib/globals.json
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/globals.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/globals.json	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,15 @@
+{
+  "afterAll": false,
+  "afterEach": false,
+  "beforeAll": false,
+  "beforeEach": false,
+  "describe": false,
+  "expect": false,
+  "fit": false,
+  "it": false,
+  "jest": false,
+  "test": false,
+  "xdescribe": false,
+  "xit": false,
+  "xtest": false
+}
Index: frontend/node_modules/eslint-plugin-jest/lib/index.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/index.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,70 @@
+"use strict";
+
+var _fs = require("fs");
+
+var _path = require("path");
+
+var _globals = _interopRequireDefault(require("./globals.json"));
+
+var snapshotProcessor = _interopRequireWildcard(require("./processors/snapshot-processor"));
+
+function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function (nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); }
+
+function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; }
+
+function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
+
+// copied from https://github.com/babel/babel/blob/d8da63c929f2d28c401571e2a43166678c555bc4/packages/babel-helpers/src/helpers.js#L602-L606
+
+/* istanbul ignore next */
+const interopRequireDefault = obj => obj && obj.__esModule ? obj : {
+  default: obj
+};
+
+const importDefault = moduleName => // eslint-disable-next-line @typescript-eslint/no-require-imports
+interopRequireDefault(require(moduleName)).default;
+
+const rulesDir = (0, _path.join)(__dirname, 'rules');
+const excludedFiles = ['__tests__', 'detectJestVersion', 'utils'];
+const rules = (0, _fs.readdirSync)(rulesDir).map(rule => (0, _path.parse)(rule).name).filter(rule => !excludedFiles.includes(rule)).reduce((acc, curr) => ({ ...acc,
+  [curr]: importDefault((0, _path.join)(rulesDir, curr))
+}), {});
+const recommendedRules = Object.entries(rules).filter(([, rule]) => rule.meta.docs.recommended).reduce((acc, [name, rule]) => ({ ...acc,
+  [`jest/${name}`]: rule.meta.docs.recommended
+}), {});
+const allRules = Object.entries(rules).filter(([, rule]) => !rule.meta.deprecated).reduce((acc, [name]) => ({ ...acc,
+  [`jest/${name}`]: 'error'
+}), {});
+
+const createConfig = rules => ({
+  plugins: ['jest'],
+  env: {
+    'jest/globals': true
+  },
+  rules
+});
+
+module.exports = {
+  configs: {
+    all: createConfig(allRules),
+    recommended: createConfig(recommendedRules),
+    style: {
+      plugins: ['jest'],
+      rules: {
+        'jest/no-alias-methods': 'warn',
+        'jest/prefer-to-be': 'error',
+        'jest/prefer-to-contain': 'error',
+        'jest/prefer-to-have-length': 'error'
+      }
+    }
+  },
+  environments: {
+    globals: {
+      globals: _globals.default
+    }
+  },
+  processors: {
+    '.snap': snapshotProcessor
+  },
+  rules
+};
Index: frontend/node_modules/eslint-plugin-jest/lib/processors/snapshot-processor.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/processors/snapshot-processor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/processors/snapshot-processor.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,17 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.preprocess = exports.postprocess = void 0;
+
+// https://eslint.org/docs/developer-guide/working-with-plugins#processors-in-plugins
+// https://github.com/typescript-eslint/typescript-eslint/issues/808
+const preprocess = source => [source];
+
+exports.preprocess = preprocess;
+
+const postprocess = messages => // snapshot files should only be linted with snapshot specific rules
+messages[0].filter(message => message.ruleId === 'jest/no-large-snapshots');
+
+exports.postprocess = postprocess;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/consistent-test-it.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/consistent-test-it.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/consistent-test-it.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,119 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const buildFixer = (callee, nodeName, preferredTestKeyword) => fixer => [fixer.replaceText(callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression ? callee.object : callee, getPreferredNodeName(nodeName, preferredTestKeyword))];
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Have control over `test` and `it` usages',
+      recommended: false
+    },
+    fixable: 'code',
+    messages: {
+      consistentMethod: "Prefer using '{{ testKeyword }}' instead of '{{ oppositeTestKeyword }}'",
+      consistentMethodWithinDescribe: "Prefer using '{{ testKeywordWithinDescribe }}' instead of '{{ oppositeTestKeyword }}' within describe"
+    },
+    schema: [{
+      type: 'object',
+      properties: {
+        fn: {
+          enum: [_utils.TestCaseName.it, _utils.TestCaseName.test]
+        },
+        withinDescribe: {
+          enum: [_utils.TestCaseName.it, _utils.TestCaseName.test]
+        }
+      },
+      additionalProperties: false
+    }],
+    type: 'suggestion'
+  },
+  defaultOptions: [{
+    fn: _utils.TestCaseName.test,
+    withinDescribe: _utils.TestCaseName.it
+  }],
+
+  create(context) {
+    const configObj = context.options[0] || {};
+    const testKeyword = configObj.fn || _utils.TestCaseName.test;
+    const testKeywordWithinDescribe = configObj.withinDescribe || configObj.fn || _utils.TestCaseName.it;
+    let describeNestingLevel = 0;
+    return {
+      CallExpression(node) {
+        const nodeName = (0, _utils.getNodeName)(node.callee);
+
+        if (!nodeName) {
+          return;
+        }
+
+        if ((0, _utils.isDescribeCall)(node)) {
+          describeNestingLevel++;
+        }
+
+        const funcNode = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
+
+        if ((0, _utils.isTestCaseCall)(node) && describeNestingLevel === 0 && !nodeName.includes(testKeyword)) {
+          const oppositeTestKeyword = getOppositeTestKeyword(testKeyword);
+          context.report({
+            messageId: 'consistentMethod',
+            node: node.callee,
+            data: {
+              testKeyword,
+              oppositeTestKeyword
+            },
+            fix: buildFixer(funcNode, nodeName, testKeyword)
+          });
+        }
+
+        if ((0, _utils.isTestCaseCall)(node) && describeNestingLevel > 0 && !nodeName.includes(testKeywordWithinDescribe)) {
+          const oppositeTestKeyword = getOppositeTestKeyword(testKeywordWithinDescribe);
+          context.report({
+            messageId: 'consistentMethodWithinDescribe',
+            node: node.callee,
+            data: {
+              testKeywordWithinDescribe,
+              oppositeTestKeyword
+            },
+            fix: buildFixer(funcNode, nodeName, testKeywordWithinDescribe)
+          });
+        }
+      },
+
+      'CallExpression:exit'(node) {
+        if ((0, _utils.isDescribeCall)(node)) {
+          describeNestingLevel--;
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
+
+function getPreferredNodeName(nodeName, preferredTestKeyword) {
+  if (nodeName === _utils.TestCaseName.fit) {
+    return 'test.only';
+  }
+
+  return nodeName.startsWith('f') || nodeName.startsWith('x') ? nodeName.charAt(0) + preferredTestKeyword : preferredTestKeyword;
+}
+
+function getOppositeTestKeyword(test) {
+  if (test === _utils.TestCaseName.test) {
+    return _utils.TestCaseName.it;
+  }
+
+  return _utils.TestCaseName.test;
+}
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/detectJestVersion.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/detectJestVersion.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/detectJestVersion.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,29 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.detectJestVersion = void 0;
+let cachedJestVersion = null;
+
+const detectJestVersion = () => {
+  if (cachedJestVersion) {
+    return cachedJestVersion;
+  }
+
+  try {
+    const jestPath = require.resolve('jest/package.json');
+
+    const jestPackageJson = // eslint-disable-next-line @typescript-eslint/no-require-imports
+    require(jestPath);
+
+    if (jestPackageJson.version) {
+      const [majorVersion] = jestPackageJson.version.split('.');
+      return cachedJestVersion = parseInt(majorVersion, 10);
+    }
+  } catch {}
+
+  throw new Error('Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly');
+};
+
+exports.detectJestVersion = detectJestVersion;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/expect-expect.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/expect-expect.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/expect-expect.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,120 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+/*
+ * This implementation is adapted from eslint-plugin-jasmine.
+ * MIT license, Remco Haszing.
+ */
+
+/**
+ * Checks if node names returned by getNodeName matches any of the given star patterns
+ * Pattern examples:
+ *   request.*.expect
+ *   request.**.expect
+ *   request.**.expect*
+ */
+function matchesAssertFunctionName(nodeName, patterns) {
+  return patterns.some(p => new RegExp(`^${p.split('.').map(x => {
+    if (x === '**') return '[a-z\\.]*';
+    return x.replace(/\*/gu, '[a-z]*');
+  }).join('\\.')}(\\.|$)`, 'ui').test(nodeName));
+}
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Enforce assertion to be made in a test body',
+      recommended: 'warn'
+    },
+    messages: {
+      noAssertions: 'Test has no assertions'
+    },
+    schema: [{
+      type: 'object',
+      properties: {
+        assertFunctionNames: {
+          type: 'array',
+          items: [{
+            type: 'string'
+          }]
+        },
+        additionalTestBlockFunctions: {
+          type: 'array',
+          items: {
+            type: 'string'
+          }
+        }
+      },
+      additionalProperties: false
+    }],
+    type: 'suggestion'
+  },
+  defaultOptions: [{
+    assertFunctionNames: ['expect'],
+    additionalTestBlockFunctions: []
+  }],
+
+  create(context, [{
+    assertFunctionNames = ['expect'],
+    additionalTestBlockFunctions = []
+  }]) {
+    const unchecked = [];
+
+    function checkCallExpressionUsed(nodes) {
+      for (const node of nodes) {
+        const index = node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? unchecked.indexOf(node) : -1;
+
+        if (node.type === _experimentalUtils.AST_NODE_TYPES.FunctionDeclaration) {
+          const declaredVariables = context.getDeclaredVariables(node);
+          const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
+          checkCallExpressionUsed(testCallExpressions);
+        }
+
+        if (index !== -1) {
+          unchecked.splice(index, 1);
+          break;
+        }
+      }
+    }
+
+    return {
+      CallExpression(node) {
+        var _getNodeName;
+
+        const name = (_getNodeName = (0, _utils.getNodeName)(node.callee)) !== null && _getNodeName !== void 0 ? _getNodeName : '';
+
+        if ((0, _utils.isTestCaseCall)(node) || additionalTestBlockFunctions.includes(name)) {
+          if (node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property, 'todo')) {
+            return;
+          }
+
+          unchecked.push(node);
+        } else if (matchesAssertFunctionName(name, assertFunctionNames)) {
+          // Return early in case of nested `it` statements.
+          checkCallExpressionUsed(context.getAncestors());
+        }
+      },
+
+      'Program:exit'() {
+        unchecked.forEach(node => context.report({
+          messageId: 'noAssertions',
+          node
+        }));
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/max-nested-describe.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/max-nested-describe.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/max-nested-describe.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,87 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Enforces a maximum depth to nested describe calls',
+      recommended: false
+    },
+    messages: {
+      exceededMaxDepth: 'Too many nested describe calls ({{ depth }}). Maximum allowed is {{ max }}.'
+    },
+    type: 'suggestion',
+    schema: [{
+      type: 'object',
+      properties: {
+        max: {
+          type: 'integer',
+          minimum: 0
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+  defaultOptions: [{
+    max: 5
+  }],
+
+  create(context, [{
+    max
+  }]) {
+    const describeCallbackStack = [];
+
+    function pushDescribeCallback(node) {
+      const {
+        parent
+      } = node;
+
+      if ((parent === null || parent === void 0 ? void 0 : parent.type) !== _experimentalUtils.AST_NODE_TYPES.CallExpression || !(0, _utils.isDescribeCall)(parent)) {
+        return;
+      }
+
+      describeCallbackStack.push(0);
+
+      if (describeCallbackStack.length > max) {
+        context.report({
+          node: parent,
+          messageId: 'exceededMaxDepth',
+          data: {
+            depth: describeCallbackStack.length,
+            max
+          }
+        });
+      }
+    }
+
+    function popDescribeCallback(node) {
+      const {
+        parent
+      } = node;
+
+      if ((parent === null || parent === void 0 ? void 0 : parent.type) === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isDescribeCall)(parent)) {
+        describeCallbackStack.pop();
+      }
+    }
+
+    return {
+      FunctionExpression: pushDescribeCallback,
+      'FunctionExpression:exit': popDescribeCallback,
+      ArrowFunctionExpression: pushDescribeCallback,
+      'ArrowFunctionExpression:exit': popDescribeCallback
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-alias-methods.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-alias-methods.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-alias-methods.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,77 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow alias methods',
+      recommended: false
+    },
+    messages: {
+      replaceAlias: `Replace {{ alias }}() with its canonical name of {{ canonical }}()`
+    },
+    fixable: 'code',
+    type: 'suggestion',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    // map of jest matcher aliases & their canonical names
+    const methodNames = {
+      toBeCalled: 'toHaveBeenCalled',
+      toBeCalledTimes: 'toHaveBeenCalledTimes',
+      toBeCalledWith: 'toHaveBeenCalledWith',
+      lastCalledWith: 'toHaveBeenLastCalledWith',
+      nthCalledWith: 'toHaveBeenNthCalledWith',
+      toReturn: 'toHaveReturned',
+      toReturnTimes: 'toHaveReturnedTimes',
+      toReturnWith: 'toHaveReturnedWith',
+      lastReturnedWith: 'toHaveLastReturnedWith',
+      nthReturnedWith: 'toHaveNthReturnedWith',
+      toThrowError: 'toThrow'
+    };
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          matcher
+        } = (0, _utils.parseExpectCall)(node);
+
+        if (!matcher) {
+          return;
+        }
+
+        const alias = matcher.name;
+
+        if (alias in methodNames) {
+          const canonical = methodNames[alias];
+          context.report({
+            messageId: 'replaceAlias',
+            data: {
+              alias,
+              canonical
+            },
+            node: matcher.node.property,
+            fix: fixer => [fixer.replaceText(matcher.node.property, canonical)]
+          });
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-commented-out-tests.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-commented-out-tests.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-commented-out-tests.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,55 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+function hasTests(node) {
+  return /^\s*[xf]?(test|it|describe)(\.\w+|\[['"]\w+['"]\])?\s*\(/mu.test(node.value);
+}
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow commented out tests',
+      recommended: 'warn'
+    },
+    messages: {
+      commentedTests: 'Some tests seem to be commented'
+    },
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    const sourceCode = context.getSourceCode();
+
+    function checkNode(node) {
+      if (!hasTests(node)) {
+        return;
+      }
+
+      context.report({
+        messageId: 'commentedTests',
+        node
+      });
+    }
+
+    return {
+      Program() {
+        const comments = sourceCode.getAllComments();
+        comments.forEach(checkNode);
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-conditional-expect.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-conditional-expect.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-conditional-expect.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,98 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const isCatchCall = node => node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property, 'catch');
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      description: 'Prevent calling `expect` conditionally',
+      category: 'Best Practices',
+      recommended: 'error'
+    },
+    messages: {
+      conditionalExpect: 'Avoid calling `expect` conditionally`'
+    },
+    type: 'problem',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    let conditionalDepth = 0;
+    let inTestCase = false;
+    let inPromiseCatch = false;
+
+    const increaseConditionalDepth = () => inTestCase && conditionalDepth++;
+
+    const decreaseConditionalDepth = () => inTestCase && conditionalDepth--;
+
+    return {
+      FunctionDeclaration(node) {
+        const declaredVariables = context.getDeclaredVariables(node);
+        const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
+
+        if (testCallExpressions.length > 0) {
+          inTestCase = true;
+        }
+      },
+
+      CallExpression(node) {
+        if ((0, _utils.isTestCaseCall)(node)) {
+          inTestCase = true;
+        }
+
+        if (isCatchCall(node)) {
+          inPromiseCatch = true;
+        }
+
+        if (inTestCase && (0, _utils.isExpectCall)(node) && conditionalDepth > 0) {
+          context.report({
+            messageId: 'conditionalExpect',
+            node
+          });
+        }
+
+        if (inPromiseCatch && (0, _utils.isExpectCall)(node)) {
+          context.report({
+            messageId: 'conditionalExpect',
+            node
+          });
+        }
+      },
+
+      'CallExpression:exit'(node) {
+        if ((0, _utils.isTestCaseCall)(node)) {
+          inTestCase = false;
+        }
+
+        if (isCatchCall(node)) {
+          inPromiseCatch = false;
+        }
+      },
+
+      CatchClause: increaseConditionalDepth,
+      'CatchClause:exit': decreaseConditionalDepth,
+      IfStatement: increaseConditionalDepth,
+      'IfStatement:exit': decreaseConditionalDepth,
+      SwitchStatement: increaseConditionalDepth,
+      'SwitchStatement:exit': decreaseConditionalDepth,
+      ConditionalExpression: increaseConditionalDepth,
+      'ConditionalExpression:exit': decreaseConditionalDepth,
+      LogicalExpression: increaseConditionalDepth,
+      'LogicalExpression:exit': decreaseConditionalDepth
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-deprecated-functions.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-deprecated-functions.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-deprecated-functions.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,103 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _detectJestVersion = require("./detectJestVersion");
+
+var _utils = require("./utils");
+
+const parseJestVersion = rawVersion => {
+  if (typeof rawVersion === 'number') {
+    return rawVersion;
+  }
+
+  const [majorVersion] = rawVersion.split('.');
+  return parseInt(majorVersion, 10);
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow use of deprecated functions',
+      recommended: 'error'
+    },
+    messages: {
+      deprecatedFunction: '`{{ deprecation }}` has been deprecated in favor of `{{ replacement }}`'
+    },
+    type: 'suggestion',
+    schema: [],
+    fixable: 'code'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    var _context$settings, _context$settings$jes;
+
+    const jestVersion = parseJestVersion(((_context$settings = context.settings) === null || _context$settings === void 0 ? void 0 : (_context$settings$jes = _context$settings.jest) === null || _context$settings$jes === void 0 ? void 0 : _context$settings$jes.version) || (0, _detectJestVersion.detectJestVersion)());
+    const deprecations = { ...(jestVersion >= 15 && {
+        'jest.resetModuleRegistry': 'jest.resetModules'
+      }),
+      ...(jestVersion >= 17 && {
+        'jest.addMatchers': 'expect.extend'
+      }),
+      ...(jestVersion >= 21 && {
+        'require.requireMock': 'jest.requireMock',
+        'require.requireActual': 'jest.requireActual'
+      }),
+      ...(jestVersion >= 22 && {
+        'jest.runTimersToTime': 'jest.advanceTimersByTime'
+      }),
+      ...(jestVersion >= 26 && {
+        'jest.genMockFromModule': 'jest.createMockFromModule'
+      })
+    };
+    return {
+      CallExpression(node) {
+        if (node.callee.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
+          return;
+        }
+
+        const deprecation = (0, _utils.getNodeName)(node);
+
+        if (!deprecation || !(deprecation in deprecations)) {
+          return;
+        }
+
+        const replacement = deprecations[deprecation];
+        const {
+          callee
+        } = node;
+        context.report({
+          messageId: 'deprecatedFunction',
+          data: {
+            deprecation,
+            replacement
+          },
+          node,
+
+          fix(fixer) {
+            let [name, func] = replacement.split('.');
+
+            if (callee.property.type === _experimentalUtils.AST_NODE_TYPES.Literal) {
+              func = `'${func}'`;
+            }
+
+            return [fixer.replaceText(callee.object, name), fixer.replaceText(callee.property, func)];
+          }
+
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-disabled-tests.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-disabled-tests.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-disabled-tests.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,135 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow disabled tests',
+      recommended: 'warn'
+    },
+    messages: {
+      missingFunction: 'Test is missing function argument',
+      skippedTestSuite: 'Skipped test suite',
+      skippedTest: 'Skipped test',
+      pending: 'Call to pending()',
+      pendingSuite: 'Call to pending() within test suite',
+      pendingTest: 'Call to pending() within test',
+      disabledSuite: 'Disabled test suite',
+      disabledTest: 'Disabled test'
+    },
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    let suiteDepth = 0;
+    let testDepth = 0;
+    return {
+      'CallExpression[callee.name="describe"]'() {
+        suiteDepth++;
+      },
+
+      'CallExpression[callee.name=/^(it|test)$/]'() {
+        testDepth++;
+      },
+
+      'CallExpression[callee.name=/^(it|test)$/][arguments.length<2]'(node) {
+        context.report({
+          messageId: 'missingFunction',
+          node
+        });
+      },
+
+      CallExpression(node) {
+        const functionName = (0, _utils.getNodeName)(node.callee); // prevent duplicate warnings for it.each()()
+
+        if (node.callee.type === 'CallExpression') {
+          return;
+        }
+
+        switch (functionName) {
+          case 'describe.skip.each':
+          case 'xdescribe.each':
+          case 'describe.skip':
+            context.report({
+              messageId: 'skippedTestSuite',
+              node
+            });
+            break;
+
+          case 'it.skip':
+          case 'it.concurrent.skip':
+          case 'test.skip':
+          case 'test.concurrent.skip':
+          case 'it.skip.each':
+          case 'test.skip.each':
+          case 'xit.each':
+          case 'xtest.each':
+            context.report({
+              messageId: 'skippedTest',
+              node
+            });
+            break;
+        }
+      },
+
+      'CallExpression[callee.name="pending"]'(node) {
+        if ((0, _utils.scopeHasLocalReference)(context.getScope(), 'pending')) {
+          return;
+        }
+
+        if (testDepth > 0) {
+          context.report({
+            messageId: 'pendingTest',
+            node
+          });
+        } else if (suiteDepth > 0) {
+          context.report({
+            messageId: 'pendingSuite',
+            node
+          });
+        } else {
+          context.report({
+            messageId: 'pending',
+            node
+          });
+        }
+      },
+
+      'CallExpression[callee.name="xdescribe"]'(node) {
+        context.report({
+          messageId: 'disabledSuite',
+          node
+        });
+      },
+
+      'CallExpression[callee.name=/^(xit|xtest)$/]'(node) {
+        context.report({
+          messageId: 'disabledTest',
+          node
+        });
+      },
+
+      'CallExpression[callee.name="describe"]:exit'() {
+        suiteDepth--;
+      },
+
+      'CallExpression[callee.name=/^(it|test)$/]:exit'() {
+        testDepth--;
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-done-callback.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-done-callback.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-done-callback.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,148 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const findCallbackArg = (node, isJestEach) => {
+  if (isJestEach) {
+    return node.arguments[1];
+  }
+
+  if ((0, _utils.isHook)(node) && node.arguments.length >= 1) {
+    return node.arguments[0];
+  }
+
+  if ((0, _utils.isTestCaseCall)(node) && node.arguments.length >= 2) {
+    return node.arguments[1];
+  }
+
+  return null;
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Avoid using a callback in asynchronous tests and hooks',
+      recommended: 'error',
+      suggestion: true
+    },
+    messages: {
+      noDoneCallback: 'Return a Promise instead of relying on callback parameter',
+      suggestWrappingInPromise: 'Wrap in `new Promise({{ callback }} => ...`',
+      useAwaitInsteadOfCallback: 'Use await instead of callback in async functions'
+    },
+    schema: [],
+    type: 'suggestion',
+    hasSuggestions: true
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        var _getNodeName$endsWith, _getNodeName;
+
+        // done is the second argument for it.each, not the first
+        const isJestEach = (_getNodeName$endsWith = (_getNodeName = (0, _utils.getNodeName)(node.callee)) === null || _getNodeName === void 0 ? void 0 : _getNodeName.endsWith('.each')) !== null && _getNodeName$endsWith !== void 0 ? _getNodeName$endsWith : false;
+
+        if (isJestEach && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) {
+          // isJestEach but not a TaggedTemplateExpression, so this must be
+          // the `jest.each([])()` syntax which this rule doesn't support due
+          // to its complexity (see jest-community/eslint-plugin-jest#710)
+          return;
+        }
+
+        const callback = findCallbackArg(node, isJestEach);
+        const callbackArgIndex = Number(isJestEach);
+
+        if (!callback || !(0, _utils.isFunction)(callback) || callback.params.length !== 1 + callbackArgIndex) {
+          return;
+        }
+
+        const argument = callback.params[callbackArgIndex];
+
+        if (argument.type !== _experimentalUtils.AST_NODE_TYPES.Identifier) {
+          context.report({
+            node: argument,
+            messageId: 'noDoneCallback'
+          });
+          return;
+        }
+
+        if (callback.async) {
+          context.report({
+            node: argument,
+            messageId: 'useAwaitInsteadOfCallback'
+          });
+          return;
+        }
+
+        context.report({
+          node: argument,
+          messageId: 'noDoneCallback',
+          suggest: [{
+            messageId: 'suggestWrappingInPromise',
+            data: {
+              callback: argument.name
+            },
+
+            fix(fixer) {
+              const {
+                body
+              } = callback;
+              const sourceCode = context.getSourceCode();
+              const firstBodyToken = sourceCode.getFirstToken(body);
+              const lastBodyToken = sourceCode.getLastToken(body);
+              const tokenBeforeArgument = sourceCode.getTokenBefore(argument);
+              const tokenAfterArgument = sourceCode.getTokenAfter(argument);
+              /* istanbul ignore if */
+
+              if (!firstBodyToken || !lastBodyToken || !tokenBeforeArgument || !tokenAfterArgument) {
+                throw new Error(`Unexpected null when attempting to fix ${context.getFilename()} - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
+              }
+
+              const argumentInParens = tokenBeforeArgument.value === '(' && tokenAfterArgument.value === ')';
+              let argumentFix = fixer.replaceText(argument, '()');
+
+              if (argumentInParens) {
+                argumentFix = fixer.remove(argument);
+              }
+
+              let newCallback = argument.name;
+
+              if (argumentInParens) {
+                newCallback = `(${newCallback})`;
+              }
+
+              let beforeReplacement = `new Promise(${newCallback} => `;
+              let afterReplacement = ')';
+              let replaceBefore = true;
+
+              if (body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
+                const keyword = 'return';
+                beforeReplacement = `${keyword} ${beforeReplacement}{`;
+                afterReplacement += '}';
+                replaceBefore = false;
+              }
+
+              return [argumentFix, replaceBefore ? fixer.insertTextBefore(firstBodyToken, beforeReplacement) : fixer.insertTextAfter(firstBodyToken, beforeReplacement), fixer.insertTextAfter(lastBodyToken, afterReplacement)];
+            }
+
+          }]
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-duplicate-hooks.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-duplicate-hooks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-duplicate-hooks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,68 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+const newHookContext = () => ({
+  beforeAll: 0,
+  beforeEach: 0,
+  afterAll: 0,
+  afterEach: 0
+});
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow duplicate setup and teardown hooks',
+      recommended: false
+    },
+    messages: {
+      noDuplicateHook: 'Duplicate {{hook}} in describe block'
+    },
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    const hookContexts = [newHookContext()];
+    return {
+      CallExpression(node) {
+        if ((0, _utils.isDescribeCall)(node)) {
+          hookContexts.push(newHookContext());
+        }
+
+        if ((0, _utils.isHook)(node)) {
+          const currentLayer = hookContexts[hookContexts.length - 1];
+          currentLayer[node.callee.name] += 1;
+
+          if (currentLayer[node.callee.name] > 1) {
+            context.report({
+              messageId: 'noDuplicateHook',
+              data: {
+                hook: node.callee.name
+              },
+              node
+            });
+          }
+        }
+      },
+
+      'CallExpression:exit'(node) {
+        if ((0, _utils.isDescribeCall)(node)) {
+          hookContexts.pop();
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-export.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-export.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-export.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,76 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow using `exports` in files containing tests',
+      recommended: 'error'
+    },
+    messages: {
+      unexpectedExport: `Do not export from a test file.`
+    },
+    type: 'suggestion',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    const exportNodes = [];
+    let hasTestCase = false;
+    return {
+      'Program:exit'() {
+        if (hasTestCase && exportNodes.length > 0) {
+          for (const node of exportNodes) {
+            context.report({
+              node,
+              messageId: 'unexpectedExport'
+            });
+          }
+        }
+      },
+
+      CallExpression(node) {
+        if ((0, _utils.isTestCaseCall)(node)) {
+          hasTestCase = true;
+        }
+      },
+
+      'ExportNamedDeclaration, ExportDefaultDeclaration'(node) {
+        exportNodes.push(node);
+      },
+
+      'AssignmentExpression > MemberExpression'(node) {
+        let {
+          object,
+          property
+        } = node;
+
+        if (object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
+          ({
+            object,
+            property
+          } = object);
+        }
+
+        if ('name' in object && object.name === 'module' && property.type === _experimentalUtils.AST_NODE_TYPES.Identifier && /^exports?$/u.test(property.name)) {
+          exportNodes.push(node);
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-focused-tests.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-focused-tests.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-focused-tests.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,85 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const findOnlyNode = node => {
+  const callee = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
+
+  if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
+    if (callee.object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
+      if ((0, _utils.isSupportedAccessor)(callee.object.property, 'only')) {
+        return callee.object.property;
+      }
+    }
+
+    if ((0, _utils.isSupportedAccessor)(callee.property, 'only')) {
+      return callee.property;
+    }
+  }
+
+  return null;
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow focused tests',
+      recommended: 'error',
+      suggestion: true
+    },
+    messages: {
+      focusedTest: 'Unexpected focused test.',
+      suggestRemoveFocus: 'Remove focus from test.'
+    },
+    schema: [],
+    type: 'suggestion',
+    hasSuggestions: true
+  },
+  defaultOptions: [],
+  create: context => ({
+    CallExpression(node) {
+      if (!(0, _utils.isDescribeCall)(node) && !(0, _utils.isTestCaseCall)(node)) {
+        return;
+      }
+
+      if ((0, _utils.getNodeName)(node).startsWith('f')) {
+        context.report({
+          messageId: 'focusedTest',
+          node,
+          suggest: [{
+            messageId: 'suggestRemoveFocus',
+            fix: fixer => fixer.removeRange([node.range[0], node.range[0] + 1])
+          }]
+        });
+        return;
+      }
+
+      const onlyNode = findOnlyNode(node);
+
+      if (!onlyNode) {
+        return;
+      }
+
+      context.report({
+        messageId: 'focusedTest',
+        node: onlyNode,
+        suggest: [{
+          messageId: 'suggestRemoveFocus',
+          fix: fixer => fixer.removeRange([onlyNode.range[0] - 1, onlyNode.range[1] + Number(onlyNode.type !== _experimentalUtils.AST_NODE_TYPES.Identifier)])
+        }]
+      });
+    }
+
+  })
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-hooks.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-hooks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-hooks.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,58 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow setup and teardown hooks',
+      recommended: false
+    },
+    messages: {
+      unexpectedHook: "Unexpected '{{ hookName }}' hook"
+    },
+    schema: [{
+      type: 'object',
+      properties: {
+        allow: {
+          type: 'array',
+          contains: ['beforeAll', 'beforeEach', 'afterAll', 'afterEach']
+        }
+      },
+      additionalProperties: false
+    }],
+    type: 'suggestion'
+  },
+  defaultOptions: [{
+    allow: []
+  }],
+
+  create(context, [{
+    allow = []
+  }]) {
+    return {
+      CallExpression(node) {
+        if ((0, _utils.isHook)(node) && !allow.includes(node.callee.name)) {
+          context.report({
+            node,
+            messageId: 'unexpectedHook',
+            data: {
+              hookName: node.callee.name
+            }
+          });
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-identical-title.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-identical-title.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-identical-title.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,92 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+const newDescribeContext = () => ({
+  describeTitles: [],
+  testTitles: []
+});
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow identical titles',
+      recommended: 'error'
+    },
+    messages: {
+      multipleTestTitle: 'Test title is used multiple times in the same describe block.',
+      multipleDescribeTitle: 'Describe block title is used multiple times in the same describe block.'
+    },
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    const contexts = [newDescribeContext()];
+    return {
+      CallExpression(node) {
+        var _getNodeName;
+
+        const currentLayer = contexts[contexts.length - 1];
+
+        if ((0, _utils.isDescribeCall)(node)) {
+          contexts.push(newDescribeContext());
+        }
+
+        if ((_getNodeName = (0, _utils.getNodeName)(node.callee)) !== null && _getNodeName !== void 0 && _getNodeName.endsWith('.each')) {
+          return;
+        }
+
+        const [argument] = node.arguments;
+
+        if (!argument || !(0, _utils.isStringNode)(argument)) {
+          return;
+        }
+
+        const title = (0, _utils.getStringValue)(argument);
+
+        if ((0, _utils.isTestCaseCall)(node)) {
+          if (currentLayer.testTitles.includes(title)) {
+            context.report({
+              messageId: 'multipleTestTitle',
+              node: argument
+            });
+          }
+
+          currentLayer.testTitles.push(title);
+        }
+
+        if (!(0, _utils.isDescribeCall)(node)) {
+          return;
+        }
+
+        if (currentLayer.describeTitles.includes(title)) {
+          context.report({
+            messageId: 'multipleDescribeTitle',
+            node: argument
+          });
+        }
+
+        currentLayer.describeTitles.push(title);
+      },
+
+      'CallExpression:exit'(node) {
+        if ((0, _utils.isDescribeCall)(node)) {
+          contexts.pop();
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-if.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-if.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-if.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,107 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const testCaseNames = new Set([...Object.keys(_utils.TestCaseName), 'it.only', 'it.concurrent.only', 'it.skip', 'it.concurrent.skip', 'test.only', 'test.concurrent.only', 'test.skip', 'test.concurrent.skip', 'fit.concurrent']);
+
+const isTestFunctionExpression = node => node.parent !== undefined && node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && testCaseNames.has((0, _utils.getNodeName)(node.parent.callee));
+
+const conditionName = {
+  [_experimentalUtils.AST_NODE_TYPES.ConditionalExpression]: 'conditional',
+  [_experimentalUtils.AST_NODE_TYPES.SwitchStatement]: 'switch',
+  [_experimentalUtils.AST_NODE_TYPES.IfStatement]: 'if'
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      description: 'Disallow conditional logic',
+      category: 'Best Practices',
+      recommended: false
+    },
+    messages: {
+      conditionalInTest: 'Test should not contain {{ condition }} statements.'
+    },
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    const stack = [];
+
+    function validate(node) {
+      const lastElementInStack = stack[stack.length - 1];
+
+      if (stack.length === 0 || !lastElementInStack) {
+        return;
+      }
+
+      context.report({
+        data: {
+          condition: conditionName[node.type]
+        },
+        messageId: 'conditionalInTest',
+        node
+      });
+    }
+
+    return {
+      CallExpression(node) {
+        if ((0, _utils.isTestCaseCall)(node)) {
+          stack.push(true);
+
+          if ((0, _utils.getNodeName)(node).endsWith('each')) {
+            stack.push(true);
+          }
+        }
+      },
+
+      FunctionExpression(node) {
+        stack.push(isTestFunctionExpression(node));
+      },
+
+      FunctionDeclaration(node) {
+        const declaredVariables = context.getDeclaredVariables(node);
+        const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
+        stack.push(testCallExpressions.length > 0);
+      },
+
+      ArrowFunctionExpression(node) {
+        stack.push(isTestFunctionExpression(node));
+      },
+
+      IfStatement: validate,
+      SwitchStatement: validate,
+      ConditionalExpression: validate,
+
+      'CallExpression:exit'() {
+        stack.pop();
+      },
+
+      'FunctionExpression:exit'() {
+        stack.pop();
+      },
+
+      'FunctionDeclaration:exit'() {
+        stack.pop();
+      },
+
+      'ArrowFunctionExpression:exit'() {
+        stack.pop();
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-interpolation-in-snapshots.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-interpolation-in-snapshots.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-interpolation-in-snapshots.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,63 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow string interpolation inside snapshots',
+      recommended: 'error'
+    },
+    messages: {
+      noInterpolation: 'Do not use string interpolation inside of snapshots'
+    },
+    schema: [],
+    type: 'problem'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          matcher
+        } = (0, _utils.parseExpectCall)(node);
+
+        if (!matcher) {
+          return;
+        }
+
+        if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes(matcher.name)) {
+          var _matcher$arguments;
+
+          // Check all since the optional 'propertyMatchers' argument might be present
+          (_matcher$arguments = matcher.arguments) === null || _matcher$arguments === void 0 ? void 0 : _matcher$arguments.forEach(argument => {
+            if (argument.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && argument.expressions.length > 0) {
+              context.report({
+                messageId: 'noInterpolation',
+                node: argument
+              });
+            }
+          });
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-jasmine-globals.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-jasmine-globals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-jasmine-globals.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,165 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow Jasmine globals',
+      recommended: 'error'
+    },
+    messages: {
+      illegalGlobal: 'Illegal usage of global `{{ global }}`, prefer `{{ replacement }}`',
+      illegalMethod: 'Illegal usage of `{{ method }}`, prefer `{{ replacement }}`',
+      illegalFail: 'Illegal usage of `fail`, prefer throwing an error, or the `done.fail` callback',
+      illegalPending: 'Illegal usage of `pending`, prefer explicitly skipping a test using `test.skip`',
+      illegalJasmine: 'Illegal usage of jasmine global'
+    },
+    fixable: 'code',
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        const {
+          callee
+        } = node;
+        const calleeName = (0, _utils.getNodeName)(callee);
+
+        if (!calleeName) {
+          return;
+        }
+
+        if (calleeName === 'spyOn' || calleeName === 'spyOnProperty' || calleeName === 'fail' || calleeName === 'pending') {
+          if ((0, _utils.scopeHasLocalReference)(context.getScope(), calleeName)) {
+            // It's a local variable, not a jasmine global.
+            return;
+          }
+
+          switch (calleeName) {
+            case 'spyOn':
+            case 'spyOnProperty':
+              context.report({
+                node,
+                messageId: 'illegalGlobal',
+                data: {
+                  global: calleeName,
+                  replacement: 'jest.spyOn'
+                }
+              });
+              break;
+
+            case 'fail':
+              context.report({
+                node,
+                messageId: 'illegalFail'
+              });
+              break;
+
+            case 'pending':
+              context.report({
+                node,
+                messageId: 'illegalPending'
+              });
+              break;
+          }
+
+          return;
+        }
+
+        if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && calleeName.startsWith('jasmine.')) {
+          const functionName = calleeName.replace('jasmine.', '');
+
+          if (functionName === 'any' || functionName === 'anything' || functionName === 'arrayContaining' || functionName === 'objectContaining' || functionName === 'stringMatching') {
+            context.report({
+              fix: fixer => [fixer.replaceText(callee.object, 'expect')],
+              node,
+              messageId: 'illegalMethod',
+              data: {
+                method: calleeName,
+                replacement: `expect.${functionName}`
+              }
+            });
+            return;
+          }
+
+          if (functionName === 'addMatchers') {
+            context.report({
+              node,
+              messageId: 'illegalMethod',
+              data: {
+                method: calleeName,
+                replacement: 'expect.extend'
+              }
+            });
+            return;
+          }
+
+          if (functionName === 'createSpy') {
+            context.report({
+              node,
+              messageId: 'illegalMethod',
+              data: {
+                method: calleeName,
+                replacement: 'jest.fn'
+              }
+            });
+            return;
+          }
+
+          context.report({
+            node,
+            messageId: 'illegalJasmine'
+          });
+        }
+      },
+
+      MemberExpression(node) {
+        if ((0, _utils.isSupportedAccessor)(node.object, 'jasmine')) {
+          const {
+            parent,
+            property
+          } = node;
+
+          if (parent && parent.type === _experimentalUtils.AST_NODE_TYPES.AssignmentExpression) {
+            if ((0, _utils.isSupportedAccessor)(property, 'DEFAULT_TIMEOUT_INTERVAL')) {
+              const {
+                right
+              } = parent;
+
+              if (right.type === _experimentalUtils.AST_NODE_TYPES.Literal) {
+                context.report({
+                  fix: fixer => [fixer.replaceText(parent, `jest.setTimeout(${right.value})`)],
+                  node,
+                  messageId: 'illegalJasmine'
+                });
+                return;
+              }
+            }
+
+            context.report({
+              node,
+              messageId: 'illegalJasmine'
+            });
+          }
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-jest-import.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-jest-import.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-jest-import.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,48 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    type: 'problem',
+    docs: {
+      description: 'Disallow importing Jest',
+      category: 'Best Practices',
+      recommended: 'error'
+    },
+    messages: {
+      unexpectedImport: `Jest is automatically in scope. Do not import "jest", as Jest doesn't export anything.`
+    },
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      'ImportDeclaration[source.value="jest"]'(node) {
+        context.report({
+          node,
+          messageId: 'unexpectedImport'
+        });
+      },
+
+      'CallExpression[callee.name="require"][arguments.0.value="jest"]'(node) {
+        context.report({
+          loc: node.arguments[0].loc,
+          messageId: 'unexpectedImport',
+          node
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-large-snapshots.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-large-snapshots.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-large-snapshots.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,131 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _path = require("path");
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const reportOnViolation = (context, node, {
+  maxSize: lineLimit = 50,
+  allowedSnapshots = {}
+}) => {
+  const startLine = node.loc.start.line;
+  const endLine = node.loc.end.line;
+  const lineCount = endLine - startLine;
+  const allPathsAreAbsolute = Object.keys(allowedSnapshots).every(_path.isAbsolute);
+
+  if (!allPathsAreAbsolute) {
+    throw new Error('All paths for allowedSnapshots must be absolute. You can use JS config and `path.resolve`');
+  }
+
+  let isAllowed = false;
+
+  if (node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement && 'left' in node.expression && (0, _utils.isExpectMember)(node.expression.left)) {
+    const fileName = context.getFilename();
+    const allowedSnapshotsInFile = allowedSnapshots[fileName];
+
+    if (allowedSnapshotsInFile) {
+      const snapshotName = (0, _utils.getAccessorValue)(node.expression.left.property);
+      isAllowed = allowedSnapshotsInFile.some(name => {
+        if (name instanceof RegExp) {
+          return name.test(snapshotName);
+        }
+
+        return snapshotName === name;
+      });
+    }
+  }
+
+  if (!isAllowed && lineCount > lineLimit) {
+    context.report({
+      messageId: lineLimit === 0 ? 'noSnapshot' : 'tooLongSnapshots',
+      data: {
+        lineLimit,
+        lineCount
+      },
+      node
+    });
+  }
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'disallow large snapshots',
+      recommended: false
+    },
+    messages: {
+      noSnapshot: '`{{ lineCount }}`s should begin with lowercase',
+      tooLongSnapshots: 'Expected Jest snapshot to be smaller than {{ lineLimit }} lines but was {{ lineCount }} lines long'
+    },
+    type: 'suggestion',
+    schema: [{
+      type: 'object',
+      properties: {
+        maxSize: {
+          type: 'number'
+        },
+        inlineMaxSize: {
+          type: 'number'
+        },
+        allowedSnapshots: {
+          type: 'object',
+          additionalProperties: {
+            type: 'array'
+          }
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+  defaultOptions: [{}],
+
+  create(context, [options]) {
+    if (context.getFilename().endsWith('.snap')) {
+      return {
+        ExpressionStatement(node) {
+          reportOnViolation(context, node, options);
+        }
+
+      };
+    }
+
+    return {
+      CallExpression(node) {
+        var _matcher$arguments;
+
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          matcher
+        } = (0, _utils.parseExpectCall)(node);
+
+        if ((matcher === null || matcher === void 0 ? void 0 : matcher.node.parent.type) !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
+          return;
+        }
+
+        if (['toMatchInlineSnapshot', 'toThrowErrorMatchingInlineSnapshot'].includes(matcher.name) && (_matcher$arguments = matcher.arguments) !== null && _matcher$arguments !== void 0 && _matcher$arguments.length) {
+          var _options$inlineMaxSiz;
+
+          reportOnViolation(context, matcher.arguments[0], { ...options,
+            maxSize: (_options$inlineMaxSiz = options.inlineMaxSize) !== null && _options$inlineMaxSiz !== void 0 ? _options$inlineMaxSiz : options.maxSize
+          });
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-mocks-import.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-mocks-import.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-mocks-import.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,61 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _path = require("path");
+
+var _utils = require("./utils");
+
+const mocksDirName = '__mocks__';
+
+const isMockPath = path => path.split(_path.posix.sep).includes(mocksDirName);
+
+const isMockImportLiteral = expression => (0, _utils.isStringNode)(expression) && isMockPath((0, _utils.getStringValue)(expression));
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    type: 'problem',
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow manually importing from `__mocks__`',
+      recommended: 'error'
+    },
+    messages: {
+      noManualImport: `Mocks should not be manually imported from a ${mocksDirName} directory. Instead use \`jest.mock\` and import from the original module path.`
+    },
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      ImportDeclaration(node) {
+        if (isMockImportLiteral(node.source)) {
+          context.report({
+            node,
+            messageId: 'noManualImport'
+          });
+        }
+      },
+
+      'CallExpression[callee.name="require"]'(node) {
+        const [arg] = node.arguments;
+
+        if (arg && isMockImportLiteral(arg)) {
+          context.report({
+            node: arg,
+            messageId: 'noManualImport'
+          });
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-restricted-matchers.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-restricted-matchers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-restricted-matchers.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,104 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow specific matchers & modifiers',
+      recommended: false
+    },
+    type: 'suggestion',
+    schema: [{
+      type: 'object',
+      additionalProperties: {
+        type: ['string', 'null']
+      }
+    }],
+    messages: {
+      restrictedChain: 'Use of `{{ chain }}` is disallowed',
+      restrictedChainWithMessage: '{{ message }}'
+    }
+  },
+  defaultOptions: [{}],
+
+  create(context, [restrictedChains]) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          matcher,
+          modifier
+        } = (0, _utils.parseExpectCall)(node);
+
+        if (matcher) {
+          const chain = matcher.name;
+
+          if (chain in restrictedChains) {
+            const message = restrictedChains[chain];
+            context.report({
+              messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain',
+              data: {
+                message,
+                chain
+              },
+              node: matcher.node.property
+            });
+            return;
+          }
+        }
+
+        if (modifier) {
+          const chain = modifier.name;
+
+          if (chain in restrictedChains) {
+            const message = restrictedChains[chain];
+            context.report({
+              messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain',
+              data: {
+                message,
+                chain
+              },
+              node: modifier.node.property
+            });
+            return;
+          }
+        }
+
+        if (matcher && modifier) {
+          const chain = `${modifier.name}.${matcher.name}`;
+
+          if (chain in restrictedChains) {
+            const message = restrictedChains[chain];
+            context.report({
+              messageId: message ? 'restrictedChainWithMessage' : 'restrictedChain',
+              data: {
+                message,
+                chain
+              },
+              loc: {
+                start: modifier.node.property.loc.start,
+                end: matcher.node.property.loc.end
+              }
+            });
+            return;
+          }
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-standalone-expect.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-standalone-expect.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-standalone-expect.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,143 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const getBlockType = statement => {
+  const func = statement.parent;
+  /* istanbul ignore if */
+
+  if (!func) {
+    throw new Error(`Unexpected BlockStatement. No parent defined. - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
+  } // functionDeclaration: function func() {}
+
+
+  if (func.type === _experimentalUtils.AST_NODE_TYPES.FunctionDeclaration) {
+    return 'function';
+  }
+
+  if ((0, _utils.isFunction)(func) && func.parent) {
+    const expr = func.parent; // arrow function or function expr
+
+    if (expr.type === _experimentalUtils.AST_NODE_TYPES.VariableDeclarator) {
+      return 'function';
+    } // if it's not a variable, it will be callExpr, we only care about describe
+
+
+    if (expr.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isDescribeCall)(expr)) {
+      return 'describe';
+    }
+  }
+
+  return null;
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow using `expect` outside of `it` or `test` blocks',
+      recommended: 'error'
+    },
+    messages: {
+      unexpectedExpect: 'Expect must be inside of a test block.'
+    },
+    type: 'suggestion',
+    schema: [{
+      properties: {
+        additionalTestBlockFunctions: {
+          type: 'array',
+          items: {
+            type: 'string'
+          }
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+  defaultOptions: [{
+    additionalTestBlockFunctions: []
+  }],
+
+  create(context, [{
+    additionalTestBlockFunctions = []
+  }]) {
+    const callStack = [];
+
+    const isCustomTestBlockFunction = node => additionalTestBlockFunctions.includes((0, _utils.getNodeName)(node) || '');
+
+    const isTestBlock = node => (0, _utils.isTestCaseCall)(node) || isCustomTestBlockFunction(node);
+
+    return {
+      CallExpression(node) {
+        if ((0, _utils.isExpectCall)(node)) {
+          const parent = callStack[callStack.length - 1];
+
+          if (!parent || parent === _utils.DescribeAlias.describe) {
+            context.report({
+              node,
+              messageId: 'unexpectedExpect'
+            });
+          }
+
+          return;
+        }
+
+        if (isTestBlock(node)) {
+          callStack.push('test');
+        }
+
+        if (node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) {
+          callStack.push('template');
+        }
+      },
+
+      'CallExpression:exit'(node) {
+        const top = callStack[callStack.length - 1];
+
+        if (top === 'test' && isTestBlock(node) && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression || top === 'template' && node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) {
+          callStack.pop();
+        }
+      },
+
+      BlockStatement(statement) {
+        const blockType = getBlockType(statement);
+
+        if (blockType) {
+          callStack.push(blockType);
+        }
+      },
+
+      'BlockStatement:exit'(statement) {
+        if (callStack[callStack.length - 1] === getBlockType(statement)) {
+          callStack.pop();
+        }
+      },
+
+      ArrowFunctionExpression(node) {
+        var _node$parent;
+
+        if (((_node$parent = node.parent) === null || _node$parent === void 0 ? void 0 : _node$parent.type) !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
+          callStack.push('arrow');
+        }
+      },
+
+      'ArrowFunctionExpression:exit'() {
+        if (callStack[callStack.length - 1] === 'arrow') {
+          callStack.pop();
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-test-prefixes.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-test-prefixes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-test-prefixes.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,71 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Use `.only` and `.skip` over `f` and `x`',
+      recommended: 'error'
+    },
+    messages: {
+      usePreferredName: 'Use "{{ preferredNodeName }}" instead'
+    },
+    fixable: 'code',
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        const nodeName = (0, _utils.getNodeName)(node.callee);
+        if (!nodeName || !(0, _utils.isDescribeCall)(node) && !(0, _utils.isTestCaseCall)(node)) return;
+        const preferredNodeName = getPreferredNodeName(nodeName);
+        if (!preferredNodeName) return;
+        const funcNode = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
+        context.report({
+          messageId: 'usePreferredName',
+          node: node.callee,
+          data: {
+            preferredNodeName
+          },
+
+          fix(fixer) {
+            return [fixer.replaceText(funcNode, preferredNodeName)];
+          }
+
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
+
+function getPreferredNodeName(nodeName) {
+  const firstChar = nodeName.charAt(0);
+  const suffix = nodeName.endsWith('.each') ? '.each' : '';
+
+  if (firstChar === 'f') {
+    return `${nodeName.slice(1).replace('.each', '')}.only${suffix}`;
+  }
+
+  if (firstChar === 'x') {
+    return `${nodeName.slice(1).replace('.each', '')}.skip${suffix}`;
+  }
+
+  return null;
+}
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/no-test-return-statement.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/no-test-return-statement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/no-test-return-statement.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,68 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const getBody = args => {
+  const [, secondArg] = args;
+
+  if (secondArg && (0, _utils.isFunction)(secondArg) && secondArg.body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
+    return secondArg.body.body;
+  }
+
+  return [];
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Disallow explicitly returning from tests',
+      recommended: false
+    },
+    messages: {
+      noReturnValue: 'Jest tests should not return a value.'
+    },
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isTestCaseCall)(node)) return;
+        const body = getBody(node.arguments);
+        const returnStmt = body.find(t => t.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement);
+        if (!returnStmt) return;
+        context.report({
+          messageId: 'noReturnValue',
+          node: returnStmt
+        });
+      },
+
+      FunctionDeclaration(node) {
+        const declaredVariables = context.getDeclaredVariables(node);
+        const testCallExpressions = (0, _utils.getTestCallExpressionsFromDeclaredVariables)(declaredVariables);
+        if (testCallExpressions.length === 0) return;
+        const returnStmt = node.body.body.find(t => t.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement);
+        if (!returnStmt) return;
+        context.report({
+          messageId: 'noReturnValue',
+          node: returnStmt
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-called-with.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-called-with.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-called-with.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,57 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest using `toBeCalledWith()` or `toHaveBeenCalledWith()`',
+      recommended: false
+    },
+    messages: {
+      preferCalledWith: 'Prefer {{name}}With(/* expected args */)'
+    },
+    type: 'suggestion',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          modifier,
+          matcher
+        } = (0, _utils.parseExpectCall)(node); // Could check resolves/rejects here but not a likely idiom.
+
+        if (matcher && !modifier) {
+          if (['toBeCalled', 'toHaveBeenCalled'].includes(matcher.name)) {
+            context.report({
+              data: {
+                name: matcher.name
+              },
+              // todo: rename to 'matcherName'
+              messageId: 'preferCalledWith',
+              node: matcher.node.property
+            });
+          }
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-comparison-matcher.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-comparison-matcher.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-comparison-matcher.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,139 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const isBooleanLiteral = node => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
+
+/**
+ * Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
+ * with a boolean literal as the sole argument.
+ *
+ * @example javascript
+ * toBe(true);
+ * toEqual(false);
+ *
+ * @param {ParsedExpectMatcher} matcher
+ *
+ * @return {matcher is ParsedBooleanEqualityMatcher}
+ */
+const isBooleanEqualityMatcher = matcher => (0, _utils.isParsedEqualityMatcherCall)(matcher) && isBooleanLiteral((0, _utils.followTypeAssertionChain)(matcher.arguments[0]));
+
+const isString = node => {
+  return (0, _utils.isStringNode)(node) || node.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral;
+};
+
+const isComparingToString = expression => {
+  return isString(expression.left) || isString(expression.right);
+};
+
+const invertOperator = operator => {
+  switch (operator) {
+    case '>':
+      return '<=';
+
+    case '<':
+      return '>=';
+
+    case '>=':
+      return '<';
+
+    case '<=':
+      return '>';
+  }
+
+  return null;
+};
+
+const determineMatcher = (operator, negated) => {
+  const op = negated ? invertOperator(operator) : operator;
+
+  switch (op) {
+    case '>':
+      return 'toBeGreaterThan';
+
+    case '<':
+      return 'toBeLessThan';
+
+    case '>=':
+      return 'toBeGreaterThanOrEqual';
+
+    case '<=':
+      return 'toBeLessThanOrEqual';
+  }
+
+  return null;
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest using the built-in comparison matchers',
+      recommended: false
+    },
+    messages: {
+      useToBeComparison: 'Prefer using `{{ preferredMatcher }}` instead'
+    },
+    fixable: 'code',
+    type: 'suggestion',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          expect: {
+            arguments: [comparison],
+            range: [, expectCallEnd]
+          },
+          matcher,
+          modifier
+        } = (0, _utils.parseExpectCall)(node);
+
+        if (!matcher || (comparison === null || comparison === void 0 ? void 0 : comparison.type) !== _experimentalUtils.AST_NODE_TYPES.BinaryExpression || isComparingToString(comparison) || !isBooleanEqualityMatcher(matcher)) {
+          return;
+        }
+
+        const preferredMatcher = determineMatcher(comparison.operator, (0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value === !!modifier);
+
+        if (!preferredMatcher) {
+          return;
+        }
+
+        context.report({
+          fix(fixer) {
+            const sourceCode = context.getSourceCode();
+            return [// replace the comparison argument with the left-hand side of the comparison
+            fixer.replaceText(comparison, sourceCode.getText(comparison.left)), // replace the current matcher & modifier with the preferred matcher
+            fixer.replaceTextRange([expectCallEnd, matcher.node.range[1]], `.${preferredMatcher}`), // replace the matcher argument with the right-hand side of the comparison
+            fixer.replaceText(matcher.arguments[0], sourceCode.getText(comparison.right))];
+          },
+
+          messageId: 'useToBeComparison',
+          data: {
+            preferredMatcher
+          },
+          node: (modifier || matcher).node.property
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-equality-matcher.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-equality-matcher.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-equality-matcher.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,98 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const isBooleanLiteral = node => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
+
+/**
+ * Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
+ * with a boolean literal as the sole argument.
+ *
+ * @example javascript
+ * toBe(true);
+ * toEqual(false);
+ *
+ * @param {ParsedExpectMatcher} matcher
+ *
+ * @return {matcher is ParsedBooleanEqualityMatcher}
+ */
+const isBooleanEqualityMatcher = matcher => (0, _utils.isParsedEqualityMatcherCall)(matcher) && isBooleanLiteral((0, _utils.followTypeAssertionChain)(matcher.arguments[0]));
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest using the built-in equality matchers',
+      recommended: false,
+      suggestion: true
+    },
+    messages: {
+      useEqualityMatcher: 'Prefer using one of the equality matchers instead',
+      suggestEqualityMatcher: 'Use `{{ equalityMatcher }}`'
+    },
+    hasSuggestions: true,
+    type: 'suggestion',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          expect: {
+            arguments: [comparison],
+            range: [, expectCallEnd]
+          },
+          matcher,
+          modifier
+        } = (0, _utils.parseExpectCall)(node);
+
+        if (!matcher || (comparison === null || comparison === void 0 ? void 0 : comparison.type) !== _experimentalUtils.AST_NODE_TYPES.BinaryExpression || comparison.operator !== '===' && comparison.operator !== '!==' || !isBooleanEqualityMatcher(matcher)) {
+          return;
+        }
+
+        const matcherValue = (0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value; // we need to negate the expectation if the current expected
+        // value is itself negated by the "not" modifier
+
+        const addNotModifier = (comparison.operator === '!==' ? !matcherValue : matcherValue) === !!modifier;
+
+        const buildFixer = equalityMatcher => fixer => {
+          const sourceCode = context.getSourceCode();
+          return [// replace the comparison argument with the left-hand side of the comparison
+          fixer.replaceText(comparison, sourceCode.getText(comparison.left)), // replace the current matcher & modifier with the preferred matcher
+          fixer.replaceTextRange([expectCallEnd, matcher.node.range[1]], addNotModifier ? `.${_utils.ModifierName.not}.${equalityMatcher}` : `.${equalityMatcher}`), // replace the matcher argument with the right-hand side of the comparison
+          fixer.replaceText(matcher.arguments[0], sourceCode.getText(comparison.right))];
+        };
+
+        context.report({
+          messageId: 'useEqualityMatcher',
+          suggest: ['toBe', 'toEqual', 'toStrictEqual'].map(equalityMatcher => ({
+            messageId: 'suggestEqualityMatcher',
+            data: {
+              equalityMatcher
+            },
+            fix: buildFixer(equalityMatcher)
+          })),
+          node: (modifier || matcher).node.property
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-expect-assertions.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-expect-assertions.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-expect-assertions.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,232 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const isExpectAssertionsOrHasAssertionsCall = expression => expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && expression.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(expression.callee.object, 'expect') && (0, _utils.isSupportedAccessor)(expression.callee.property) && ['assertions', 'hasAssertions'].includes((0, _utils.getAccessorValue)(expression.callee.property));
+
+const isFirstLineExprStmt = functionBody => functionBody[0] && functionBody[0].type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement;
+
+const suggestRemovingExtraArguments = (args, extraArgsStartAt) => ({
+  messageId: 'suggestRemovingExtraArguments',
+  fix: fixer => fixer.removeRange([args[extraArgsStartAt].range[0] - Math.sign(extraArgsStartAt), args[args.length - 1].range[1]])
+});
+
+const suggestions = [['suggestAddingHasAssertions', 'expect.hasAssertions();'], ['suggestAddingAssertions', 'expect.assertions();']];
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest using `expect.assertions()` OR `expect.hasAssertions()`',
+      recommended: false,
+      suggestion: true
+    },
+    messages: {
+      hasAssertionsTakesNoArguments: '`expect.hasAssertions` expects no arguments',
+      assertionsRequiresOneArgument: '`expect.assertions` excepts a single argument of type number',
+      assertionsRequiresNumberArgument: 'This argument should be a number',
+      haveExpectAssertions: 'Every test should have either `expect.assertions(<number of assertions>)` or `expect.hasAssertions()` as its first expression',
+      suggestAddingHasAssertions: 'Add `expect.hasAssertions()`',
+      suggestAddingAssertions: 'Add `expect.assertions(<number of assertions>)`',
+      suggestRemovingExtraArguments: 'Remove extra arguments'
+    },
+    type: 'suggestion',
+    hasSuggestions: true,
+    schema: [{
+      type: 'object',
+      properties: {
+        onlyFunctionsWithAsyncKeyword: {
+          type: 'boolean'
+        },
+        onlyFunctionsWithExpectInLoop: {
+          type: 'boolean'
+        },
+        onlyFunctionsWithExpectInCallback: {
+          type: 'boolean'
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+  defaultOptions: [{
+    onlyFunctionsWithAsyncKeyword: false,
+    onlyFunctionsWithExpectInLoop: false,
+    onlyFunctionsWithExpectInCallback: false
+  }],
+
+  create(context, [options]) {
+    let expressionDepth = 0;
+    let hasExpectInCallback = false;
+    let hasExpectInLoop = false;
+    let inTestCaseCall = false;
+    let inForLoop = false;
+
+    const shouldCheckFunction = testFunction => {
+      if (!options.onlyFunctionsWithAsyncKeyword && !options.onlyFunctionsWithExpectInLoop && !options.onlyFunctionsWithExpectInCallback) {
+        return true;
+      }
+
+      if (options.onlyFunctionsWithAsyncKeyword) {
+        if (testFunction.async) {
+          return true;
+        }
+      }
+
+      if (options.onlyFunctionsWithExpectInLoop) {
+        if (hasExpectInLoop) {
+          return true;
+        }
+      }
+
+      if (options.onlyFunctionsWithExpectInCallback) {
+        if (hasExpectInCallback) {
+          return true;
+        }
+      }
+
+      return false;
+    };
+
+    const enterExpression = () => inTestCaseCall && expressionDepth++;
+
+    const exitExpression = () => inTestCaseCall && expressionDepth--;
+
+    const enterForLoop = () => inForLoop = true;
+
+    const exitForLoop = () => inForLoop = false;
+
+    return {
+      FunctionExpression: enterExpression,
+      'FunctionExpression:exit': exitExpression,
+      ArrowFunctionExpression: enterExpression,
+      'ArrowFunctionExpression:exit': exitExpression,
+      ForStatement: enterForLoop,
+      'ForStatement:exit': exitForLoop,
+      ForInStatement: enterForLoop,
+      'ForInStatement:exit': exitForLoop,
+      ForOfStatement: enterForLoop,
+      'ForOfStatement:exit': exitForLoop,
+
+      CallExpression(node) {
+        if ((0, _utils.isTestCaseCall)(node)) {
+          inTestCaseCall = true;
+          return;
+        }
+
+        if ((0, _utils.isExpectCall)(node) && inTestCaseCall) {
+          if (inForLoop) {
+            hasExpectInLoop = true;
+          }
+
+          if (expressionDepth > 1) {
+            hasExpectInCallback = true;
+          }
+        }
+      },
+
+      'CallExpression:exit'(node) {
+        if (!(0, _utils.isTestCaseCall)(node)) {
+          return;
+        }
+
+        if (node.arguments.length < 2) {
+          return;
+        }
+
+        const [, testFn] = node.arguments;
+
+        if (!(0, _utils.isFunction)(testFn) || testFn.body.type !== _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
+          return;
+        }
+
+        if (!shouldCheckFunction(testFn)) {
+          return;
+        }
+
+        hasExpectInLoop = false;
+        hasExpectInCallback = false;
+        const testFuncBody = testFn.body.body;
+
+        if (!isFirstLineExprStmt(testFuncBody)) {
+          context.report({
+            messageId: 'haveExpectAssertions',
+            node,
+            suggest: suggestions.map(([messageId, text]) => ({
+              messageId,
+              fix: fixer => fixer.insertTextBeforeRange([testFn.body.range[0] + 1, testFn.body.range[1]], text)
+            }))
+          });
+          return;
+        }
+
+        const testFuncFirstLine = testFuncBody[0].expression;
+
+        if (!isExpectAssertionsOrHasAssertionsCall(testFuncFirstLine)) {
+          context.report({
+            messageId: 'haveExpectAssertions',
+            node,
+            suggest: suggestions.map(([messageId, text]) => ({
+              messageId,
+              fix: fixer => fixer.insertTextBefore(testFuncBody[0], text)
+            }))
+          });
+          return;
+        }
+
+        if ((0, _utils.isSupportedAccessor)(testFuncFirstLine.callee.property, 'hasAssertions')) {
+          if (testFuncFirstLine.arguments.length) {
+            context.report({
+              messageId: 'hasAssertionsTakesNoArguments',
+              node: testFuncFirstLine.callee.property,
+              suggest: [suggestRemovingExtraArguments(testFuncFirstLine.arguments, 0)]
+            });
+          }
+
+          return;
+        }
+
+        if (!(0, _utils.hasOnlyOneArgument)(testFuncFirstLine)) {
+          let {
+            loc
+          } = testFuncFirstLine.callee.property;
+          const suggest = [];
+
+          if (testFuncFirstLine.arguments.length) {
+            loc = testFuncFirstLine.arguments[1].loc;
+            suggest.push(suggestRemovingExtraArguments(testFuncFirstLine.arguments, 1));
+          }
+
+          context.report({
+            messageId: 'assertionsRequiresOneArgument',
+            suggest,
+            loc
+          });
+          return;
+        }
+
+        const [arg] = testFuncFirstLine.arguments;
+
+        if (arg.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof arg.value === 'number' && Number.isInteger(arg.value)) {
+          return;
+        }
+
+        context.report({
+          messageId: 'assertionsRequiresNumberArgument',
+          node: arg
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-expect-resolves.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-expect-resolves.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-expect-resolves.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,48 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Prefer `await expect(...).resolves` over `expect(await ...)` syntax',
+      recommended: false
+    },
+    fixable: 'code',
+    messages: {
+      expectResolves: 'Use `await expect(...).resolves instead.'
+    },
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+  create: context => ({
+    CallExpression(node) {
+      const [awaitNode] = node.arguments;
+
+      if ((0, _utils.isExpectCall)(node) && (awaitNode === null || awaitNode === void 0 ? void 0 : awaitNode.type) === _experimentalUtils.AST_NODE_TYPES.AwaitExpression) {
+        context.report({
+          node: node.arguments[0],
+          messageId: 'expectResolves',
+
+          fix(fixer) {
+            return [fixer.insertTextBefore(node, 'await '), fixer.removeRange([awaitNode.range[0], awaitNode.argument.range[0]]), fixer.insertTextAfter(node, '.resolves')];
+          }
+
+        });
+      }
+    }
+
+  })
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-hooks-on-top.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-hooks-on-top.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-hooks-on-top.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,53 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest having hooks before any test cases',
+      recommended: false
+    },
+    messages: {
+      noHookOnTop: 'Hooks should come before test cases'
+    },
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    const hooksContext = [false];
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isHook)(node) && (0, _utils.isTestCaseCall)(node)) {
+          hooksContext[hooksContext.length - 1] = true;
+        }
+
+        if (hooksContext[hooksContext.length - 1] && (0, _utils.isHook)(node)) {
+          context.report({
+            messageId: 'noHookOnTop',
+            node
+          });
+        }
+
+        hooksContext.push(false);
+      },
+
+      'CallExpression:exit'() {
+        hooksContext.pop();
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-lowercase-title.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-lowercase-title.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-lowercase-title.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,150 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+const hasStringAsFirstArgument = node => node.arguments[0] && (0, _utils.isStringNode)(node.arguments[0]);
+
+const findNodeNameAndArgument = node => {
+  if (!((0, _utils.isTestCaseCall)(node) || (0, _utils.isDescribeCall)(node))) {
+    return null;
+  }
+
+  if (!hasStringAsFirstArgument(node)) {
+    return null;
+  }
+
+  return [(0, _utils.getNodeName)(node).split('.')[0], node.arguments[0]];
+};
+
+const populateIgnores = ignore => {
+  const ignores = [];
+
+  if (ignore.includes(_utils.DescribeAlias.describe)) {
+    ignores.push(...Object.keys(_utils.DescribeAlias));
+  }
+
+  if (ignore.includes(_utils.TestCaseName.test)) {
+    ignores.push(...Object.keys(_utils.TestCaseName).filter(k => k.endsWith(_utils.TestCaseName.test)));
+  }
+
+  if (ignore.includes(_utils.TestCaseName.it)) {
+    ignores.push(...Object.keys(_utils.TestCaseName).filter(k => k.endsWith(_utils.TestCaseName.it)));
+  }
+
+  return ignores;
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    type: 'suggestion',
+    docs: {
+      description: 'Enforce lowercase test names',
+      category: 'Best Practices',
+      recommended: false
+    },
+    fixable: 'code',
+    messages: {
+      unexpectedLowercase: '`{{ method }}`s should begin with lowercase'
+    },
+    schema: [{
+      type: 'object',
+      properties: {
+        ignore: {
+          type: 'array',
+          items: {
+            enum: [_utils.DescribeAlias.describe, _utils.TestCaseName.test, _utils.TestCaseName.it]
+          },
+          additionalItems: false
+        },
+        allowedPrefixes: {
+          type: 'array',
+          items: {
+            type: 'string'
+          },
+          additionalItems: false
+        },
+        ignoreTopLevelDescribe: {
+          type: 'boolean',
+          default: false
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+  defaultOptions: [{
+    ignore: [],
+    allowedPrefixes: [],
+    ignoreTopLevelDescribe: false
+  }],
+
+  create(context, [{
+    ignore = [],
+    allowedPrefixes = [],
+    ignoreTopLevelDescribe
+  }]) {
+    const ignores = populateIgnores(ignore);
+    let numberOfDescribeBlocks = 0;
+    return {
+      CallExpression(node) {
+        if ((0, _utils.isDescribeCall)(node)) {
+          numberOfDescribeBlocks++;
+
+          if (ignoreTopLevelDescribe && numberOfDescribeBlocks === 1) {
+            return;
+          }
+        }
+
+        const results = findNodeNameAndArgument(node);
+
+        if (!results) {
+          return;
+        }
+
+        const [name, firstArg] = results;
+        const description = (0, _utils.getStringValue)(firstArg);
+
+        if (allowedPrefixes.some(name => description.startsWith(name))) {
+          return;
+        }
+
+        const firstCharacter = description.charAt(0);
+
+        if (!firstCharacter || firstCharacter === firstCharacter.toLowerCase() || ignores.includes(name)) {
+          return;
+        }
+
+        context.report({
+          messageId: 'unexpectedLowercase',
+          node: node.arguments[0],
+          data: {
+            method: name
+          },
+
+          fix(fixer) {
+            const description = (0, _utils.getStringValue)(firstArg);
+            const rangeIgnoringQuotes = [firstArg.range[0] + 1, firstArg.range[1] - 1];
+            const newDescription = description.substring(0, 1).toLowerCase() + description.substring(1);
+            return [fixer.replaceTextRange(rangeIgnoringQuotes, newDescription)];
+          }
+
+        });
+      },
+
+      'CallExpression:exit'(node) {
+        if ((0, _utils.isDescribeCall)(node)) {
+          numberOfDescribeBlocks--;
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-spy-on.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-spy-on.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-spy-on.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,89 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const findNodeObject = node => {
+  if ('object' in node) {
+    return node.object;
+  }
+
+  if (node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
+    return node.callee.object;
+  }
+
+  return null;
+};
+
+const getJestFnCall = node => {
+  if (node.type !== _experimentalUtils.AST_NODE_TYPES.CallExpression && node.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
+    return null;
+  }
+
+  const obj = findNodeObject(node);
+
+  if (!obj) {
+    return null;
+  }
+
+  if (obj.type === _experimentalUtils.AST_NODE_TYPES.Identifier) {
+    return node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.getNodeName)(node.callee) === 'jest.fn' ? node : null;
+  }
+
+  return getJestFnCall(obj);
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest using `jest.spyOn()`',
+      recommended: false
+    },
+    messages: {
+      useJestSpyOn: 'Use jest.spyOn() instead.'
+    },
+    fixable: 'code',
+    schema: [],
+    type: 'suggestion'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      AssignmentExpression(node) {
+        const {
+          left,
+          right
+        } = node;
+        if (left.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) return;
+        const jestFnCall = getJestFnCall(right);
+        if (!jestFnCall) return;
+        context.report({
+          node,
+          messageId: 'useJestSpyOn',
+
+          fix(fixer) {
+            const leftPropQuote = left.property.type === _experimentalUtils.AST_NODE_TYPES.Identifier ? "'" : '';
+            const [arg] = jestFnCall.arguments;
+            const argSource = arg && context.getSourceCode().getText(arg);
+            const mockImplementation = argSource ? `.mockImplementation(${argSource})` : '.mockImplementation()';
+            return [fixer.insertTextBefore(left, `jest.spyOn(`), fixer.replaceTextRange([left.object.range[1], left.property.range[0]], `, ${leftPropQuote}`), fixer.replaceTextRange([left.property.range[1], jestFnCall.range[1]], `${leftPropQuote})${mockImplementation}`)];
+          }
+
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-strict-equal.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-strict-equal.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-strict-equal.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,57 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest using `toStrictEqual()`',
+      recommended: false,
+      suggestion: true
+    },
+    messages: {
+      useToStrictEqual: 'Use `toStrictEqual()` instead',
+      suggestReplaceWithStrictEqual: 'Replace with `toStrictEqual()`'
+    },
+    type: 'suggestion',
+    schema: [],
+    hasSuggestions: true
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          matcher
+        } = (0, _utils.parseExpectCall)(node);
+
+        if (matcher && (0, _utils.isParsedEqualityMatcherCall)(matcher, _utils.EqualityMatcher.toEqual)) {
+          context.report({
+            messageId: 'useToStrictEqual',
+            node: matcher.node.property,
+            suggest: [{
+              messageId: 'suggestReplaceWithStrictEqual',
+              fix: fixer => [fixer.replaceText(matcher.node.property, _utils.EqualityMatcher.toStrictEqual)]
+            }]
+          });
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-to-be.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-to-be.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-to-be.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,136 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const isNullLiteral = node => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && node.value === null;
+/**
+ * Checks if the given `ParsedEqualityMatcherCall` is a call to one of the equality matchers,
+ * with a `null` literal as the sole argument.
+ */
+
+
+const isNullEqualityMatcher = matcher => isNullLiteral(getFirstArgument(matcher));
+
+const isFirstArgumentIdentifier = (matcher, name) => (0, _utils.isIdentifier)(getFirstArgument(matcher), name);
+
+const shouldUseToBe = matcher => {
+  const firstArg = getFirstArgument(matcher);
+
+  if (firstArg.type === _experimentalUtils.AST_NODE_TYPES.Literal) {
+    // regex literals are classed as literals, but they're actually objects
+    // which means "toBe" will give different results than other matchers
+    return !('regex' in firstArg);
+  }
+
+  return firstArg.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral;
+};
+
+const getFirstArgument = matcher => {
+  return (0, _utils.followTypeAssertionChain)(matcher.arguments[0]);
+};
+
+const reportPreferToBe = (context, whatToBe, matcher, modifier) => {
+  const modifierNode = (modifier === null || modifier === void 0 ? void 0 : modifier.negation) || (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils.ModifierName.not && (modifier === null || modifier === void 0 ? void 0 : modifier.node);
+  context.report({
+    messageId: `useToBe${whatToBe}`,
+
+    fix(fixer) {
+      var _matcher$arguments;
+
+      const fixes = [fixer.replaceText(matcher.node.property, `toBe${whatToBe}`)];
+
+      if ((_matcher$arguments = matcher.arguments) !== null && _matcher$arguments !== void 0 && _matcher$arguments.length && whatToBe !== '') {
+        fixes.push(fixer.remove(matcher.arguments[0]));
+      }
+
+      if (modifierNode) {
+        fixes.push(fixer.removeRange([modifierNode.property.range[0] - 1, modifierNode.property.range[1]]));
+      }
+
+      return fixes;
+    },
+
+    node: matcher.node.property
+  });
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest using `toBe()` for primitive literals',
+      recommended: false
+    },
+    messages: {
+      useToBe: 'Use `toBe` when expecting primitive literals',
+      useToBeUndefined: 'Use `toBeUndefined` instead',
+      useToBeDefined: 'Use `toBeDefined` instead',
+      useToBeNull: 'Use `toBeNull` instead',
+      useToBeNaN: 'Use `toBeNaN` instead'
+    },
+    fixable: 'code',
+    type: 'suggestion',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          matcher,
+          modifier
+        } = (0, _utils.parseExpectCall)(node);
+
+        if (!matcher) {
+          return;
+        }
+
+        if (((modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils.ModifierName.not || modifier !== null && modifier !== void 0 && modifier.negation) && ['toBeUndefined', 'toBeDefined'].includes(matcher.name)) {
+          reportPreferToBe(context, matcher.name === 'toBeDefined' ? 'Undefined' : 'Defined', matcher, modifier);
+          return;
+        }
+
+        if (!(0, _utils.isParsedEqualityMatcherCall)(matcher)) {
+          return;
+        }
+
+        if (isNullEqualityMatcher(matcher)) {
+          reportPreferToBe(context, 'Null', matcher);
+          return;
+        }
+
+        if (isFirstArgumentIdentifier(matcher, 'undefined')) {
+          const name = (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils.ModifierName.not || modifier !== null && modifier !== void 0 && modifier.negation ? 'Defined' : 'Undefined';
+          reportPreferToBe(context, name, matcher, modifier);
+          return;
+        }
+
+        if (isFirstArgumentIdentifier(matcher, 'NaN')) {
+          reportPreferToBe(context, 'NaN', matcher);
+          return;
+        }
+
+        if (shouldUseToBe(matcher) && matcher.name !== _utils.EqualityMatcher.toBe) {
+          reportPreferToBe(context, '', matcher);
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-to-contain.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-to-contain.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-to-contain.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,98 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const isBooleanLiteral = node => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof node.value === 'boolean';
+
+/**
+ * Checks if the given `ParsedExpectMatcher` is a call to one of the equality matchers,
+ * with a boolean literal as the sole argument.
+ *
+ * @example javascript
+ * toBe(true);
+ * toEqual(false);
+ *
+ * @param {ParsedExpectMatcher} matcher
+ *
+ * @return {matcher is ParsedBooleanEqualityMatcher}
+ */
+const isBooleanEqualityMatcher = matcher => (0, _utils.isParsedEqualityMatcherCall)(matcher) && isBooleanLiteral((0, _utils.followTypeAssertionChain)(matcher.arguments[0]));
+
+/**
+ * Checks if the given `node` is a `CallExpression` representing the calling
+ * of an `includes`-like method that can be 'fixed' (using `toContain`).
+ *
+ * @param {CallExpression} node
+ *
+ * @return {node is FixableIncludesCallExpression}
+ */
+const isFixableIncludesCallExpression = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property, 'includes') && (0, _utils.hasOnlyOneArgument)(node); // expect(array.includes(<value>)[not.]{toBe,toEqual}(<boolean>)
+
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest using `toContain()`',
+      recommended: false
+    },
+    messages: {
+      useToContain: 'Use toContain() instead'
+    },
+    fixable: 'code',
+    type: 'suggestion',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          expect: {
+            arguments: [includesCall],
+            range: [, expectCallEnd]
+          },
+          matcher,
+          modifier
+        } = (0, _utils.parseExpectCall)(node);
+
+        if (!matcher || !includesCall || modifier && modifier.name !== _utils.ModifierName.not || !isBooleanEqualityMatcher(matcher) || !isFixableIncludesCallExpression(includesCall)) {
+          return;
+        }
+
+        context.report({
+          fix(fixer) {
+            const sourceCode = context.getSourceCode(); // we need to negate the expectation if the current expected
+            // value is itself negated by the "not" modifier
+
+            const addNotModifier = (0, _utils.followTypeAssertionChain)(matcher.arguments[0]).value === !!modifier;
+            return [// remove the "includes" call entirely
+            fixer.removeRange([includesCall.callee.property.range[0] - 1, includesCall.range[1]]), // replace the current matcher with "toContain", adding "not" if needed
+            fixer.replaceTextRange([expectCallEnd, matcher.node.range[1]], addNotModifier ? `.${_utils.ModifierName.not}.toContain` : '.toContain'), // replace the matcher argument with the value from the "includes"
+            fixer.replaceText(matcher.arguments[0], sourceCode.getText(includesCall.arguments[0]))];
+          },
+
+          messageId: 'useToContain',
+          node: (modifier || matcher).node.property
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-to-have-length.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-to-have-length.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-to-have-length.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,64 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest using `toHaveLength()`',
+      recommended: false
+    },
+    messages: {
+      useToHaveLength: 'Use toHaveLength() instead'
+    },
+    fixable: 'code',
+    type: 'suggestion',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          expect: {
+            arguments: [argument]
+          },
+          matcher
+        } = (0, _utils.parseExpectCall)(node);
+
+        if (!matcher || !(0, _utils.isParsedEqualityMatcherCall)(matcher) || (argument === null || argument === void 0 ? void 0 : argument.type) !== _experimentalUtils.AST_NODE_TYPES.MemberExpression || !(0, _utils.isSupportedAccessor)(argument.property, 'length')) {
+          return;
+        }
+
+        context.report({
+          fix(fixer) {
+            return [// remove the "length" property accessor
+            fixer.removeRange([argument.property.range[0] - 1, argument.range[1]]), // replace the current matcher with "toHaveLength"
+            fixer.replaceTextRange([matcher.node.object.range[1], matcher.node.range[1]], '.toHaveLength')];
+          },
+
+          messageId: 'useToHaveLength',
+          node: matcher.node.property
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-todo.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-todo.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/prefer-todo.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,76 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+function isEmptyFunction(node) {
+  if (!(0, _utils.isFunction)(node)) {
+    return false;
+  }
+
+  return node.body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement && !node.body.body.length;
+}
+
+function createTodoFixer(node, fixer) {
+  const testName = (0, _utils.getNodeName)(node).split('.').shift();
+  return fixer.replaceText(node.callee, `${testName}.todo`);
+}
+
+const isTargetedTestCase = node => (0, _utils.isTestCaseCall)(node) && [_utils.TestCaseName.it, _utils.TestCaseName.test, 'it.skip', 'test.skip'].includes((0, _utils.getNodeName)(node));
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Suggest using `test.todo`',
+      recommended: false
+    },
+    messages: {
+      emptyTest: 'Prefer todo test case over empty test case',
+      unimplementedTest: 'Prefer todo test case over unimplemented test case'
+    },
+    fixable: 'code',
+    schema: [],
+    type: 'layout'
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        const [title, callback] = node.arguments;
+
+        if (!title || !isTargetedTestCase(node) || !(0, _utils.isStringNode)(title)) {
+          return;
+        }
+
+        if (callback && isEmptyFunction(callback)) {
+          context.report({
+            messageId: 'emptyTest',
+            node,
+            fix: fixer => [fixer.removeRange([title.range[1], callback.range[1]]), createTodoFixer(node, fixer)]
+          });
+        }
+
+        if ((0, _utils.hasOnlyOneArgument)(node)) {
+          context.report({
+            messageId: 'unimplementedTest',
+            node,
+            fix: fixer => [createTodoFixer(node, fixer)]
+          });
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/require-hook.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/require-hook.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/require-hook.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,121 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const isJestFnCall = node => {
+  var _getNodeName;
+
+  if ((0, _utils.isDescribeCall)(node) || (0, _utils.isTestCaseCall)(node) || (0, _utils.isHook)(node)) {
+    return true;
+  }
+
+  return !!((_getNodeName = (0, _utils.getNodeName)(node)) !== null && _getNodeName !== void 0 && _getNodeName.startsWith('jest.'));
+};
+
+const isNullOrUndefined = node => {
+  return node.type === _experimentalUtils.AST_NODE_TYPES.Literal && node.value === null || (0, _utils.isIdentifier)(node, 'undefined');
+};
+
+const shouldBeInHook = (node, allowedFunctionCalls = []) => {
+  switch (node.type) {
+    case _experimentalUtils.AST_NODE_TYPES.ExpressionStatement:
+      return shouldBeInHook(node.expression, allowedFunctionCalls);
+
+    case _experimentalUtils.AST_NODE_TYPES.CallExpression:
+      return !(isJestFnCall(node) || allowedFunctionCalls.includes((0, _utils.getNodeName)(node)));
+
+    case _experimentalUtils.AST_NODE_TYPES.VariableDeclaration:
+      {
+        if (node.kind === 'const') {
+          return false;
+        }
+
+        return node.declarations.some(({
+          init
+        }) => init !== null && !isNullOrUndefined(init));
+      }
+
+    default:
+      return false;
+  }
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Require setup and teardown code to be within a hook',
+      recommended: false
+    },
+    messages: {
+      useHook: 'This should be done within a hook'
+    },
+    type: 'suggestion',
+    schema: [{
+      type: 'object',
+      properties: {
+        allowedFunctionCalls: {
+          type: 'array',
+          items: {
+            type: 'string'
+          }
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+  defaultOptions: [{
+    allowedFunctionCalls: []
+  }],
+
+  create(context) {
+    var _context$options$;
+
+    const {
+      allowedFunctionCalls
+    } = (_context$options$ = context.options[0]) !== null && _context$options$ !== void 0 ? _context$options$ : {};
+
+    const checkBlockBody = body => {
+      for (const statement of body) {
+        if (shouldBeInHook(statement, allowedFunctionCalls)) {
+          context.report({
+            node: statement,
+            messageId: 'useHook'
+          });
+        }
+      }
+    };
+
+    return {
+      Program(program) {
+        checkBlockBody(program.body);
+      },
+
+      CallExpression(node) {
+        if (!(0, _utils.isDescribeCall)(node) || node.arguments.length < 2) {
+          return;
+        }
+
+        const [, testFn] = node.arguments;
+
+        if (!(0, _utils.isFunction)(testFn) || testFn.body.type !== _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
+          return;
+        }
+
+        checkBlockBody(testFn.body.body);
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/require-to-throw-message.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/require-to-throw-message.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/require-to-throw-message.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,57 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Require a message for `toThrow()`',
+      recommended: false
+    },
+    messages: {
+      addErrorMessage: 'Add an error message to {{ matcherName }}()'
+    },
+    type: 'suggestion',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        var _matcher$arguments;
+
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          matcher,
+          modifier
+        } = (0, _utils.parseExpectCall)(node);
+
+        if ((matcher === null || matcher === void 0 ? void 0 : (_matcher$arguments = matcher.arguments) === null || _matcher$arguments === void 0 ? void 0 : _matcher$arguments.length) === 0 && ['toThrow', 'toThrowError'].includes(matcher.name) && (!modifier || !(modifier.name === _utils.ModifierName.not || modifier.negation))) {
+          // Look for `toThrow` calls with no arguments.
+          context.report({
+            messageId: 'addErrorMessage',
+            data: {
+              matcherName: matcher.name
+            },
+            node: matcher.node.property
+          });
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/require-top-level-describe.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/require-top-level-describe.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/require-top-level-describe.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,100 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+const messages = {
+  tooManyDescribes: 'There should not be more than {{ max }} describe{{ s }} at the top level',
+  unexpectedTestCase: 'All test cases must be wrapped in a describe block.',
+  unexpectedHook: 'All hooks must be wrapped in a describe block.'
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Require test cases and hooks to be inside a `describe` block',
+      recommended: false
+    },
+    messages,
+    type: 'suggestion',
+    schema: [{
+      type: 'object',
+      properties: {
+        maxNumberOfTopLevelDescribes: {
+          type: 'number',
+          minimum: 1
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+  defaultOptions: [{}],
+
+  create(context) {
+    var _context$options$;
+
+    const {
+      maxNumberOfTopLevelDescribes = Infinity
+    } = (_context$options$ = context.options[0]) !== null && _context$options$ !== void 0 ? _context$options$ : {};
+    let numberOfTopLevelDescribeBlocks = 0;
+    let numberOfDescribeBlocks = 0;
+    return {
+      CallExpression(node) {
+        if ((0, _utils.isDescribeCall)(node)) {
+          numberOfDescribeBlocks++;
+
+          if (numberOfDescribeBlocks === 1) {
+            numberOfTopLevelDescribeBlocks++;
+
+            if (numberOfTopLevelDescribeBlocks > maxNumberOfTopLevelDescribes) {
+              context.report({
+                node,
+                messageId: 'tooManyDescribes',
+                data: {
+                  max: maxNumberOfTopLevelDescribes,
+                  s: maxNumberOfTopLevelDescribes === 1 ? '' : 's'
+                }
+              });
+            }
+          }
+
+          return;
+        }
+
+        if (numberOfDescribeBlocks === 0) {
+          if ((0, _utils.isTestCaseCall)(node)) {
+            context.report({
+              node,
+              messageId: 'unexpectedTestCase'
+            });
+            return;
+          }
+
+          if ((0, _utils.isHook)(node)) {
+            context.report({
+              node,
+              messageId: 'unexpectedHook'
+            });
+            return;
+          }
+        }
+      },
+
+      'CallExpression:exit'(node) {
+        if ((0, _utils.isDescribeCall)(node)) {
+          numberOfDescribeBlocks--;
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/unbound-method.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/unbound-method.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/unbound-method.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,113 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _utils = require("./utils");
+
+const toThrowMatchers = ['toThrow', 'toThrowError', 'toThrowErrorMatchingSnapshot', 'toThrowErrorMatchingInlineSnapshot'];
+
+const isJestExpectToThrowCall = node => {
+  if (!(0, _utils.isExpectCall)(node)) {
+    return false;
+  }
+
+  const {
+    matcher
+  } = (0, _utils.parseExpectCall)(node);
+
+  if (!matcher) {
+    return false;
+  }
+
+  return !toThrowMatchers.includes(matcher.name);
+};
+
+const baseRule = (() => {
+  try {
+    // eslint-disable-next-line @typescript-eslint/no-require-imports
+    const TSESLintPlugin = require('@typescript-eslint/eslint-plugin');
+
+    return TSESLintPlugin.rules['unbound-method'];
+  } catch (e) {
+    const error = e;
+
+    if (error.code === 'MODULE_NOT_FOUND') {
+      return null;
+    }
+
+    throw error;
+  }
+})();
+
+const tryCreateBaseRule = context => {
+  try {
+    return baseRule === null || baseRule === void 0 ? void 0 : baseRule.create(context);
+  } catch {
+    return null;
+  }
+};
+
+const DEFAULT_MESSAGE = 'This rule requires `@typescript-eslint/eslint-plugin`';
+
+var _default = (0, _utils.createRule)({
+  defaultOptions: [{
+    ignoreStatic: false
+  }],
+  ...baseRule,
+  name: __filename,
+  meta: {
+    messages: {
+      unbound: DEFAULT_MESSAGE,
+      unboundWithoutThisAnnotation: DEFAULT_MESSAGE
+    },
+    schema: [],
+    type: 'problem',
+    ...(baseRule === null || baseRule === void 0 ? void 0 : baseRule.meta),
+    docs: {
+      category: 'Best Practices',
+      description: 'Enforces unbound methods are called with their expected scope',
+      requiresTypeChecking: true,
+      ...(baseRule === null || baseRule === void 0 ? void 0 : baseRule.meta.docs),
+      recommended: false
+    }
+  },
+
+  create(context) {
+    const baseSelectors = tryCreateBaseRule(context);
+
+    if (!baseSelectors) {
+      return {};
+    }
+
+    let inExpectToThrowCall = false;
+    return { ...baseSelectors,
+
+      CallExpression(node) {
+        inExpectToThrowCall = isJestExpectToThrowCall(node);
+      },
+
+      'CallExpression:exit'(node) {
+        if (inExpectToThrowCall && isJestExpectToThrowCall(node)) {
+          inExpectToThrowCall = false;
+        }
+      },
+
+      MemberExpression(node) {
+        var _baseSelectors$Member;
+
+        if (inExpectToThrowCall) {
+          return;
+        }
+
+        (_baseSelectors$Member = baseSelectors.MemberExpression) === null || _baseSelectors$Member === void 0 ? void 0 : _baseSelectors$Member.call(baseSelectors, node);
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/utils.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/utils.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,513 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.getAccessorValue = exports.followTypeAssertionChain = exports.createRule = exports.TestCaseProperty = exports.TestCaseName = exports.ModifierName = exports.HookName = exports.EqualityMatcher = exports.DescribeProperty = exports.DescribeAlias = void 0;
+exports.getNodeName = getNodeName;
+exports.scopeHasLocalReference = exports.parseExpectCall = exports.isTestCaseCall = exports.isSupportedAccessor = exports.isStringNode = exports.isParsedEqualityMatcherCall = exports.isIdentifier = exports.isHook = exports.isFunction = exports.isExpectMember = exports.isExpectCall = exports.isDescribeCall = exports.hasOnlyOneArgument = exports.getTestCallExpressionsFromDeclaredVariables = exports.getStringValue = void 0;
+
+var _path = require("path");
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _package = require("../../package.json");
+
+const REPO_URL = 'https://github.com/jest-community/eslint-plugin-jest';
+
+const createRule = _experimentalUtils.ESLintUtils.RuleCreator(name => {
+  const ruleName = (0, _path.parse)(name).name;
+  return `${REPO_URL}/blob/v${_package.version}/docs/rules/${ruleName}.md`;
+});
+
+exports.createRule = createRule;
+
+const isTypeCastExpression = node => node.type === _experimentalUtils.AST_NODE_TYPES.TSAsExpression || node.type === _experimentalUtils.AST_NODE_TYPES.TSTypeAssertion;
+
+const followTypeAssertionChain = expression => isTypeCastExpression(expression) ? followTypeAssertionChain(expression.expression) : expression;
+/**
+ * A `Literal` with a `value` of type `string`.
+ */
+
+
+exports.followTypeAssertionChain = followTypeAssertionChain;
+
+/**
+ * Checks if the given `node` is a `StringLiteral`.
+ *
+ * If a `value` is provided & the `node` is a `StringLiteral`,
+ * the `value` will be compared to that of the `StringLiteral`.
+ *
+ * @param {Node} node
+ * @param {V} [value]
+ *
+ * @return {node is StringLiteral<V>}
+ *
+ * @template V
+ */
+const isStringLiteral = (node, value) => node.type === _experimentalUtils.AST_NODE_TYPES.Literal && typeof node.value === 'string' && (value === undefined || node.value === value);
+
+/**
+ * Checks if the given `node` is a `TemplateLiteral`.
+ *
+ * Complex `TemplateLiteral`s are not considered specific, and so will return `false`.
+ *
+ * If a `value` is provided & the `node` is a `TemplateLiteral`,
+ * the `value` will be compared to that of the `TemplateLiteral`.
+ *
+ * @param {Node} node
+ * @param {V} [value]
+ *
+ * @return {node is TemplateLiteral<V>}
+ *
+ * @template V
+ */
+const isTemplateLiteral = (node, value) => node.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && node.quasis.length === 1 && ( // bail out if not simple
+value === undefined || node.quasis[0].value.raw === value);
+
+/**
+ * Checks if the given `node` is a {@link StringNode}.
+ *
+ * @param {Node} node
+ * @param {V} [specifics]
+ *
+ * @return {node is StringNode}
+ *
+ * @template V
+ */
+const isStringNode = (node, specifics) => isStringLiteral(node, specifics) || isTemplateLiteral(node, specifics);
+/**
+ * Gets the value of the given `StringNode`.
+ *
+ * If the `node` is a `TemplateLiteral`, the `raw` value is used;
+ * otherwise, `value` is returned instead.
+ *
+ * @param {StringNode<S>} node
+ *
+ * @return {S}
+ *
+ * @template S
+ */
+
+
+exports.isStringNode = isStringNode;
+
+const getStringValue = node => isTemplateLiteral(node) ? node.quasis[0].value.raw : node.value;
+/**
+ * Represents a `MemberExpression` with a "known" `property`.
+ */
+
+
+exports.getStringValue = getStringValue;
+
+/**
+ * Guards that the given `call` has only one `argument`.
+ *
+ * @param {CallExpression} call
+ *
+ * @return {call is CallExpressionWithSingleArgument}
+ */
+const hasOnlyOneArgument = call => call.arguments.length === 1;
+/**
+ * An `Identifier` with a known `name` value - i.e `expect`.
+ */
+
+
+exports.hasOnlyOneArgument = hasOnlyOneArgument;
+
+/**
+ * Checks if the given `node` is an `Identifier`.
+ *
+ * If a `name` is provided, & the `node` is an `Identifier`,
+ * the `name` will be compared to that of the `identifier`.
+ *
+ * @param {Node} node
+ * @param {V} [name]
+ *
+ * @return {node is KnownIdentifier<Name>}
+ *
+ * @template V
+ */
+const isIdentifier = (node, name) => node.type === _experimentalUtils.AST_NODE_TYPES.Identifier && (name === undefined || node.name === name);
+/**
+ * Checks if the given `node` is a "supported accessor".
+ *
+ * This means that it's a node can be used to access properties,
+ * and who's "value" can be statically determined.
+ *
+ * `MemberExpression` nodes most commonly contain accessors,
+ * but it's possible for other nodes to contain them.
+ *
+ * If a `value` is provided & the `node` is an `AccessorNode`,
+ * the `value` will be compared to that of the `AccessorNode`.
+ *
+ * Note that `value` here refers to the normalised value.
+ * The property that holds the value is not always called `name`.
+ *
+ * @param {Node} node
+ * @param {V} [value]
+ *
+ * @return {node is AccessorNode<V>}
+ *
+ * @template V
+ */
+
+
+exports.isIdentifier = isIdentifier;
+
+const isSupportedAccessor = (node, value) => isIdentifier(node, value) || isStringNode(node, value);
+/**
+ * Gets the value of the given `AccessorNode`,
+ * account for the different node types.
+ *
+ * @param {AccessorNode<S>} accessor
+ *
+ * @return {S}
+ *
+ * @template S
+ */
+
+
+exports.isSupportedAccessor = isSupportedAccessor;
+
+const getAccessorValue = accessor => accessor.type === _experimentalUtils.AST_NODE_TYPES.Identifier ? accessor.name : getStringValue(accessor);
+
+exports.getAccessorValue = getAccessorValue;
+
+/**
+ * Checks if the given `node` is a valid `ExpectCall`.
+ *
+ * In order to be an `ExpectCall`, the `node` must:
+ *  * be a `CallExpression`,
+ *  * have an accessor named 'expect',
+ *  * have a `parent`.
+ *
+ * @param {Node} node
+ *
+ * @return {node is ExpectCall}
+ */
+const isExpectCall = node => node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && isSupportedAccessor(node.callee, 'expect') && node.parent !== undefined;
+
+exports.isExpectCall = isExpectCall;
+
+const isExpectMember = (node, name) => node.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && isSupportedAccessor(node.property, name);
+/**
+ * Represents all the jest matchers.
+ */
+
+
+exports.isExpectMember = isExpectMember;
+let ModifierName;
+exports.ModifierName = ModifierName;
+
+(function (ModifierName) {
+  ModifierName["not"] = "not";
+  ModifierName["rejects"] = "rejects";
+  ModifierName["resolves"] = "resolves";
+})(ModifierName || (exports.ModifierName = ModifierName = {}));
+
+let EqualityMatcher;
+exports.EqualityMatcher = EqualityMatcher;
+
+(function (EqualityMatcher) {
+  EqualityMatcher["toBe"] = "toBe";
+  EqualityMatcher["toEqual"] = "toEqual";
+  EqualityMatcher["toStrictEqual"] = "toStrictEqual";
+})(EqualityMatcher || (exports.EqualityMatcher = EqualityMatcher = {}));
+
+const isParsedEqualityMatcherCall = (matcher, name) => (name ? matcher.name === name : EqualityMatcher.hasOwnProperty(matcher.name)) && matcher.arguments !== null && matcher.arguments.length === 1;
+/**
+ * Represents a parsed expect matcher, such as `toBe`, `toContain`, and so on.
+ */
+
+
+exports.isParsedEqualityMatcherCall = isParsedEqualityMatcherCall;
+
+const parseExpectMember = expectMember => ({
+  name: getAccessorValue(expectMember.property),
+  node: expectMember
+});
+
+const reparseAsMatcher = parsedMember => ({ ...parsedMember,
+
+  /**
+   * The arguments being passed to this `Matcher`, if any.
+   *
+   * If this matcher isn't called, this will be `null`.
+   */
+  arguments: parsedMember.node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? parsedMember.node.parent.arguments : null
+});
+/**
+ * Re-parses the given `parsedMember` as a `ParsedExpectModifier`.
+ *
+ * If the given `parsedMember` does not have a `name` of a valid `Modifier`,
+ * an exception will be thrown.
+ *
+ * @param {ParsedExpectMember<ModifierName>} parsedMember
+ *
+ * @return {ParsedExpectModifier}
+ */
+
+
+const reparseMemberAsModifier = parsedMember => {
+  if (isSpecificMember(parsedMember, ModifierName.not)) {
+    return parsedMember;
+  }
+  /* istanbul ignore if */
+
+
+  if (!isSpecificMember(parsedMember, ModifierName.resolves) && !isSpecificMember(parsedMember, ModifierName.rejects)) {
+    // ts doesn't think that the ModifierName.not check is the direct inverse as the above two checks
+    // todo: impossible at runtime, but can't be typed w/o negation support
+    throw new Error(`modifier name must be either "${ModifierName.resolves}" or "${ModifierName.rejects}" (got "${parsedMember.name}")`);
+  }
+
+  const negation = isExpectMember(parsedMember.node.parent, ModifierName.not) ? parsedMember.node.parent : undefined;
+  return { ...parsedMember,
+    negation
+  };
+};
+
+const isSpecificMember = (member, specific) => member.name === specific;
+/**
+ * Checks if the given `ParsedExpectMember` should be re-parsed as an `ParsedExpectModifier`.
+ *
+ * @param {ParsedExpectMember} member
+ *
+ * @return {member is ParsedExpectMember<ModifierName>}
+ */
+
+
+const shouldBeParsedExpectModifier = member => ModifierName.hasOwnProperty(member.name);
+
+const parseExpectCall = expect => {
+  const expectation = {
+    expect
+  };
+
+  if (!isExpectMember(expect.parent)) {
+    return expectation;
+  }
+
+  const parsedMember = parseExpectMember(expect.parent);
+
+  if (!shouldBeParsedExpectModifier(parsedMember)) {
+    expectation.matcher = reparseAsMatcher(parsedMember);
+    return expectation;
+  }
+
+  const modifier = expectation.modifier = reparseMemberAsModifier(parsedMember);
+  const memberNode = modifier.negation || modifier.node;
+
+  if (!isExpectMember(memberNode.parent)) {
+    return expectation;
+  }
+
+  expectation.matcher = reparseAsMatcher(parseExpectMember(memberNode.parent));
+  return expectation;
+};
+
+exports.parseExpectCall = parseExpectCall;
+let DescribeAlias;
+exports.DescribeAlias = DescribeAlias;
+
+(function (DescribeAlias) {
+  DescribeAlias["describe"] = "describe";
+  DescribeAlias["fdescribe"] = "fdescribe";
+  DescribeAlias["xdescribe"] = "xdescribe";
+})(DescribeAlias || (exports.DescribeAlias = DescribeAlias = {}));
+
+let TestCaseName;
+exports.TestCaseName = TestCaseName;
+
+(function (TestCaseName) {
+  TestCaseName["fit"] = "fit";
+  TestCaseName["it"] = "it";
+  TestCaseName["test"] = "test";
+  TestCaseName["xit"] = "xit";
+  TestCaseName["xtest"] = "xtest";
+})(TestCaseName || (exports.TestCaseName = TestCaseName = {}));
+
+let HookName;
+exports.HookName = HookName;
+
+(function (HookName) {
+  HookName["beforeAll"] = "beforeAll";
+  HookName["beforeEach"] = "beforeEach";
+  HookName["afterAll"] = "afterAll";
+  HookName["afterEach"] = "afterEach";
+})(HookName || (exports.HookName = HookName = {}));
+
+let DescribeProperty;
+exports.DescribeProperty = DescribeProperty;
+
+(function (DescribeProperty) {
+  DescribeProperty["each"] = "each";
+  DescribeProperty["only"] = "only";
+  DescribeProperty["skip"] = "skip";
+})(DescribeProperty || (exports.DescribeProperty = DescribeProperty = {}));
+
+let TestCaseProperty;
+exports.TestCaseProperty = TestCaseProperty;
+
+(function (TestCaseProperty) {
+  TestCaseProperty["each"] = "each";
+  TestCaseProperty["concurrent"] = "concurrent";
+  TestCaseProperty["only"] = "only";
+  TestCaseProperty["skip"] = "skip";
+  TestCaseProperty["todo"] = "todo";
+})(TestCaseProperty || (exports.TestCaseProperty = TestCaseProperty = {}));
+
+const joinNames = (a, b) => a && b ? `${a}.${b}` : null;
+
+function getNodeName(node) {
+  if (isSupportedAccessor(node)) {
+    return getAccessorValue(node);
+  }
+
+  switch (node.type) {
+    case _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression:
+      return getNodeName(node.tag);
+
+    case _experimentalUtils.AST_NODE_TYPES.MemberExpression:
+      return joinNames(getNodeName(node.object), getNodeName(node.property));
+
+    case _experimentalUtils.AST_NODE_TYPES.NewExpression:
+    case _experimentalUtils.AST_NODE_TYPES.CallExpression:
+      return getNodeName(node.callee);
+  }
+
+  return null;
+}
+
+const isFunction = node => node.type === _experimentalUtils.AST_NODE_TYPES.FunctionExpression || node.type === _experimentalUtils.AST_NODE_TYPES.ArrowFunctionExpression;
+
+exports.isFunction = isFunction;
+
+const isHook = node => node.callee.type === _experimentalUtils.AST_NODE_TYPES.Identifier && HookName.hasOwnProperty(node.callee.name);
+
+exports.isHook = isHook;
+
+const getTestCallExpressionsFromDeclaredVariables = declaredVariables => {
+  return declaredVariables.reduce((acc, {
+    references
+  }) => acc.concat(references.map(({
+    identifier
+  }) => identifier.parent).filter(node => !!node && node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && isTestCaseCall(node))), []);
+};
+
+exports.getTestCallExpressionsFromDeclaredVariables = getTestCallExpressionsFromDeclaredVariables;
+
+const isTestCaseName = node => node.type === _experimentalUtils.AST_NODE_TYPES.Identifier && TestCaseName.hasOwnProperty(node.name);
+
+const isTestCaseProperty = node => isSupportedAccessor(node) && TestCaseProperty.hasOwnProperty(getAccessorValue(node));
+/**
+ * Checks if the given `node` is a *call* to a test case function that would
+ * result in tests being run by `jest`.
+ *
+ * Note that `.each()` does not count as a call in this context, as it will not
+ * result in `jest` running any tests.
+ *
+ * @param {TSESTree.CallExpression} node
+ *
+ * @return {node is JestFunctionCallExpression<TestCaseName>}
+ */
+
+
+const isTestCaseCall = node => {
+  if (isTestCaseName(node.callee)) {
+    return true;
+  }
+
+  const callee = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
+
+  if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && isTestCaseProperty(callee.property)) {
+    // if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
+    if (getAccessorValue(callee.property) === 'each' && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
+      return false;
+    }
+
+    return callee.object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression ? isTestCaseName(callee.object.object) : isTestCaseName(callee.object);
+  }
+
+  return false;
+};
+
+exports.isTestCaseCall = isTestCaseCall;
+
+const isDescribeAlias = node => node.type === _experimentalUtils.AST_NODE_TYPES.Identifier && DescribeAlias.hasOwnProperty(node.name);
+
+const isDescribeProperty = node => isSupportedAccessor(node) && DescribeProperty.hasOwnProperty(getAccessorValue(node));
+/**
+ * Checks if the given `node` is a *call* to a `describe` function that would
+ * result in a `describe` block being created by `jest`.
+ *
+ * Note that `.each()` does not count as a call in this context, as it will not
+ * result in `jest` creating any `describe` blocks.
+ *
+ * @param {TSESTree.CallExpression} node
+ *
+ * @return {node is JestFunctionCallExpression<TestCaseName>}
+ */
+
+
+const isDescribeCall = node => {
+  if (isDescribeAlias(node.callee)) {
+    return true;
+  }
+
+  const callee = node.callee.type === _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression ? node.callee.tag : node.callee.type === _experimentalUtils.AST_NODE_TYPES.CallExpression ? node.callee.callee : node.callee;
+
+  if (callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && isDescribeProperty(callee.property)) {
+    // if we're an `each()`, ensure we're the outer CallExpression (i.e `.each()()`)
+    if (getAccessorValue(callee.property) === 'each' && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.CallExpression) {
+      return false;
+    }
+
+    return callee.object.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression ? isDescribeAlias(callee.object.object) : isDescribeAlias(callee.object);
+  }
+
+  return false;
+};
+
+exports.isDescribeCall = isDescribeCall;
+
+const collectReferences = scope => {
+  const locals = new Set();
+  const unresolved = new Set();
+  let currentScope = scope;
+
+  while (currentScope !== null) {
+    for (const ref of currentScope.variables) {
+      const isReferenceDefined = ref.defs.some(def => {
+        return def.type !== 'ImplicitGlobalVariable';
+      });
+
+      if (isReferenceDefined) {
+        locals.add(ref.name);
+      }
+    }
+
+    for (const ref of currentScope.through) {
+      unresolved.add(ref.identifier.name);
+    }
+
+    currentScope = currentScope.upper;
+  }
+
+  return {
+    locals,
+    unresolved
+  };
+};
+
+const scopeHasLocalReference = (scope, referenceName) => {
+  const references = collectReferences(scope);
+  return (// referenceName was found as a local variable or function declaration.
+    references.locals.has(referenceName) || // referenceName was not found as an unresolved reference,
+    // meaning it is likely not an implicit global reference.
+    !references.unresolved.has(referenceName)
+  );
+};
+
+exports.scopeHasLocalReference = scopeHasLocalReference;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/valid-describe-callback.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/valid-describe-callback.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/valid-describe-callback.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,111 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const paramsLocation = params => {
+  const [first] = params;
+  const last = params[params.length - 1];
+  return {
+    start: first.loc.start,
+    end: last.loc.end
+  };
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    type: 'problem',
+    docs: {
+      category: 'Possible Errors',
+      description: 'Enforce valid `describe()` callback',
+      recommended: 'error'
+    },
+    messages: {
+      nameAndCallback: 'Describe requires name and callback arguments',
+      secondArgumentMustBeFunction: 'Second argument must be function',
+      noAsyncDescribeCallback: 'No async describe callback',
+      unexpectedDescribeArgument: 'Unexpected argument(s) in describe callback',
+      unexpectedReturnInDescribe: 'Unexpected return statement in describe callback'
+    },
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isDescribeCall)(node)) {
+          return;
+        }
+
+        if (node.arguments.length < 1) {
+          return context.report({
+            messageId: 'nameAndCallback',
+            loc: node.loc
+          });
+        }
+
+        const [, callback] = node.arguments;
+
+        if (!callback) {
+          context.report({
+            messageId: 'nameAndCallback',
+            loc: paramsLocation(node.arguments)
+          });
+          return;
+        }
+
+        if (!(0, _utils.isFunction)(callback)) {
+          context.report({
+            messageId: 'secondArgumentMustBeFunction',
+            loc: paramsLocation(node.arguments)
+          });
+          return;
+        }
+
+        if (callback.async) {
+          context.report({
+            messageId: 'noAsyncDescribeCallback',
+            node: callback
+          });
+        }
+
+        if (!(0, _utils.getNodeName)(node).endsWith('each') && callback.params.length) {
+          context.report({
+            messageId: 'unexpectedDescribeArgument',
+            loc: paramsLocation(callback.params)
+          });
+        }
+
+        if (callback.body.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
+          context.report({
+            messageId: 'unexpectedReturnInDescribe',
+            node: callback
+          });
+        }
+
+        if (callback.body.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
+          callback.body.body.forEach(node => {
+            if (node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement) {
+              context.report({
+                messageId: 'unexpectedReturnInDescribe',
+                node
+              });
+            }
+          });
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/valid-expect-in-promise.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/valid-expect-in-promise.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/valid-expect-in-promise.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,409 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const isPromiseChainCall = node => {
+  if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.property)) {
+    // promise methods should have at least 1 argument
+    if (node.arguments.length === 0) {
+      return false;
+    }
+
+    switch ((0, _utils.getAccessorValue)(node.callee.property)) {
+      case 'then':
+        return node.arguments.length < 3;
+
+      case 'catch':
+      case 'finally':
+        return node.arguments.length < 2;
+    }
+  }
+
+  return false;
+};
+
+const findTopMostCallExpression = node => {
+  let topMostCallExpression = node;
+  let {
+    parent
+  } = node;
+
+  while (parent) {
+    if (parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
+      topMostCallExpression = parent;
+      parent = parent.parent;
+      continue;
+    }
+
+    if (parent.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
+      break;
+    }
+
+    parent = parent.parent;
+  }
+
+  return topMostCallExpression;
+};
+
+const isTestCaseCallWithCallbackArg = node => {
+  if (!(0, _utils.isTestCaseCall)(node)) {
+    return false;
+  }
+
+  const isJestEach = (0, _utils.getNodeName)(node).endsWith('.each');
+
+  if (isJestEach && node.callee.type !== _experimentalUtils.AST_NODE_TYPES.TaggedTemplateExpression) {
+    // isJestEach but not a TaggedTemplateExpression, so this must be
+    // the `jest.each([])()` syntax which this rule doesn't support due
+    // to its complexity (see jest-community/eslint-plugin-jest#710)
+    // so we return true to trigger bailout
+    return true;
+  }
+
+  if (isJestEach || node.arguments.length >= 2) {
+    const [, callback] = node.arguments;
+    const callbackArgIndex = Number(isJestEach);
+    return callback && (0, _utils.isFunction)(callback) && callback.params.length === 1 + callbackArgIndex;
+  }
+
+  return false;
+};
+
+const isPromiseMethodThatUsesValue = (node, identifier) => {
+  const {
+    name
+  } = identifier;
+
+  if (node.argument === null) {
+    return false;
+  }
+
+  if (node.argument.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.argument.arguments.length > 0) {
+    const nodeName = (0, _utils.getNodeName)(node.argument);
+
+    if (['Promise.all', 'Promise.allSettled'].includes(nodeName)) {
+      const [firstArg] = node.argument.arguments;
+
+      if (firstArg.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression && firstArg.elements.some(nod => (0, _utils.isIdentifier)(nod, name))) {
+        return true;
+      }
+    }
+
+    if (['Promise.resolve', 'Promise.reject'].includes(nodeName) && node.argument.arguments.length === 1) {
+      return (0, _utils.isIdentifier)(node.argument.arguments[0], name);
+    }
+  }
+
+  return (0, _utils.isIdentifier)(node.argument, name);
+};
+/**
+ * Attempts to determine if the runtime value represented by the given `identifier`
+ * is `await`ed within the given array of elements
+ */
+
+
+const isValueAwaitedInElements = (name, elements) => {
+  for (const element of elements) {
+    if (element.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression && (0, _utils.isIdentifier)(element.argument, name)) {
+      return true;
+    }
+
+    if (element.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression && isValueAwaitedInElements(name, element.elements)) {
+      return true;
+    }
+  }
+
+  return false;
+};
+/**
+ * Attempts to determine if the runtime value represented by the given `identifier`
+ * is `await`ed as an argument along the given call expression
+ */
+
+
+const isValueAwaitedInArguments = (name, call) => {
+  let node = call;
+
+  while (node) {
+    if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
+      if (isValueAwaitedInElements(name, node.arguments)) {
+        return true;
+      }
+
+      node = node.callee;
+    }
+
+    if (node.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
+      break;
+    }
+
+    node = node.object;
+  }
+
+  return false;
+};
+
+const getLeftMostCallExpression = call => {
+  let leftMostCallExpression = call;
+  let node = call;
+
+  while (node) {
+    if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
+      leftMostCallExpression = node;
+      node = node.callee;
+    }
+
+    if (node.type !== _experimentalUtils.AST_NODE_TYPES.MemberExpression) {
+      break;
+    }
+
+    node = node.object;
+  }
+
+  return leftMostCallExpression;
+};
+/**
+ * Attempts to determine if the runtime value represented by the given `identifier`
+ * is `await`ed or `return`ed within the given `body` of statements
+ */
+
+
+const isValueAwaitedOrReturned = (identifier, body) => {
+  const {
+    name
+  } = identifier;
+
+  for (const node of body) {
+    // skip all nodes that are before this identifier, because they'd probably
+    // be affecting a different runtime value (e.g. due to reassignment)
+    if (node.range[0] <= identifier.range[0]) {
+      continue;
+    }
+
+    if (node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement) {
+      return isPromiseMethodThatUsesValue(node, identifier);
+    }
+
+    if (node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement) {
+      // it's possible that we're awaiting the value as an argument
+      if (node.expression.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
+        if (isValueAwaitedInArguments(name, node.expression)) {
+          return true;
+        }
+
+        const leftMostCall = getLeftMostCallExpression(node.expression);
+
+        if ((0, _utils.isExpectCall)(leftMostCall) && leftMostCall.arguments.length > 0 && (0, _utils.isIdentifier)(leftMostCall.arguments[0], name)) {
+          const {
+            modifier
+          } = (0, _utils.parseExpectCall)(leftMostCall);
+
+          if ((modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils.ModifierName.resolves || (modifier === null || modifier === void 0 ? void 0 : modifier.name) === _utils.ModifierName.rejects) {
+            return true;
+          }
+        }
+      }
+
+      if (node.expression.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression && isPromiseMethodThatUsesValue(node.expression, identifier)) {
+        return true;
+      } // (re)assignment changes the runtime value, so if we've not found an
+      // await or return already we act as if we've reached the end of the body
+
+
+      if (node.expression.type === _experimentalUtils.AST_NODE_TYPES.AssignmentExpression) {
+        var _getNodeName;
+
+        // unless we're assigning to the same identifier, in which case
+        // we might be chaining off the existing promise value
+        if ((0, _utils.isIdentifier)(node.expression.left, name) && (_getNodeName = (0, _utils.getNodeName)(node.expression.right)) !== null && _getNodeName !== void 0 && _getNodeName.startsWith(`${name}.`) && isPromiseChainCall(node.expression.right)) {
+          continue;
+        }
+
+        break;
+      }
+    }
+
+    if (node.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement && isValueAwaitedOrReturned(identifier, node.body)) {
+      return true;
+    }
+  }
+
+  return false;
+};
+
+const findFirstBlockBodyUp = node => {
+  let parent = node;
+
+  while (parent) {
+    if (parent.type === _experimentalUtils.AST_NODE_TYPES.BlockStatement) {
+      return parent.body;
+    }
+
+    parent = parent.parent;
+  }
+  /* istanbul ignore next */
+
+
+  throw new Error(`Could not find BlockStatement - please file a github issue at https://github.com/jest-community/eslint-plugin-jest`);
+};
+
+const isDirectlyWithinTestCaseCall = node => {
+  let parent = node;
+
+  while (parent) {
+    if ((0, _utils.isFunction)(parent)) {
+      var _parent;
+
+      parent = parent.parent;
+      return !!(((_parent = parent) === null || _parent === void 0 ? void 0 : _parent.type) === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isTestCaseCall)(parent));
+    }
+
+    parent = parent.parent;
+  }
+
+  return false;
+};
+
+const isVariableAwaitedOrReturned = variable => {
+  const body = findFirstBlockBodyUp(variable); // it's pretty much impossible for us to track destructuring assignments,
+  // so we return true to bailout gracefully
+
+  if (!(0, _utils.isIdentifier)(variable.id)) {
+    return true;
+  }
+
+  return isValueAwaitedOrReturned(variable.id, body);
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Ensure promises that have expectations in their chain are valid',
+      recommended: 'error'
+    },
+    messages: {
+      expectInFloatingPromise: "This promise should either be returned or awaited to ensure the expects in it's chain are called"
+    },
+    type: 'suggestion',
+    schema: []
+  },
+  defaultOptions: [],
+
+  create(context) {
+    let inTestCaseWithDoneCallback = false; // an array of booleans representing each promise chain we enter, with the
+    // boolean value representing if we think a given chain contains an expect
+    // in it's body.
+    //
+    // since we only care about the inner-most chain, we represent the state in
+    // reverse with the inner-most being the first item, as that makes it
+    // slightly less code to assign to by not needing to know the length
+
+    const chains = [];
+    return {
+      CallExpression(node) {
+        // there are too many ways that the done argument could be used with
+        // promises that contain expect that would make the promise safe for us
+        if (isTestCaseCallWithCallbackArg(node)) {
+          inTestCaseWithDoneCallback = true;
+          return;
+        } // if this call expression is a promise chain, add it to the stack with
+        // value of "false", as we assume there are no expect calls initially
+
+
+        if (isPromiseChainCall(node)) {
+          chains.unshift(false);
+          return;
+        } // if we're within a promise chain, and this call expression looks like
+        // an expect call, mark the deepest chain as having an expect call
+
+
+        if (chains.length > 0 && (0, _utils.isExpectCall)(node)) {
+          chains[0] = true;
+        }
+      },
+
+      'CallExpression:exit'(node) {
+        // there are too many ways that the "done" argument could be used to
+        // make promises containing expects safe in a test for us to be able to
+        // accurately check, so we just bail out completely if it's present
+        if (inTestCaseWithDoneCallback) {
+          if ((0, _utils.isTestCaseCall)(node)) {
+            inTestCaseWithDoneCallback = false;
+          }
+
+          return;
+        }
+
+        if (!isPromiseChainCall(node)) {
+          return;
+        } // since we're exiting this call expression (which is a promise chain)
+        // we remove it from the stack of chains, since we're unwinding
+
+
+        const hasExpectCall = chains.shift(); // if the promise chain we're exiting doesn't contain an expect,
+        // then we don't need to check it for anything
+
+        if (!hasExpectCall) {
+          return;
+        }
+
+        const {
+          parent
+        } = findTopMostCallExpression(node); // if we don't have a parent (which is technically impossible at runtime)
+        // or our parent is not directly within the test case, we stop checking
+        // because we're most likely in the body of a function being defined
+        // within the test, which we can't track
+
+        if (!parent || !isDirectlyWithinTestCaseCall(parent)) {
+          return;
+        }
+
+        switch (parent.type) {
+          case _experimentalUtils.AST_NODE_TYPES.VariableDeclarator:
+            {
+              if (isVariableAwaitedOrReturned(parent)) {
+                return;
+              }
+
+              break;
+            }
+
+          case _experimentalUtils.AST_NODE_TYPES.AssignmentExpression:
+            {
+              if (parent.left.type === _experimentalUtils.AST_NODE_TYPES.Identifier && isValueAwaitedOrReturned(parent.left, findFirstBlockBodyUp(parent))) {
+                return;
+              }
+
+              break;
+            }
+
+          case _experimentalUtils.AST_NODE_TYPES.ExpressionStatement:
+            break;
+
+          case _experimentalUtils.AST_NODE_TYPES.ReturnStatement:
+          case _experimentalUtils.AST_NODE_TYPES.AwaitExpression:
+          default:
+            return;
+        }
+
+        context.report({
+          messageId: 'expectInFloatingPromise',
+          node: parent
+        });
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/valid-expect.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/valid-expect.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/valid-expect.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,293 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+/*
+ * This implementation is ported from from eslint-plugin-jasmine.
+ * MIT license, Tom Vincent.
+ */
+
+/**
+ * Async assertions might be called in Promise
+ * methods like `Promise.x(expect1)` or `Promise.x([expect1, expect2])`.
+ * If that's the case, Promise node have to be awaited or returned.
+ *
+ * @Returns CallExpressionNode
+ */
+const getPromiseCallExpressionNode = node => {
+  if (node.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression && node.parent && node.parent.type === _experimentalUtils.AST_NODE_TYPES.CallExpression) {
+    node = node.parent;
+  }
+
+  if (node.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && node.callee.type === _experimentalUtils.AST_NODE_TYPES.MemberExpression && (0, _utils.isSupportedAccessor)(node.callee.object) && (0, _utils.getAccessorValue)(node.callee.object) === 'Promise' && node.parent) {
+    return node;
+  }
+
+  return null;
+};
+
+const findPromiseCallExpressionNode = node => {
+  var _node$parent;
+
+  return (_node$parent = node.parent) !== null && _node$parent !== void 0 && _node$parent.parent && [_experimentalUtils.AST_NODE_TYPES.CallExpression, _experimentalUtils.AST_NODE_TYPES.ArrayExpression].includes(node.parent.type) ? getPromiseCallExpressionNode(node.parent) : null;
+};
+
+const getParentIfThenified = node => {
+  var _node$parent2;
+
+  const grandParentNode = (_node$parent2 = node.parent) === null || _node$parent2 === void 0 ? void 0 : _node$parent2.parent;
+
+  if (grandParentNode && grandParentNode.type === _experimentalUtils.AST_NODE_TYPES.CallExpression && (0, _utils.isExpectMember)(grandParentNode.callee) && ['then', 'catch'].includes((0, _utils.getAccessorValue)(grandParentNode.callee.property)) && grandParentNode.parent) {
+    // Just in case `then`s are chained look one above.
+    return getParentIfThenified(grandParentNode);
+  }
+
+  return node;
+};
+
+const isAcceptableReturnNode = (node, allowReturn) => {
+  if (allowReturn && node.type === _experimentalUtils.AST_NODE_TYPES.ReturnStatement) {
+    return true;
+  }
+
+  if (node.type === _experimentalUtils.AST_NODE_TYPES.ConditionalExpression && node.parent) {
+    return isAcceptableReturnNode(node.parent, allowReturn);
+  }
+
+  return [_experimentalUtils.AST_NODE_TYPES.ArrowFunctionExpression, _experimentalUtils.AST_NODE_TYPES.AwaitExpression].includes(node.type);
+};
+
+const isNoAssertionsParentNode = node => node.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement || node.type === _experimentalUtils.AST_NODE_TYPES.AwaitExpression && node.parent !== undefined && node.parent.type === _experimentalUtils.AST_NODE_TYPES.ExpressionStatement;
+
+const promiseArrayExceptionKey = ({
+  start,
+  end
+}) => `${start.line}:${start.column}-${end.line}:${end.column}`;
+
+const defaultAsyncMatchers = ['toReject', 'toResolve'];
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Enforce valid `expect()` usage',
+      recommended: 'error'
+    },
+    messages: {
+      tooManyArgs: 'Expect takes at most {{ amount }} argument{{ s }}.',
+      notEnoughArgs: 'Expect requires at least {{ amount }} argument{{ s }}.',
+      modifierUnknown: 'Expect has no modifier named "{{ modifierName }}".',
+      matcherNotFound: 'Expect must have a corresponding matcher call.',
+      matcherNotCalled: 'Matchers must be called to assert.',
+      asyncMustBeAwaited: 'Async assertions must be awaited{{ orReturned }}.',
+      promisesWithAsyncAssertionsMustBeAwaited: 'Promises which return async assertions must be awaited{{ orReturned }}.'
+    },
+    type: 'suggestion',
+    schema: [{
+      type: 'object',
+      properties: {
+        alwaysAwait: {
+          type: 'boolean',
+          default: false
+        },
+        asyncMatchers: {
+          type: 'array',
+          items: {
+            type: 'string'
+          }
+        },
+        minArgs: {
+          type: 'number',
+          minimum: 1
+        },
+        maxArgs: {
+          type: 'number',
+          minimum: 1
+        }
+      },
+      additionalProperties: false
+    }]
+  },
+  defaultOptions: [{
+    alwaysAwait: false,
+    asyncMatchers: defaultAsyncMatchers,
+    minArgs: 1,
+    maxArgs: 1
+  }],
+
+  create(context, [{
+    alwaysAwait,
+    asyncMatchers = defaultAsyncMatchers,
+    minArgs = 1,
+    maxArgs = 1
+  }]) {
+    // Context state
+    const arrayExceptions = new Set();
+
+    const pushPromiseArrayException = loc => arrayExceptions.add(promiseArrayExceptionKey(loc));
+    /**
+     * Promise method that accepts an array of promises,
+     * (eg. Promise.all), will throw warnings for the each
+     * unawaited or non-returned promise. To avoid throwing
+     * multiple warnings, we check if there is a warning in
+     * the given location.
+     */
+
+
+    const promiseArrayExceptionExists = loc => arrayExceptions.has(promiseArrayExceptionKey(loc));
+
+    return {
+      CallExpression(node) {
+        if (!(0, _utils.isExpectCall)(node)) {
+          return;
+        }
+
+        const {
+          expect,
+          modifier,
+          matcher
+        } = (0, _utils.parseExpectCall)(node);
+
+        if (expect.arguments.length < minArgs) {
+          const expectLength = (0, _utils.getAccessorValue)(expect.callee).length;
+          const loc = {
+            start: {
+              column: node.loc.start.column + expectLength,
+              line: node.loc.start.line
+            },
+            end: {
+              column: node.loc.start.column + expectLength + 1,
+              line: node.loc.start.line
+            }
+          };
+          context.report({
+            messageId: 'notEnoughArgs',
+            data: {
+              amount: minArgs,
+              s: minArgs === 1 ? '' : 's'
+            },
+            node,
+            loc
+          });
+        }
+
+        if (expect.arguments.length > maxArgs) {
+          const {
+            start
+          } = expect.arguments[maxArgs].loc;
+          const {
+            end
+          } = expect.arguments[node.arguments.length - 1].loc;
+          const loc = {
+            start,
+            end: {
+              column: end.column - 1,
+              line: end.line
+            }
+          };
+          context.report({
+            messageId: 'tooManyArgs',
+            data: {
+              amount: maxArgs,
+              s: maxArgs === 1 ? '' : 's'
+            },
+            node,
+            loc
+          });
+        } // something was called on `expect()`
+
+
+        if (!matcher) {
+          if (modifier) {
+            context.report({
+              messageId: 'matcherNotFound',
+              node: modifier.node.property
+            });
+          }
+
+          return;
+        }
+
+        if ((0, _utils.isExpectMember)(matcher.node.parent)) {
+          context.report({
+            messageId: 'modifierUnknown',
+            data: {
+              modifierName: matcher.name
+            },
+            node: matcher.node.property
+          });
+          return;
+        }
+
+        if (!matcher.arguments) {
+          context.report({
+            messageId: 'matcherNotCalled',
+            node: matcher.node.property
+          });
+        }
+
+        const parentNode = matcher.node.parent;
+        const shouldBeAwaited = modifier && modifier.name !== _utils.ModifierName.not || asyncMatchers.includes(matcher.name);
+
+        if (!parentNode.parent || !shouldBeAwaited) {
+          return;
+        }
+        /**
+         * If parent node is an array expression, we'll report the warning,
+         * for the array object, not for each individual assertion.
+         */
+
+
+        const isParentArrayExpression = parentNode.parent.type === _experimentalUtils.AST_NODE_TYPES.ArrayExpression;
+        const orReturned = alwaysAwait ? '' : ' or returned';
+        /**
+         * An async assertion can be chained with `then` or `catch` statements.
+         * In that case our target CallExpression node is the one with
+         * the last `then` or `catch` statement.
+         */
+
+        const targetNode = getParentIfThenified(parentNode);
+        const finalNode = findPromiseCallExpressionNode(targetNode) || targetNode;
+
+        if (finalNode.parent && // If node is not awaited or returned
+        !isAcceptableReturnNode(finalNode.parent, !alwaysAwait) && // if we didn't warn user already
+        !promiseArrayExceptionExists(finalNode.loc)) {
+          context.report({
+            loc: finalNode.loc,
+            data: {
+              orReturned
+            },
+            messageId: finalNode === targetNode ? 'asyncMustBeAwaited' : 'promisesWithAsyncAssertionsMustBeAwaited',
+            node
+          });
+
+          if (isParentArrayExpression) {
+            pushPromiseArrayException(finalNode.loc);
+          }
+        }
+      },
+
+      // nothing called on "expect()"
+      'CallExpression:exit'(node) {
+        if ((0, _utils.isExpectCall)(node) && isNoAssertionsParentNode(node.parent)) {
+          context.report({
+            messageId: 'matcherNotFound',
+            node
+          });
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
Index: frontend/node_modules/eslint-plugin-jest/lib/rules/valid-title.js
===================================================================
--- frontend/node_modules/eslint-plugin-jest/lib/rules/valid-title.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
+++ frontend/node_modules/eslint-plugin-jest/lib/rules/valid-title.js	(revision 9af201e94b5a79beb92a30858fc54de4bc3b9449)
@@ -0,0 +1,246 @@
+"use strict";
+
+Object.defineProperty(exports, "__esModule", {
+  value: true
+});
+exports.default = void 0;
+
+var _experimentalUtils = require("@typescript-eslint/experimental-utils");
+
+var _utils = require("./utils");
+
+const trimFXprefix = word => ['f', 'x'].includes(word.charAt(0)) ? word.substr(1) : word;
+
+const doesBinaryExpressionContainStringNode = binaryExp => {
+  if ((0, _utils.isStringNode)(binaryExp.right)) {
+    return true;
+  }
+
+  if (binaryExp.left.type === _experimentalUtils.AST_NODE_TYPES.BinaryExpression) {
+    return doesBinaryExpressionContainStringNode(binaryExp.left);
+  }
+
+  return (0, _utils.isStringNode)(binaryExp.left);
+};
+
+const quoteStringValue = node => node.type === _experimentalUtils.AST_NODE_TYPES.TemplateLiteral ? `\`${node.quasis[0].value.raw}\`` : node.raw;
+
+const compileMatcherPattern = matcherMaybeWithMessage => {
+  const [matcher, message] = Array.isArray(matcherMaybeWithMessage) ? matcherMaybeWithMessage : [matcherMaybeWithMessage];
+  return [new RegExp(matcher, 'u'), message];
+};
+
+const compileMatcherPatterns = matchers => {
+  if (typeof matchers === 'string' || Array.isArray(matchers)) {
+    const compiledMatcher = compileMatcherPattern(matchers);
+    return {
+      describe: compiledMatcher,
+      test: compiledMatcher,
+      it: compiledMatcher
+    };
+  }
+
+  return {
+    describe: matchers.describe ? compileMatcherPattern(matchers.describe) : null,
+    test: matchers.test ? compileMatcherPattern(matchers.test) : null,
+    it: matchers.it ? compileMatcherPattern(matchers.it) : null
+  };
+};
+
+const MatcherAndMessageSchema = {
+  type: 'array',
+  items: {
+    type: 'string'
+  },
+  minItems: 1,
+  maxItems: 2,
+  additionalItems: false
+};
+
+var _default = (0, _utils.createRule)({
+  name: __filename,
+  meta: {
+    docs: {
+      category: 'Best Practices',
+      description: 'Enforce valid titles',
+      recommended: 'error'
+    },
+    messages: {
+      titleMustBeString: 'Title must be a string',
+      emptyTitle: '{{ jestFunctionName }} should not have an empty title',
+      duplicatePrefix: 'should not have duplicate prefix',
+      accidentalSpace: 'should not have leading or trailing spaces',
+      disallowedWord: '"{{ word }}" is not allowed in test titles.',
+      mustNotMatch: '{{ jestFunctionName }} should not match {{ pattern }}',
+      mustMatch: '{{ jestFunctionName }} should match {{ pattern }}',
+      mustNotMatchCustom: '{{ message }}',
+      mustMatchCustom: '{{ message }}'
+    },
+    type: 'suggestion',
+    schema: [{
+      type: 'object',
+      properties: {
+        ignoreTypeOfDescribeName: {
+          type: 'boolean',
+          default: false
+        },
+        disallowedWords: {
+          type: 'array',
+          items: {
+            type: 'string'
+          }
+        }
+      },
+      patternProperties: {
+        [/^must(?:Not)?Match$/u.source]: {
+          oneOf: [{
+            type: 'string'
+          }, MatcherAndMessageSchema, {
+            type: 'object',
+            propertyNames: {
+              enum: ['describe', 'test', 'it']
+            },
+            additionalProperties: {
+              oneOf: [{
+                type: 'string'
+              }, MatcherAndMessageSchema]
+            }
+          }]
+        }
+      },
+      additionalProperties: false
+    }],
+    fixable: 'code'
+  },
+  defaultOptions: [{
+    ignoreTypeOfDescribeName: false,
+    disallowedWords: []
+  }],
+
+  create(context, [{
+    ignoreTypeOfDescribeName,
+    disallowedWords = [],
+    mustNotMatch,
+    mustMatch
+  }]) {
+    const disallowedWordsRegexp = new RegExp(`\\b(${disallowedWords.join('|')})\\b`, 'iu');
+    const mustNotMatchPatterns = compileMatcherPatterns(mustNotMatch !== null && mustNotMatch !== void 0 ? mustNotMatch : {});
+    const mustMatchPatterns = compileMatcherPatterns(mustMatch !== null && mustMatch !== void 0 ? mustMatch : {});
+    return {
+      CallExpression(node) {
+        var _mustNotMatchPatterns, _mustMatchPatterns$je;
+
+        if (!(0, _utils.isDescribeCall)(node) && !(0, _utils.isTestCaseCall)(node)) {
+          return;
+        }
+
+        const [argument] = node.arguments;
+
+        if (!argument) {
+          return;
+        }
+
+        if (!(0, _utils.isStringNode)(argument)) {
+          if (argument.type === _experimentalUtils.AST_NODE_TYPES.BinaryExpression && doesBinaryExpressionContainStringNode(argument)) {
+            return;
+          }
+
+          if (argument.type !== _experimentalUtils.AST_NODE_TYPES.TemplateLiteral && !(ignoreTypeOfDescribeName && (0, _utils.isDescribeCall)(node))) {
+            context.report({
+              messageId: 'titleMustBeString',
+              loc: argument.loc
+            });
+          }
+
+          return;
+        }
+
+        const title = (0, _utils.getStringValue)(argument);
+
+        if (!title) {
+          context.report({
+            messageId: 'emptyTitle',
+            data: {
+              jestFunctionName: (0, _utils.isDescribeCall)(node) ? _utils.DescribeAlias.describe : _utils.TestCaseName.test
+            },
+            node
+          });
+          return;
+        }
+
+        if (disallowedWords.length > 0) {
+          const disallowedMatch = disallowedWordsRegexp.exec(title);
+
+          if (disallowedMatch) {
+            context.report({
+              data: {
+                word: disallowedMatch[1]
+              },
+              messageId: 'disallowedWord',
+              node: argument
+            });
+            return;
+          }
+        }
+
+        if (title.trim().length !== title.length) {
+          context.report({
+            messageId: 'accidentalSpace',
+            node: argument,
+            fix: fixer => [fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]) +?/u, '$1').replace(/ +?([`'"])$/u, '$1'))]
+          });
+        }
+
+        const nodeName = trimFXprefix((0, _utils.getNodeName)(node));
+        const [firstWord] = title.split(' ');
+
+        if (firstWord.toLowerCase() === nodeName) {
+          context.report({
+            messageId: 'duplicatePrefix',
+            node: argument,
+            fix: fixer => [fixer.replaceTextRange(argument.range, quoteStringValue(argument).replace(/^([`'"]).+? /u, '$1'))]
+          });
+        }
+
+        const [jestFunctionName] = nodeName.split('.');
+        const [mustNotMatchPattern, mustNotMatchMessage] = (_mustNotMatchPatterns = mustNotMatchPatterns[jestFunctionName]) !== null && _mustNotMatchPatterns !== void 0 ? _mustNotMatchPatterns : [];
+
+        if (mustNotMatchPattern) {
+          if (mustNotMatchPattern.test(title)) {
+            context.report({
+              messageId: mustNotMatchMessage ? 'mustNotMatchCustom' : 'mustNotMatch',
+              node: argument,
+              data: {
+                jestFunctionName,
+                pattern: mustNotMatchPattern,
+                message: mustNotMatchMessage
+              }
+            });
+            return;
+          }
+        }
+
+        const [mustMatchPattern, mustMatchMessage] = (_mustMatchPatterns$je = mustMatchPatterns[jestFunctionName]) !== null && _mustMatchPatterns$je !== void 0 ? _mustMatchPatterns$je : [];
+
+        if (mustMatchPattern) {
+          if (!mustMatchPattern.test(title)) {
+            context.report({
+              messageId: mustMatchMessage ? 'mustMatchCustom' : 'mustMatch',
+              node: argument,
+              data: {
+                jestFunctionName,
+                pattern: mustMatchPattern,
+                message: mustMatchMessage
+              }
+            });
+            return;
+          }
+        }
+      }
+
+    };
+  }
+
+});
+
+exports.default = _default;
