diff --git a/frontend/src/metabase/components/EmptyState.jsx b/frontend/src/metabase/components/EmptyState.jsx
index 476bb2824a8f6356466b5af459402dd5ac15b9cc..119b4c6f9a67f8d904df1a159a1dac969b862613 100644
--- a/frontend/src/metabase/components/EmptyState.jsx
+++ b/frontend/src/metabase/components/EmptyState.jsx
@@ -1,12 +1,11 @@
 /* @flow */
 import React, {PropTypes} from "react";
 import {Link} from "react-router";
-import cx from "classnames";
 
 /*
  * EmptyState is a component that can
  *   1) introduce a new section of Metabase to a user, and encourages the user to take an action
-  *  2) indicate an empty result after an user-triggered search/query
+  *  2) indicate an empty result after a user-triggered search/query
  */
 
 
diff --git a/frontend/src/metabase/dashboards/components/DashboardList.jsx b/frontend/src/metabase/dashboards/components/DashboardList.jsx
index fcd9b5e5a5560a52724193223b6aa3d3f8132b85..db4fea30a7ce50c86d2920a53f331f009ee4a897 100644
--- a/frontend/src/metabase/dashboards/components/DashboardList.jsx
+++ b/frontend/src/metabase/dashboards/components/DashboardList.jsx
@@ -19,7 +19,7 @@ type DashboardListItemType = {
 
 const enhance = withState('hover', 'setHover', false)
 const DashboardListItem = enhance(({dashboard, hover, setHover}: DashboardListItemType) =>
-    <li key={dashboard.id} className="Grid-cell flex-retain-width">
+    <li className="Grid-cell flex-retain-width">
         <Link to={Urls.dashboard(dashboard.id)}
               data-metabase-event={"Navbar;Dashboards;Open Dashboard;" + dashboard.id}
               className="flex align-center border-box p2 bg-white bg-brand-hover rounded hover-parent hover--display no-decoration"
@@ -54,9 +54,10 @@ export default class DashboardList extends Component {
     render() {
         const {dashboards} = this.props;
 
+        console.log('daash:', dashboards)
         return (
             <ol className="Grid Grid--guttersXl Grid--1of2 large-Grid--1of3">
-                { dashboards.map(dash => <DashboardListItem dashboard={dash}/>)}
+                { dashboards.map(dash => <DashboardListItem key={dash.id} dashboard={dash}/>)}
             </ol>
         );
     }
diff --git a/frontend/src/metabase/dashboards/containers/Dashboards.spec.data.js b/frontend/src/metabase/dashboards/containers/Dashboards.spec.data.js
new file mode 100644
index 0000000000000000000000000000000000000000..9e91094898e3e5b9c672697eaec36e8e659a66b5
--- /dev/null
+++ b/frontend/src/metabase/dashboards/containers/Dashboards.spec.data.js
@@ -0,0 +1,81 @@
+import reducers from 'metabase/reducers-main';
+import { combineReducers, createStore, applyMiddleware} from 'redux'
+
+import thunk from "redux-thunk";
+import promise from "redux-promise";
+import logger from "redux-logger";
+
+const createTestStore = (initialState) =>
+    createStore(combineReducers(reducers), initialState, applyMiddleware(thunk, promise, logger()))
+
+export const noDashboardsStore = createTestStore({dashboards: {dashboardListing: []}});
+
+// Dumped from redux state tree 4/10/17
+export const twoDashboardsStore = createTestStore({
+    dashboards: {
+        dashboardListing: [
+            {
+                description: 'For seeing the usual response times, feedback topics, our response rate, how often customers are directed to our knowledge base instead of providing a customized response',
+                creator: {
+                    email: 'atte@metabase.com',
+                    first_name: 'Atte',
+                    last_login: '2017-04-10T23:56:12.562Z',
+                    is_qbnewb: false,
+                    is_superuser: false,
+                    id: 1,
+                    last_name: 'Keinänen',
+                    date_joined: '2017-03-17T03:37:27.396Z',
+                    common_name: 'Atte Keinänen'
+                },
+                enable_embedding: false,
+                show_in_getting_started: false,
+                name: 'Customer Feedback Analysis',
+                caveats: null,
+                creator_id: 1,
+                updated_at: '2017-04-05T00:23:20.991Z',
+                made_public_by_id: null,
+                embedding_params: null,
+                id: 3,
+                parameters: [
+                    {
+                        name: 'Date Range',
+                        slug: 'date_range',
+                        id: 'fc68dfa5',
+                        type: 'date/range',
+                        'default': '2017-03-07~2017-03-14'
+                    }
+                ],
+                created_at: '2017-03-28T23:24:16.891Z',
+                public_uuid: null,
+                points_of_interest: null
+            },
+            {
+                description: 'For seeing our progress over time, and if there are bottlenecks in our product development process',
+                creator: {
+                    email: 'atte@metabase.com',
+                    first_name: 'Atte',
+                    last_login: '2017-04-10T23:56:12.562Z',
+                    is_qbnewb: false,
+                    is_superuser: false,
+                    id: 1,
+                    last_name: 'Keinänen',
+                    date_joined: '2017-03-17T03:37:27.396Z',
+                    common_name: 'Atte Keinänen'
+                },
+                enable_embedding: false,
+                show_in_getting_started: false,
+                name: 'Kanban Breakdown Chart',
+                caveats: null,
+                creator_id: 1,
+                updated_at: '2017-04-04T20:41:50.616Z',
+                made_public_by_id: null,
+                embedding_params: null,
+                id: 7,
+                parameters: [],
+                created_at: '2017-04-04T20:39:01.263Z',
+                public_uuid: null,
+                points_of_interest: null
+            }
+        ]
+    }
+});
diff --git a/frontend/src/metabase/dashboards/containers/Dashboards.spec.js b/frontend/src/metabase/dashboards/containers/Dashboards.spec.js
new file mode 100644
index 0000000000000000000000000000000000000000..207d0f45c3a775149146032360507fda0d41f684
--- /dev/null
+++ b/frontend/src/metabase/dashboards/containers/Dashboards.spec.js
@@ -0,0 +1,28 @@
+import React from 'react';
+import renderer from 'react-test-renderer';
+import {Provider} from 'react-redux';
+
+import Dashboards from './Dashboards';
+import {noDashboardsStore, twoDashboardsStore} from './Dashboards.spec.data';
+
+describe('Dashboards list view', () => {
+    it('should render correctly without dashboards', () => {
+        const tree = renderer.create(
+            <Provider store={noDashboardsStore}>
+                <Dashboards />
+            </Provider>).toJSON();
+
+        console.log(tree);
+        expect(tree).toMatchSnapshot()
+    })
+
+    it('should render correctly with two dashboards', () => {
+        const tree = renderer.create(
+            <Provider store={twoDashboardsStore}>
+                <Dashboards />
+            </Provider>).toJSON();
+
+        console.log(tree);
+        expect(tree).toMatchSnapshot()
+    })
+})
\ No newline at end of file
diff --git a/frontend/src/metabase/dashboards/containers/__snapshots__/Dashboards.spec.js.snap b/frontend/src/metabase/dashboards/containers/__snapshots__/Dashboards.spec.js.snap
new file mode 100644
index 0000000000000000000000000000000000000000..453d329920f4091d0fff1e66f7d6221e96d63a91
--- /dev/null
+++ b/frontend/src/metabase/dashboards/containers/__snapshots__/Dashboards.spec.js.snap
@@ -0,0 +1,183 @@
+exports[`Dashboards list view should render correctly with two dashboards 1`] = `
+<div
+  className="relative mx4"
+  style={undefined}>
+  <div>
+    <div
+      className="flex align-center pt4 pb1">
+      <div
+        className="flex align-center">
+        <h2
+          className="mr1">
+          Dashboards
+        </h2>
+      </div>
+      <svg
+        className="flex-align-right px4 cursor-pointer text-grey-5 text-brand-hover"
+        fill="currentcolor"
+        height={20}
+        name="add"
+        onClick={[Function]}
+        size={20}
+        viewBox="0 0 32 32"
+        width={20}>
+        <path
+          d="M19,13 L19,2 L14,2 L14,13 L2,13 L2,18 L14,18 L14,30 L19,30 L19,18 L30,18 L30,13 L19,13 Z" />
+      </svg>
+    </div>
+    <div
+      className="flex align-center pb1">
+      <div
+        className="searchHeader">
+        <svg
+          className="searchIcon"
+          fill="currentcolor"
+          height={18}
+          name="search"
+          size={18}
+          viewBox="0 0 32 32"
+          width={18}>
+          <path
+            d="M12 0 A12 12 0 0 0 0 12 A12 12 0 0 0 12 24 A12 12 0 0 0 18.5 22.25 L28 32 L32 28 L22.25 18.5 A12 12 0 0 0 24 12 A12 12 0 0 0 12 0 M12 4 A8 8 0 0 1 12 20 A8 8 0 0 1 12 4  " />
+        </svg>
+        <input
+          className="input searchBox"
+          onChange={[Function]}
+          placeholder="Filter this list..."
+          type="text"
+          value="" />
+      </div>
+    </div>
+    <ol
+      className="Grid Grid--guttersXl Grid--1of2 large-Grid--1of3">
+      <li
+        className="Grid-cell flex-retain-width">
+        <a
+          className="flex align-center border-box p2 bg-white bg-brand-hover rounded hover-parent hover--display no-decoration"
+          data-metabase-event="Navbar;Dashboards;Open Dashboard;3"
+          onClick={[Function]}
+          onMouseEnter={[Function]}
+          onMouseLeave={[Function]}
+          style={
+            Object {
+              "border": "1px solid rgba(220,225,228,0.50)",
+              "boxShadow": "0 1px 3px 0 rgba(220,220,220,0.50)",
+              "height": "80px",
+            }
+          }>
+          <svg
+            className="pr2 text-grey-1"
+            fill="currentcolor"
+            height={32}
+            name="dashboard"
+            size={32}
+            viewBox="0 0 32 32"
+            width={32}>
+            <path
+              d="M32,29 L32,4 L32,0 L0,0 L0,8 L28,8 L28,28 L4,28 L4,8 L0,8 L0,29.5 L0,32 L32,32 L32,29 Z M7.27272727,18.9090909 L17.4545455,18.9090909 L17.4545455,23.2727273 L7.27272727,23.2727273 L7.27272727,18.9090909 Z M7.27272727,12.0909091 L24.7272727,12.0909091 L24.7272727,16.4545455 L7.27272727,16.4545455 L7.27272727,12.0909091 Z M20.3636364,18.9090909 L24.7272727,18.9090909 L24.7272727,23.2727273 L20.3636364,23.2727273 L20.3636364,18.9090909 Z" />
+          </svg>
+          <div
+            className="flex-full flex-retain-width">
+            <h4
+              className="text-ellipsis text-nowrap overflow-hidden text-brand"
+              style={
+                Object {
+                  "marginBottom": "0.2em",
+                }
+              }>
+              Customer Feedback Analysis
+            </h4>
+            <div
+              className="text-small text-uppercase text-bold text-grey-3">
+              Mar 28, 2017
+            </div>
+          </div>
+        </a>
+      </li>
+      <li
+        className="Grid-cell flex-retain-width">
+        <a
+          className="flex align-center border-box p2 bg-white bg-brand-hover rounded hover-parent hover--display no-decoration"
+          data-metabase-event="Navbar;Dashboards;Open Dashboard;7"
+          onClick={[Function]}
+          onMouseEnter={[Function]}
+          onMouseLeave={[Function]}
+          style={
+            Object {
+              "border": "1px solid rgba(220,225,228,0.50)",
+              "boxShadow": "0 1px 3px 0 rgba(220,220,220,0.50)",
+              "height": "80px",
+            }
+          }>
+          <svg
+            className="pr2 text-grey-1"
+            fill="currentcolor"
+            height={32}
+            name="dashboard"
+            size={32}
+            viewBox="0 0 32 32"
+            width={32}>
+            <path
+              d="M32,29 L32,4 L32,0 L0,0 L0,8 L28,8 L28,28 L4,28 L4,8 L0,8 L0,29.5 L0,32 L32,32 L32,29 Z M7.27272727,18.9090909 L17.4545455,18.9090909 L17.4545455,23.2727273 L7.27272727,23.2727273 L7.27272727,18.9090909 Z M7.27272727,12.0909091 L24.7272727,12.0909091 L24.7272727,16.4545455 L7.27272727,16.4545455 L7.27272727,12.0909091 Z M20.3636364,18.9090909 L24.7272727,18.9090909 L24.7272727,23.2727273 L20.3636364,23.2727273 L20.3636364,18.9090909 Z" />
+          </svg>
+          <div
+            className="flex-full flex-retain-width">
+            <h4
+              className="text-ellipsis text-nowrap overflow-hidden text-brand"
+              style={
+                Object {
+                  "marginBottom": "0.2em",
+                }
+              }>
+              Kanban Breakdown Chart
+            </h4>
+            <div
+              className="text-small text-uppercase text-bold text-grey-3">
+              Apr 4, 2017
+            </div>
+          </div>
+        </a>
+      </li>
+    </ol>
+  </div>
+</div>
+`;
+
+exports[`Dashboards list view should render correctly without dashboards 1`] = `
+<div
+  className="relative mx4 flex-full flex align-center justify-center"
+  style={undefined}>
+  <div
+    className="mt2">
+    <div
+      className="text-centered text-brand-light my2">
+      <img
+        alt={
+          <div>
+            Put the charts and graphs you look at
+            <br />
+            frequently in a single, handy place.
+          </div>
+        }
+        className="mln2"
+        height="250px"
+        src="/app/img/dashboard_illustration.png"
+        srcSet="/app/img/dashboard_illustration@2x.png 2x" />
+      <div
+        className="flex justify-center">
+        <div>
+          Put the charts and graphs you look at
+          <br />
+          frequently in a single, handy place.
+        </div>
+        }
+      </div>
+      <a
+        className="Button Button--primary mt3"
+        onClick={[Function]}>
+        Create a dashboard
+      </a>
+    </div>
+  </div>
+</div>
+`;
diff --git a/frontend/src/metabase/lib/formatting.js b/frontend/src/metabase/lib/formatting.js
index 1a2a30921af916035253fe3af8f0f1092a0d90f7..f6db24482f44966263512c399afdcf79bdefa09f 100644
--- a/frontend/src/metabase/lib/formatting.js
+++ b/frontend/src/metabase/lib/formatting.js
@@ -1,7 +1,7 @@
 import d3 from "d3";
 import inflection from "inflection";
 import moment from "moment";
-import Humanize from "humanize";
+import Humanize from "humanize-plus";
 import React from "react";
 
 import ExternalLink from "metabase/components/ExternalLink.jsx";
diff --git a/frontend/test/__mocks__/fileMock.js b/frontend/test/__mocks__/fileMock.js
new file mode 100644
index 0000000000000000000000000000000000000000..84c1da6fdcb26b510616f8dbbec6416945fd4104
--- /dev/null
+++ b/frontend/test/__mocks__/fileMock.js
@@ -0,0 +1 @@
+module.exports = 'test-file-stub';
\ No newline at end of file
diff --git a/frontend/test/__mocks__/styleMock.js b/frontend/test/__mocks__/styleMock.js
new file mode 100644
index 0000000000000000000000000000000000000000..a0995453769c8a76594b879c09c58064530c155a
--- /dev/null
+++ b/frontend/test/__mocks__/styleMock.js
@@ -0,0 +1 @@
+module.exports = {};
\ No newline at end of file
diff --git a/package.json b/package.json
index 77edb5a62183b55dc27d90deac3fb94b2cfdd11f..e64f568cf3c2348e5fd78006a1491bbf80562e67 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
     "history": "^4.5.0",
     "humanize-plus": "^1.8.1",
     "icepick": "^1.1.0",
+    "identity-obj-proxy": "^3.0.0",
     "inflection": "^1.7.1",
     "isomorphic-fetch": "^2.2.1",
     "js-cookie": "^2.1.2",
@@ -173,11 +174,19 @@
     ]
   },
   "jest": {
+    "moduleNameMapper": {
+      "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/frontend/test/__mocks__/fileMock.js",
+      "\\.(css|less)$": "identity-obj-proxy",
+      "^promise-loader": "<rootDir>/frontend/test/__mocks__/fileMock.js"
+    },
     "testPathIgnorePatterns": [
       "<rootDir>/frontend/test/"
     ],
     "modulePaths": [
       "<rootDir>/frontend/src"
+    ],
+    "setupFiles": [
+      "<rootDir>/frontend/test/metabase-bootstrap.js"
     ]
   }
 }
diff --git a/yarn.lock b/yarn.lock
index a343edb47001c17cfd759be5ee61eccd6f0f3c6e..6ffd6c53e4f5982cc4058bdaae93cac107909059 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3603,6 +3603,10 @@ har-validator@~2.0.6:
     is-my-json-valid "^2.12.4"
     pinkie-promise "^2.0.0"
 
+harmony-reflect@^1.4.6:
+  version "1.5.1"
+  resolved "https://registry.yarnpkg.com/harmony-reflect/-/harmony-reflect-1.5.1.tgz#b54ca617b00cc8aef559bbb17b3d85431dc7e329"
+
 has-ansi@^0.1.0:
   version "0.1.0"
   resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e"
@@ -3833,6 +3837,12 @@ icss-replace-symbols@^1.0.2:
   version "1.0.2"
   resolved "https://registry.yarnpkg.com/icss-replace-symbols/-/icss-replace-symbols-1.0.2.tgz#cb0b6054eb3af6edc9ab1d62d01933e2d4c8bfa5"
 
+identity-obj-proxy@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/identity-obj-proxy/-/identity-obj-proxy-3.0.0.tgz#94d2bda96084453ef36fbc5aaec37e0f79f1fc14"
+  dependencies:
+    harmony-reflect "^1.4.6"
+
 ieee754@^1.1.4:
   version "1.1.8"
   resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.8.tgz#be33d40ac10ef1926701f6f08a2d86fbfd1ad3e4"