diff --git a/package-lock.json b/package-lock.json
index 20fcd01ebf1147f2a9d3299c41fea36690834804..4c38e0f226e56868c77064942699736bf3594027 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -985,6 +985,12 @@
         "@types/node": "*"
       }
     },
+    "@types/json-schema": {
+      "version": "7.0.3",
+      "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.3.tgz",
+      "integrity": "sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==",
+      "dev": true
+    },
     "@types/minimatch": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@@ -997,6 +1003,47 @@
       "integrity": "sha512-aX+gFgA5GHcDi89KG5keey2zf0WfZk/HAQotEamsK2kbey+8yGKcson0hbK8E+v0NArlCJQCqMP161YhV6ZXLg==",
       "dev": true
     },
+    "@typescript-eslint/experimental-utils": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz",
+      "integrity": "sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg==",
+      "dev": true,
+      "requires": {
+        "@types/json-schema": "^7.0.3",
+        "@typescript-eslint/typescript-estree": "1.13.0",
+        "eslint-scope": "^4.0.0"
+      },
+      "dependencies": {
+        "eslint-scope": {
+          "version": "4.0.3",
+          "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+          "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+          "dev": true,
+          "requires": {
+            "esrecurse": "^4.1.0",
+            "estraverse": "^4.1.1"
+          }
+        }
+      }
+    },
+    "@typescript-eslint/typescript-estree": {
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz",
+      "integrity": "sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw==",
+      "dev": true,
+      "requires": {
+        "lodash.unescape": "4.0.1",
+        "semver": "5.5.0"
+      },
+      "dependencies": {
+        "semver": {
+          "version": "5.5.0",
+          "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
+          "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==",
+          "dev": true
+        }
+      }
+    },
     "@vernier/godirect": {
       "version": "1.5.0",
       "resolved": "https://registry.npmjs.org/@vernier/godirect/-/godirect-1.5.0.tgz",
@@ -4262,6 +4309,15 @@
         }
       }
     },
+    "eslint-plugin-jest": {
+      "version": "22.14.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.14.1.tgz",
+      "integrity": "sha512-mpLjhADl+HjagrlaGNx95HIji089S18DhnU/Ee8P8VP+dhEnuEzb43BXEaRmDgQ7BiSUPcSCvt1ydtgPkjOF/Q==",
+      "dev": true,
+      "requires": {
+        "@typescript-eslint/experimental-utils": "^1.13.0"
+      }
+    },
     "eslint-plugin-react": {
       "version": "7.14.3",
       "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz",
@@ -8497,6 +8553,12 @@
         "lodash.debounce": "^4.0.0"
       }
     },
+    "lodash.unescape": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/lodash.unescape/-/lodash.unescape-4.0.1.tgz",
+      "integrity": "sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=",
+      "dev": true
+    },
     "loglevel": {
       "version": "1.6.3",
       "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.3.tgz",
diff --git a/package.json b/package.json
index ebe761b09bf4b3d8982b6b16032d17d7e4515986..f4ca664c2249ace9a7b6f802113c2a5b081709a6 100644
--- a/package.json
+++ b/package.json
@@ -56,6 +56,7 @@
     "eslint": "^5.0.1",
     "eslint-config-scratch": "^5.0.0",
     "eslint-plugin-import": "^2.8.0",
+    "eslint-plugin-jest": "^22.14.1",
     "eslint-plugin-react": "^7.12.4",
     "file-loader": "2.0.0",
     "get-float-time-domain-data": "0.1.0",
diff --git a/test/.eslintrc.js b/test/.eslintrc.js
index ea7ef84f15c71d2b379ba0e22b700e82cd8a8893..be26685849d0057b630ef681cb15f25796bb7ee6 100644
--- a/test/.eslintrc.js
+++ b/test/.eslintrc.js
@@ -1,9 +1,10 @@
 module.exports = {
-    extends: ['scratch/react', 'scratch/es6'],
+    extends: ['scratch/react', 'scratch/es6', 'plugin:jest/recommended'],
     env: {
         browser: true,
         jest: true
     },
+    plugins: ['jest'],
     rules: {
         'react/prop-types': 0
     }
diff --git a/test/helpers/selenium-helper.js b/test/helpers/selenium-helper.js
index f10f0800a557413590e4b181bc862faf8b57d5ce..1072cb0d6f33d0f551de1d47cab392361ca183c7 100644
--- a/test/helpers/selenium-helper.js
+++ b/test/helpers/selenium-helper.js
@@ -1,4 +1,4 @@
-jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; // eslint-disable-line no-undef
+jest.setTimeout(30000); // eslint-disable-line no-undef
 
 import bindAll from 'lodash.bindall';
 import 'chromedriver'; // register path
diff --git a/test/unit/containers/slider-prompt.test.jsx b/test/unit/containers/slider-prompt.test.jsx
index ebb5aa6f3830f1ec2c67438755414418a97e4213..0c96c50267cee1738d742062fb94108344503e2d 100644
--- a/test/unit/containers/slider-prompt.test.jsx
+++ b/test/unit/containers/slider-prompt.test.jsx
@@ -55,7 +55,7 @@ describe('Slider Prompt Container', () => {
         const componentProps = wrapper.find(SliderPromptComponent).props();
         componentProps.onChangeMin({target: {value: '1.0'}});
         componentProps.onOk();
-        expect(onOk).toBeCalledWith(1, 100, false);
+        expect(onOk).toHaveBeenCalledWith(1, 100, false);
     });
 
     test('Entering integers submits with isDiscrete=true', () => {
@@ -72,7 +72,7 @@ describe('Slider Prompt Container', () => {
         componentProps.onChangeMin({target: {value: '1'}});
         componentProps.onChangeMax({target: {value: '2'}});
         componentProps.onOk();
-        expect(onOk).toBeCalledWith(1, 2, true);
+        expect(onOk).toHaveBeenCalledWith(1, 2, true);
     });
 
     test('Enter button submits the form', () => {
@@ -89,7 +89,7 @@ describe('Slider Prompt Container', () => {
         componentProps.onChangeMin({target: {value: '1'}});
         componentProps.onChangeMax({target: {value: '2'}});
         componentProps.onKeyPress({key: 'Enter'});
-        expect(onOk).toBeCalledWith(1, 2, true);
+        expect(onOk).toHaveBeenCalledWith(1, 2, true);
     });
 
     test('Validates number-ness before submitting', () => {
@@ -105,7 +105,7 @@ describe('Slider Prompt Container', () => {
         const componentProps = wrapper.find(SliderPromptComponent).props();
         componentProps.onChangeMin({target: {value: 'hello'}});
         componentProps.onOk();
-        expect(onOk).not.toBeCalled();
-        expect(onCancel).toBeCalled();
+        expect(onOk).not.toHaveBeenCalled();
+        expect(onCancel).toHaveBeenCalled();
     });
 });
diff --git a/test/unit/util/drag-recognizer.test.js b/test/unit/util/drag-recognizer.test.js
index 825917ba8eddb75e2278808166581eec552e5c06..3737e7493f3a537905b1607da0391b26ce2554fe 100644
--- a/test/unit/util/drag-recognizer.test.js
+++ b/test/unit/util/drag-recognizer.test.js
@@ -18,25 +18,25 @@ describe('DragRecognizer', () => {
     test('start -> small drag', () => {
         dragRecognizer.start({clientX: 100, clientY: 100});
         window.dispatchEvent(new MouseEvent('mousemove', {clientX: 101, clientY: 101}));
-        expect(onDrag).not.toBeCalled();
+        expect(onDrag).not.toHaveBeenCalled();
     });
 
     test('start -> large vertical touch move -> scroll, not drag', () => {
         dragRecognizer.start({clientX: 100, clientY: 100});
         window.dispatchEvent(new MouseEvent('touchmove', {clientX: 106, clientY: 150}));
-        expect(onDrag).not.toBeCalled();
+        expect(onDrag).not.toHaveBeenCalled();
     });
 
     test('start -> large vertical mouse move -> mouse moves always drag)', () => {
         dragRecognizer.start({clientX: 100, clientY: 100});
         window.dispatchEvent(new MouseEvent('mousemove', {clientX: 100, clientY: 150}));
-        expect(onDrag).toBeCalled();
+        expect(onDrag).toHaveBeenCalled();
     });
 
     test('start -> large horizontal touch move -> drag', () => {
         dragRecognizer.start({clientX: 100, clientY: 100});
         window.dispatchEvent(new MouseEvent('touchmove', {clientX: 150, clientY: 106}));
-        expect(onDrag).toBeCalled();
+        expect(onDrag).toHaveBeenCalled();
     });
 
     test('after starting a scroll, it cannot become a drag', () => {
@@ -44,7 +44,7 @@ describe('DragRecognizer', () => {
         window.dispatchEvent(new MouseEvent('touchmove', {clientX: 100, clientY: 110}));
         window.dispatchEvent(new MouseEvent('touchmove', {clientX: 100, clientY: 100}));
         window.dispatchEvent(new MouseEvent('touchmove', {clientX: 110, clientY: 100}));
-        expect(onDrag).not.toBeCalled();
+        expect(onDrag).not.toHaveBeenCalled();
     });
 
     test('start -> end unbinds', () => {
diff --git a/test/unit/util/project-saver-hoc.test.jsx b/test/unit/util/project-saver-hoc.test.jsx
index 56ed39d44f9816dc7fd4015b2d242829bc2232c7..0ec3190bf773fdf74aa6cc39c671b27e0df32beb 100644
--- a/test/unit/util/project-saver-hoc.test.jsx
+++ b/test/unit/util/project-saver-hoc.test.jsx
@@ -423,7 +423,7 @@ describe('projectSaverHOC', () => {
         expect(mockedOnRemixing).toHaveBeenCalledWith(true);
     });
 
-    test('when starting to remix, onRemixing should be called with param true', () => {
+    test('when starting to remix, onRemixing should be called with param false', () => {
         const mockedOnRemixing = jest.fn();
         const mockedStoreProject = jest.fn(() => Promise.resolve());
         const Component = () => <div />;