Browse Source

Use Jest for JS/React tests (#5252)

Ben Vinegar 8 years ago
parent
commit
da72a6d77e

+ 25 - 9
.babelrc

@@ -1,16 +1,32 @@
 {
   "presets": [
     "react",
-    ["latest", {
-      "es2015": {
-        "modules": false
+    [
+      "latest",
+      {
+        "es2015": {
+          "modules": false
+        }
       }
-    }]
+    ]
   ],
   "plugins": [
     "transform-object-rest-spread",
-    ["babel-plugin-transform-builtin-extend", {
-      "globals": ["Array"]
-    }]
-  ]
-}
+    [
+      "babel-plugin-transform-builtin-extend",
+      {
+        "globals": [
+          "Array"
+        ]
+      }
+    ]
+  ],
+  "env": {
+    "test": {
+      "presets": [
+        "react",
+        "latest"
+      ]
+    }
+  }
+}

+ 148 - 102
.eslintrc

@@ -14,7 +14,8 @@
     "require": false,
     "expect": false,
     "sinon": false,
-    "Raven": true
+    "Raven": true,
+    "jest": true
   },
   "plugins": [
     "react",
@@ -24,126 +25,171 @@
     /**
      * Strict mode
      */
-    "strict": [2, "global"],         // http://eslint.org/docs/rules/strict
-
+    "strict": [
+      2,
+      "global"
+    ], // http://eslint.org/docs/rules/strict
     /**
      * ES6
      */
-    "no-var": 2,                     // http://eslint.org/docs/rules/no-var
-    "prefer-const": 0,               // http://eslint.org/docs/rules/prefer-const
-
+    "no-var": 2, // http://eslint.org/docs/rules/no-var
+    "prefer-const": 0, // http://eslint.org/docs/rules/prefer-const
     /**
      * Variables
      */
-    "no-shadow": 2,                  // http://eslint.org/docs/rules/no-shadow
+    "no-shadow": 2, // http://eslint.org/docs/rules/no-shadow
     "no-shadow-restricted-names": 2, // http://eslint.org/docs/rules/no-shadow-restricted-names
-    "no-undef": 2,                   // http://eslint.org/docs/rules/no-undef
-    "no-unused-vars": [2, {          // http://eslint.org/docs/rules/no-unused-vars
-      "vars": "local",
-      "args": "none"
-    }],
-    "no-use-before-define": 2,       // http://eslint.org/docs/rules/no-use-before-define
-
+    "no-undef": 2, // http://eslint.org/docs/rules/no-undef
+    "no-unused-vars": [
+      2,
+      { // http://eslint.org/docs/rules/no-unused-vars
+        "vars": "local",
+        "args": "none"
+      }
+    ],
+    "no-use-before-define": 2, // http://eslint.org/docs/rules/no-use-before-define
     /**
      * Possible errors
      */
-    "comma-dangle": [0, "always-multiline"],    // http://eslint.org/docs/rules/comma-dangle
-    "no-cond-assign": [2, "always"], // http://eslint.org/docs/rules/no-cond-assign
-    "no-console": 1,                 // http://eslint.org/docs/rules/no-console
-    "no-debugger": 1,                // http://eslint.org/docs/rules/no-debugger
-    "no-alert": 1,                   // http://eslint.org/docs/rules/no-alert
-    "no-constant-condition": 1,      // http://eslint.org/docs/rules/no-constant-condition
-    "no-dupe-keys": 2,               // http://eslint.org/docs/rules/no-dupe-keys
-    "no-duplicate-case": 2,          // http://eslint.org/docs/rules/no-duplicate-case
-    "no-empty": 2,                   // http://eslint.org/docs/rules/no-empty
-    "no-ex-assign": 2,               // http://eslint.org/docs/rules/no-ex-assign
-    "no-extra-boolean-cast": 0,      // http://eslint.org/docs/rules/no-extra-boolean-cast
-    "no-extra-semi": 2,              // http://eslint.org/docs/rules/no-extra-semi
-    "no-func-assign": 2,             // http://eslint.org/docs/rules/no-func-assign
-    "no-inner-declarations": 2,      // http://eslint.org/docs/rules/no-inner-declarations
-    "no-invalid-regexp": 2,          // http://eslint.org/docs/rules/no-invalid-regexp
-    "no-irregular-whitespace": 2,    // http://eslint.org/docs/rules/no-irregular-whitespace
-    "no-obj-calls": 2,               // http://eslint.org/docs/rules/no-obj-calls
-    "no-sparse-arrays": 2,           // http://eslint.org/docs/rules/no-sparse-arrays
-    "no-unreachable": 2,             // http://eslint.org/docs/rules/no-unreachable
-    "semi": [2, "always"],           // http://eslint.org/docs/rules/semi.html
-    "use-isnan": 2,                  // http://eslint.org/docs/rules/use-isnan
-    "block-scoped-var": 2,           // http://eslint.org/docs/rules/block-scoped-var
-
+    "comma-dangle": [
+      0,
+      "always-multiline"
+    ], // http://eslint.org/docs/rules/comma-dangle
+    "no-cond-assign": [
+      2,
+      "always"
+    ], // http://eslint.org/docs/rules/no-cond-assign
+    "no-console": 1, // http://eslint.org/docs/rules/no-console
+    "no-debugger": 1, // http://eslint.org/docs/rules/no-debugger
+    "no-alert": 1, // http://eslint.org/docs/rules/no-alert
+    "no-constant-condition": 1, // http://eslint.org/docs/rules/no-constant-condition
+    "no-dupe-keys": 2, // http://eslint.org/docs/rules/no-dupe-keys
+    "no-duplicate-case": 2, // http://eslint.org/docs/rules/no-duplicate-case
+    "no-empty": 2, // http://eslint.org/docs/rules/no-empty
+    "no-ex-assign": 2, // http://eslint.org/docs/rules/no-ex-assign
+    "no-extra-boolean-cast": 0, // http://eslint.org/docs/rules/no-extra-boolean-cast
+    "no-extra-semi": 2, // http://eslint.org/docs/rules/no-extra-semi
+    "no-func-assign": 2, // http://eslint.org/docs/rules/no-func-assign
+    "no-inner-declarations": 2, // http://eslint.org/docs/rules/no-inner-declarations
+    "no-invalid-regexp": 2, // http://eslint.org/docs/rules/no-invalid-regexp
+    "no-irregular-whitespace": 2, // http://eslint.org/docs/rules/no-irregular-whitespace
+    "no-obj-calls": 2, // http://eslint.org/docs/rules/no-obj-calls
+    "no-sparse-arrays": 2, // http://eslint.org/docs/rules/no-sparse-arrays
+    "no-unreachable": 2, // http://eslint.org/docs/rules/no-unreachable
+    "semi": [
+      2,
+      "always"
+    ], // http://eslint.org/docs/rules/semi.html
+    "use-isnan": 2, // http://eslint.org/docs/rules/use-isnan
+    "block-scoped-var": 2, // http://eslint.org/docs/rules/block-scoped-var
     /**
      * Best practices
      */
-    "consistent-return": 2,          // http://eslint.org/docs/rules/consistent-return
-    "curly": 0,                      // http://eslint.org/docs/rules/curly [REVISIT ME]
-    "default-case": 2,               // http://eslint.org/docs/rules/default-case
-    "dot-notation": [2, {            // http://eslint.org/docs/rules/dot-notation
-      "allowKeywords": true
-    }],
-    "eqeqeq": 0,                     // http://eslint.org/docs/rules/eqeqeq [REVISIT ME]
-    "guard-for-in": 0,               // http://eslint.org/docs/rules/guard-for-in [REVISIT ME]
-    "jsx-quotes": [2, "prefer-double"], // http://eslint.org/docs/rules/jsx-quotes
-    "no-caller": 2,                  // http://eslint.org/docs/rules/no-caller
-    "no-else-return": 0,             // http://eslint.org/docs/rules/no-else-return [REVISIT ME]
-    "no-eq-null": 2,                 // http://eslint.org/docs/rules/no-eq-null
-    "no-eval": 2,                    // http://eslint.org/docs/rules/no-eval
-    "no-extend-native": 2,           // http://eslint.org/docs/rules/no-extend-native
-    "no-extra-bind": 2,              // http://eslint.org/docs/rules/no-extra-bind
-    "no-fallthrough": 2,             // http://eslint.org/docs/rules/no-fallthrough
-    "no-floating-decimal": 2,        // http://eslint.org/docs/rules/no-floating-decimal
-    "no-implied-eval": 2,            // http://eslint.org/docs/rules/no-implied-eval
-    "no-lone-blocks": 2,             // http://eslint.org/docs/rules/no-lone-blocks
-    "no-loop-func": 2,               // http://eslint.org/docs/rules/no-loop-func
-    "no-multi-str": 2,               // http://eslint.org/docs/rules/no-multi-str
-    "no-native-reassign": 2,         // http://eslint.org/docs/rules/no-native-reassign
-    "no-new": 2,                     // http://eslint.org/docs/rules/no-new
-    "no-new-func": 2,                // http://eslint.org/docs/rules/no-new-func
-    "no-new-wrappers": 2,            // http://eslint.org/docs/rules/no-new-wrappers
-    "no-octal": 2,                   // http://eslint.org/docs/rules/no-octal
-    "no-octal-escape": 2,            // http://eslint.org/docs/rules/no-octal-escape
-    "no-param-reassign": 0,          // http://eslint.org/docs/rules/no-param-reassign [REVISIT ME]
-    "no-proto": 2,                   // http://eslint.org/docs/rules/no-proto
-    "no-redeclare": 2,               // http://eslint.org/docs/rules/no-redeclare
-    "no-return-assign": 2,           // http://eslint.org/docs/rules/no-return-assign
-    "no-script-url": 2,              // http://eslint.org/docs/rules/no-script-url
-    "no-self-compare": 2,            // http://eslint.org/docs/rules/no-self-compare
-    "no-sequences": 2,               // http://eslint.org/docs/rules/no-sequences
-    "no-throw-literal": 2,           // http://eslint.org/docs/rules/no-throw-literal
-    "no-with": 2,                    // http://eslint.org/docs/rules/no-with
-    "quotes": [2, "single"],         // http://eslint.org/docs/rules/quotes.html
-    "radix": 2,                      // http://eslint.org/docs/rules/radix
-    "computed-property-spacing": [2, "never"], // http://eslint.org/docs/rules/space-in-brackets.html
-    "array-bracket-spacing": [2, "never"], // http://eslint.org/docs/rules/space-in-brackets.html
-    "object-curly-spacing": [2, "never"], // http://eslint.org/docs/rules/space-in-brackets.html
-    "space-infix-ops": 2,            // http://eslint.org/docs/rules/space-infix-ops.html
-    "vars-on-top": 0,                // http://eslint.org/docs/rules/vars-on-top
-    "wrap-iife": [2, "any"],         // http://eslint.org/docs/rules/wrap-iife
-    "yoda": 2,                       // http://eslint.org/docs/rules/yoda
-
+    "consistent-return": 2, // http://eslint.org/docs/rules/consistent-return
+    "curly": 0, // http://eslint.org/docs/rules/curly [REVISIT ME]
+    "default-case": 2, // http://eslint.org/docs/rules/default-case
+    "dot-notation": [
+      2,
+      { // http://eslint.org/docs/rules/dot-notation
+        "allowKeywords": true
+      }
+    ],
+    "eqeqeq": 0, // http://eslint.org/docs/rules/eqeqeq [REVISIT ME]
+    "guard-for-in": 0, // http://eslint.org/docs/rules/guard-for-in [REVISIT ME]
+    "jsx-quotes": [
+      2,
+      "prefer-double"
+    ], // http://eslint.org/docs/rules/jsx-quotes
+    "no-caller": 2, // http://eslint.org/docs/rules/no-caller
+    "no-else-return": 0, // http://eslint.org/docs/rules/no-else-return [REVISIT ME]
+    "no-eq-null": 2, // http://eslint.org/docs/rules/no-eq-null
+    "no-eval": 2, // http://eslint.org/docs/rules/no-eval
+    "no-extend-native": 2, // http://eslint.org/docs/rules/no-extend-native
+    "no-extra-bind": 2, // http://eslint.org/docs/rules/no-extra-bind
+    "no-fallthrough": 2, // http://eslint.org/docs/rules/no-fallthrough
+    "no-floating-decimal": 2, // http://eslint.org/docs/rules/no-floating-decimal
+    "no-implied-eval": 2, // http://eslint.org/docs/rules/no-implied-eval
+    "no-lone-blocks": 2, // http://eslint.org/docs/rules/no-lone-blocks
+    "no-loop-func": 2, // http://eslint.org/docs/rules/no-loop-func
+    "no-multi-str": 2, // http://eslint.org/docs/rules/no-multi-str
+    "no-native-reassign": 2, // http://eslint.org/docs/rules/no-native-reassign
+    "no-new": 2, // http://eslint.org/docs/rules/no-new
+    "no-new-func": 2, // http://eslint.org/docs/rules/no-new-func
+    "no-new-wrappers": 2, // http://eslint.org/docs/rules/no-new-wrappers
+    "no-octal": 2, // http://eslint.org/docs/rules/no-octal
+    "no-octal-escape": 2, // http://eslint.org/docs/rules/no-octal-escape
+    "no-param-reassign": 0, // http://eslint.org/docs/rules/no-param-reassign [REVISIT ME]
+    "no-proto": 2, // http://eslint.org/docs/rules/no-proto
+    "no-redeclare": 2, // http://eslint.org/docs/rules/no-redeclare
+    "no-return-assign": 2, // http://eslint.org/docs/rules/no-return-assign
+    "no-script-url": 2, // http://eslint.org/docs/rules/no-script-url
+    "no-self-compare": 2, // http://eslint.org/docs/rules/no-self-compare
+    "no-sequences": 2, // http://eslint.org/docs/rules/no-sequences
+    "no-throw-literal": 2, // http://eslint.org/docs/rules/no-throw-literal
+    "no-with": 2, // http://eslint.org/docs/rules/no-with
+    "quotes": [
+      2,
+      "single"
+    ], // http://eslint.org/docs/rules/quotes.html
+    "radix": 2, // http://eslint.org/docs/rules/radix
+    "computed-property-spacing": [
+      2,
+      "never"
+    ], // http://eslint.org/docs/rules/space-in-brackets.html
+    "array-bracket-spacing": [
+      2,
+      "never"
+    ], // http://eslint.org/docs/rules/space-in-brackets.html
+    "object-curly-spacing": [
+      2,
+      "never"
+    ], // http://eslint.org/docs/rules/space-in-brackets.html
+    "space-infix-ops": 2, // http://eslint.org/docs/rules/space-infix-ops.html
+    "vars-on-top": 0, // http://eslint.org/docs/rules/vars-on-top
+    "wrap-iife": [
+      2,
+      "any"
+    ], // http://eslint.org/docs/rules/wrap-iife
+    "yoda": 2, // http://eslint.org/docs/rules/yoda
     /**
      * React
      */
-    "react/display-name": 0,         // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md
-    "react/no-multi-comp": [0, {"ignoreStateless": true}], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md
-    "react/jsx-key": 2,              // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md
-    "react/jsx-no-undef": 2,         // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md
+    "react/display-name": 0, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/display-name.md
+    "react/no-multi-comp": [
+      0,
+      {
+        "ignoreStateless": true
+      }
+    ], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md
+    "react/jsx-key": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-key.md
+    "react/jsx-no-undef": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-undef.md
     "react/jsx-no-duplicate-props": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-duplicate-props.md
-    "react/jsx-uses-react": 2,       // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md
-    "react/jsx-uses-vars": 2,        // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md
-    "react/no-did-mount-set-state": [2, "allow-in-func"], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md
+    "react/jsx-uses-react": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-react.md
+    "react/jsx-uses-vars": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-uses-vars.md
+    "react/no-did-mount-set-state": [
+      2,
+      "allow-in-func"
+    ], // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-mount-set-state.md
     "react/no-did-update-set-state": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-did-update-set-state.md"
-    "react/no-unknown-property": 2,  // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md
-    "react/prop-types": [2, {
-      "ignore": ["className", "children", "location", "params"]
-    }],
-    "react/react-in-jsx-scope": 2,   // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md
-    "react/self-closing-comp": 2,    // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
-    "react/sort-comp": 2,            // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md
-    "react/wrap-multilines": 2,      // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md
-
+    "react/no-unknown-property": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-unknown-property.md
+    "react/prop-types": [
+      2,
+      {
+        "ignore": [
+          "className",
+          "children",
+          "location",
+          "params"
+        ]
+      }
+    ],
+    "react/react-in-jsx-scope": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/react-in-jsx-scope.md
+    "react/self-closing-comp": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md
+    "react/sort-comp": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md
+    "react/wrap-multilines": 2, // https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/wrap-multilines.md
     /**
      * Custom
      */
-    "getsentry/jsx-needs-il8n": 0,   // highlights literals in JSX components w/o translation tags
+    "getsentry/jsx-needs-il8n": 0, // highlights literals in JSX components w/o translation tags
   }
-
-}
+}

+ 17 - 9
package.json

@@ -67,28 +67,36 @@
   "APIMethod": "stub",
   "proxyURL": "http://localhost:8000",
   "scripts": {
-    "test": "node_modules/karma/bin/karma start tests/karma.conf.js --single-run",
-    "test-watch": "node_modules/karma/bin/karma start tests/karma.conf.js",
+    "test": "NODE_ENV=test node_modules/.bin/jest tests/js/spec",
+    "test-watch": "NODE_ENV=test node_modules/.bin/jest tests/js/spec --watch",
     "lint": "node_modules/.bin/eslint tests/js src/sentry/static/sentry/app --ext .jsx"
   },
+  "jest": {
+    "modulePaths": [
+      "<rootDir>/src/sentry/static/sentry"
+    ],
+    "setupFiles": [
+      "<rootDir>/tests/js/setup.js"
+    ],
+    "unmockedModulePathPatterns": [
+      "<rootDir>/node_modules/react",
+      "<rootDir>/node_modules/reflux"
+    ]
+  },
   "devDependencies": {
     "babel-eslint": "7.1.1",
+    "babel-jest": "^19.0.0",
     "chai": "3.4.1",
     "enzyme": "2.4.1",
     "eslint": "1.9.0",
     "eslint-plugin-getsentry": "1.0.0",
     "eslint-plugin-react": "3.11.2",
-    "karma": "0.13.22",
-    "karma-chai": "0.1.0",
-    "karma-mocha": "0.1.10",
-    "karma-phantomjs-launcher": "1.0.2",
-    "karma-sinon": "1.0.4",
+    "jest": "^19.0.2",
     "karma-sourcemap-loader": "0.3.7",
     "karma-webpack": "2.0.1",
-    "mocha": "2.3.4",
     "phantomjs-prebuilt": "2.1.14",
     "prettier": "1.1.0",
     "sinon": "1.17.2",
     "sinon-chai": "2.8.0"
   }
-}
+}

+ 13 - 0
src/sentry/static/sentry/app/__mocks__/translations.jsx

@@ -0,0 +1,13 @@
+export function getTranslations(language) {
+  return {
+    '': {
+      domain: 'the_domain',
+      lang: 'en',
+      plural_forms: 'nplurals=2; plural=(n != 1);'
+    }
+  };
+}
+
+export function translationsExist(language) {
+  return true; // no translations for tests
+}

+ 6 - 1
src/sentry/static/sentry/app/components/events/interfaces/richHttpContent.jsx

@@ -32,7 +32,12 @@ const RichHttpContent = React.createClass({
             : [[k, val]] // key has single value
         );
       }, [])
-      .sort(function([keyA], [keyB]) {
+      .sort(function([keyA, valA], [keyB, valB]) {
+        // if keys are identical, sort on value
+        if (keyA === keyB) {
+          return valA < valB ? -1 : 1;
+        }
+
         return keyA < keyB ? -1 : 1;
       });
   },

+ 1 - 0
src/sentry/static/sentry/app/locale.jsx

@@ -7,6 +7,7 @@ import _ from 'underscore';
 
 let LOCALE_DEBUG = false;
 
+let sessionStorage = window.sessionStorage;
 if (sessionStorage && sessionStorage.getItem('localeDebug') == '1') {
   LOCALE_DEBUG = true;
 }

+ 1 - 0
src/sentry/static/sentry/app/routes.jsx

@@ -4,6 +4,7 @@ import {Redirect, Route, IndexRoute, IndexRedirect} from 'react-router';
 import HookStore from './stores/hookStore';
 
 import AccountAuthorizations from './views/accountAuthorizations';
+
 import AccountLayout from './views/accountLayout';
 import ApiApplications from './views/apiApplications';
 import ApiApplicationDetails from './views/apiApplicationDetails';

+ 3 - 5
src/sentry/static/sentry/app/stores/groupStore.jsx

@@ -2,7 +2,7 @@ import jQuery from 'jquery';
 import Reflux from 'reflux';
 import GroupActions from '../actions/groupActions';
 import IndicatorStore from './indicatorStore';
-import utils from '../utils';
+import PendingChangeQueue from '../utils/pendingChangeQueue';
 import {t} from '../locale';
 import _ from 'underscore';
 
@@ -16,15 +16,13 @@ const GroupStore = Reflux.createStore({
   listenables: [GroupActions],
 
   init() {
-    this.items = [];
-    this.statuses = {};
-    this.pendingChanges = new utils.PendingChangeQueue();
+    this.reset();
   },
 
   reset() {
     this.items = [];
     this.statuses = {};
-    this.pendingChanges.clear();
+    this.pendingChanges = new PendingChangeQueue();
   },
 
   // TODO(dcramer): this should actually come from an action of some sorts

+ 16 - 0
src/sentry/static/sentry/app/utils/__mocks__/localStorage.jsx

@@ -0,0 +1,16 @@
+let localStorageMock = function() {
+  let store = {};
+  return {
+    getItem: function(key) {
+      return store[key];
+    },
+    setItem: function(key, value) {
+      store[key] = value.toString();
+    },
+    clear: function() {
+      store = {};
+    }
+  };
+};
+
+export default localStorageMock();

+ 2 - 0
src/sentry/static/sentry/app/utils/localStorage.jsx

@@ -1,5 +1,7 @@
 let functions = {};
 
+let localStorage = window.localStorage;
+
 try {
   let mod = 'sentry';
   localStorage.setItem(mod, mod);

Some files were not shown because too many files changed in this diff