Repository: reactioncommerce/reaction-next-starterkit
Pull request #35 (2018-04-09 18:42:09)

feat: navigation
Closes:
#6 
#9 

## Issue
The storefront app needed a responsive 3 tier navigation.

## Solution
Used Material UI components to build out the base of the storefront navigation.

## Testing 
Startup the storefront app to view desktop navigation, view the app in a phone or responsive design view to see the mobile navigation.

## Note
1. The current navigation is not connected to the `tags` GQL endpoint so it's only displaying test data for now.
2. Sub nav links not working because we need to pull the real `tags` data to build out the URL paths.
3. Open to reorg the Navigation folder to keep each component in their own folders.


Patch:
diff --git a/package.json b/package.json
index 771a19a2..eadc0e80 100644
--- a/package.json
+++ b/package.json
@@ -74,6 +74,7 @@
   "jest": {
     "roots": [
       "./src"
-    ]
+    ],
+    "setupTestFrameworkScriptFile": "<rootDir>/src/setupTests.js"
   }
 }
diff --git a/src/components/Cart/Cart.js b/src/components/Cart/Cart.js
new file mode 100644
index 00000000..60a8e4e8
--- /dev/null
+++ b/src/components/Cart/Cart.js
@@ -0,0 +1,37 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { inject, observer } from "mobx-react";
+import { withStyles } from "material-ui/styles";
+import Drawer from "material-ui/Drawer";
+
+const styles = () => ({
+  cart: {
+    width: "90vw"
+  }
+});
+
+@withStyles(styles)
+@inject("uiStore")
+@observer
+class Cart extends Component {
+  static propTypes = {
+    classes: PropTypes.object,
+    uiStore: PropTypes.object
+  };
+
+  static defaultProps = {
+    classes: {},
+    uiStore: {}
+  };
+
+  render() {
+    const { classes, uiStore } = this.props;
+    return (
+      <Drawer anchor="right" open={uiStore.cartOpen} onClose={uiStore.toggleCartOpen}>
+        <div className={classes.cart}>Cart Component</div>
+      </Drawer>
+    );
+  }
+}
+
+export default Cart;
diff --git a/src/components/Cart/CartToggle.js b/src/components/Cart/CartToggle.js
new file mode 100644
index 00000000..def11b6d
--- /dev/null
+++ b/src/components/Cart/CartToggle.js
@@ -0,0 +1,28 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { inject, observer } from "mobx-react";
+import IconButton from "material-ui/IconButton";
+import CartIcon from "mdi-material-ui/Cart";
+
+@inject("uiStore")
+@observer
+class CartToggle extends Component {
+  static propTypes = {
+    uiStore: PropTypes.object
+  };
+
+  static defaultProps = {
+    uiStore: {}
+  };
+
+  render() {
+    const { uiStore } = this.props;
+    return (
+      <IconButton color="inherit" onClick={uiStore.toggleCartOpen}>
+        <CartIcon />
+      </IconButton>
+    );
+  }
+}
+
+export default CartToggle;
diff --git a/src/components/Cart/index.js b/src/components/Cart/index.js
new file mode 100644
index 00000000..60c44e96
--- /dev/null
+++ b/src/components/Cart/index.js
@@ -0,0 +1,2 @@
+export { default as Cart } from "./Cart";
+export { default as CartToggle } from "./CartToggle";
diff --git a/src/components/Header/Header.js b/src/components/Header/Header.js
index d107c6a0..70afea1e 100644
--- a/src/components/Header/Header.js
+++ b/src/components/Header/Header.js
@@ -1,46 +1,98 @@
 import React, { Component } from "react";
 import PropTypes from "prop-types";
 import AppBar from "material-ui/AppBar";
+import Hidden from "material-ui/Hidden";
 import Toolbar from "material-ui/Toolbar";
 import Typography from "material-ui/Typography";
-import IconButton from "material-ui/IconButton";
-import Drawer from "material-ui/Drawer";
-import CartIcon from "mdi-material-ui/Cart";
-
-import { inject, observer } from "mobx-react";
 import { withStyles } from "material-ui/styles";
+import { DesktopNavigation, MobileNavigation, MobileNavigationToggle } from "components/Navigation";
+import { CartToggle } from "components/Cart";
 
-const styles = () => ({
-  title: {
+// TODO: Get tag data from GraphQL, this is just a sample
+const tags = [
+  { name: "men", title: "Men" },
+  { name: "women", title: "Women" },
+  {
+    name: "kids",
+    title: "Kids",
+    relatedTags: [
+      {
+        name: "test1",
+        title: "Test 1",
+        relatedTags: [
+          { name: "men", title: "Men" },
+          { name: "women", title: "Women" },
+          { name: "men", title: "Men" },
+          { name: "women", title: "Women" }
+        ]
+      },
+      {
+        name: "test2",
+        title: "Test 2",
+        relatedTags: [
+          { name: "men", title: "Men" },
+          { name: "women", title: "Women" },
+          { name: "men", title: "Men" },
+          { name: "women", title: "Women" }
+        ]
+      },
+      { name: "test3", title: "Test 3" }
+    ]
+  }
+];
+
+const styles = (theme) => ({
+  controls: {
+    alignItems: "inherit",
+    display: "inherit",
     flex: 1
   },
-  cart: {
-    width: 320
+  title: {
+    marginRight: theme.spacing.unit
+  },
+  toolbar: {
+    alignItems: "center",
+    display: "flex",
+    justifyContent: "space-between"
   }
 });
 
 @withStyles(styles)
-@inject("uiStore")
-@observer
 class Header extends Component {
   static propTypes = {
     classes: PropTypes.object,
     uiStore: PropTypes.object
-  }
+  };
+
+  static defaultProps = {
+    classes: {},
+    uiStore: {}
+  };
+
   render() {
-    const { classes, uiStore } = this.props;
+    const { classes } = this.props;
 
     return (
-      <AppBar position="static">
-        <Toolbar>
-          <Typography className={classes.title} variant="title">Reaction Commerce</Typography>
-          <IconButton onClick={uiStore.toggleCartOpen}>
-            <CartIcon />
-          </IconButton>
+      <AppBar position="static" elevation={0}>
+        <Toolbar className={classes.toolbar}>
+          <Hidden mdUp>
+            <MobileNavigationToggle />
+          </Hidden>
+
+          <div className={classes.controls}>
+            <Typography className={classes.title} color="inherit" variant="title">
+              Reaction
+            </Typography>
+
+            <Hidden smDown initialWidth={"md"}>
+              <DesktopNavigation navItems={tags} />
+            </Hidden>
+          </div>
+
+          <CartToggle />
         </Toolbar>
-        <Drawer anchor="right" open={uiStore.cartOpen} onClose={uiStore.toggleCartOpen}>
-          <div className={classes.cart}>Cart</div>
-        </Drawer>
+
+        <MobileNavigation navItems={tags} />
       </AppBar>
     );
   }
diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js
index 7962bf6f..ba1d1dea 100644
--- a/src/components/Layout/Layout.js
+++ b/src/components/Layout/Layout.js
@@ -1,15 +1,33 @@
-import React from "react";
+import React, { Component } from "react";
+import PropTypes from "prop-types";
 import Helmet from "react-helmet";
+
 import Header from "components/Header";
+import { Cart } from "components/Cart";
+
+class Layout extends Component {
+  static propTypes = {
+    children: PropTypes.node,
+    title: PropTypes.string
+  };
 
-export default ({ children, title = "" }) => (
-  <div>
-    <Helmet>
-      <title>{title}</title>
-    </Helmet>
-    <Header />
+  static defaultProps = {
+    title: ""
+  };
 
-    {children}
+  render() {
+    const { children, title } = this.props;
+    return (
+      <div>
+        <Helmet>
+          <title>{title}</title>
+        </Helmet>
+        <Header />
+        <main>{children}</main>
+        <Cart />
+      </div>
+    );
+  }
+}
 
-  </div>
-);
+export default Layout;
diff --git a/src/components/Navigation/DesktopNavigation.js b/src/components/Navigation/DesktopNavigation.js
new file mode 100644
index 00000000..acee0c57
--- /dev/null
+++ b/src/components/Navigation/DesktopNavigation.js
@@ -0,0 +1,27 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+
+import { DesktopNavigationItem } from "components/Navigation";
+
+class DesktopNavigation extends Component {
+  static propTypes = {
+    classes: PropTypes.object,
+    navItems: PropTypes.arrayOf(PropTypes.object)
+  };
+
+  static defaultProps = {
+    classes: {},
+    navItems: []
+  };
+
+  renderNavItem(navItem, index) {
+    return <DesktopNavigationItem key={index} navItem={navItem} />;
+  }
+
+  render() {
+    const { navItems } = this.props;
+    return <nav>{navItems.map(this.renderNavItem)}</nav>;
+  }
+}
+
+export default DesktopNavigation;
diff --git a/src/components/Navigation/DesktopNavigationItem.js b/src/components/Navigation/DesktopNavigationItem.js
new file mode 100644
index 00000000..8ca67b5a
--- /dev/null
+++ b/src/components/Navigation/DesktopNavigationItem.js
@@ -0,0 +1,128 @@
+import React, { Component, Fragment } from "react";
+import PropTypes from "prop-types";
+import Router from "next/router";
+import Button from "material-ui/Button";
+import Divider from "material-ui/Divider";
+import Grid from "material-ui/Grid";
+import ListItemText from "material-ui/List/ListItemText";
+import MenuList from "material-ui/Menu/MenuList";
+import MenuItem from "material-ui/Menu/MenuItem";
+import Popover from "material-ui/Popover";
+import ChevronDownIcon from "mdi-material-ui/ChevronDown";
+import ChevronUpIcon from "mdi-material-ui/ChevronUp";
+
+import { observer } from "mobx-react";
+import { action, computed, observable } from "mobx";
+import { withStyles } from "material-ui/styles";
+
+const styles = (theme) => ({
+  popover: {
+    maxWidth: "100vw",
+    padding: theme.spacing.unit * 2,
+    width: "100vw"
+  },
+  grid: {
+    width: "100vw"
+  }
+});
+
+@withStyles(styles)
+@observer
+class DesktopNavigationItem extends Component {
+  static propTypes = {
+    classes: PropTypes.object,
+    navItem: PropTypes.object
+  };
+
+  static defaultProps = {
+    classes: {},
+    navItem: {}
+  };
+
+  get hasSubNavItems() {
+    const { navItem: { relatedTags } } = this.props;
+    return Array.isArray(relatedTags) && relatedTags.length > 0;
+  }
+
+  @observable _isSubNavOpen = false;
+
+  @computed
+  get isSubNavOpen() {
+    return this._isSubNavOpen;
+  }
+
+  set isSubNavOpen(value) {
+    this._isSubNavOpen = value;
+  }
+
+  @action
+  onClick = () => {
+    const { navItem } = this.props;
+
+    if (this.hasSubNavItems) {
+      this.isSubNavOpen = !this.isSubNavOpen;
+    } else {
+      Router.push(`/tag/${navItem.name}`);
+    }
+  };
+
+  @action
+  onClose = () => {
+    this.isSubNavOpen = false;
+  };
+
+  renderSubNav(navItemGroup) {
+    return (
+      <Fragment>
+        <Divider />
+        {navItemGroup.relatedTags.map((navItem, index) => (
+          <MenuItem dense key={index}>
+            <ListItemText primary={navItem.title} />
+          </MenuItem>
+        ))}
+      </Fragment>
+    );
+  }
+
+  renderPopover() {
+    const { classes, navItem: { relatedTags } } = this.props;
+    return (
+      <Popover
+        classes={{ paper: classes.popover }}
+        anchorReference={"anchorPosition"}
+        anchorPosition={{ top: 64 }}
+        elevation={1}
+        onClose={this.onClose}
+        open={this.isSubNavOpen}
+      >
+        <Grid container className={classes.grid} spacing={16}>
+          {relatedTags.map((navItemGroup, index) => (
+            <Grid item key={index}>
+              <MenuList disablePadding>
+                <MenuItem>
+                  <ListItemText primary={navItemGroup.title} />
+                </MenuItem>
+                {Array.isArray(navItemGroup.relatedTags) && this.renderSubNav(navItemGroup)}
+              </MenuList>
+            </Grid>
+          ))}
+        </Grid>
+      </Popover>
+    );
+  }
+
+  render() {
+    const { navItem } = this.props;
+    return (
+      <Fragment>
+        <Button color="inherit" onClick={this.onClick}>
+          {navItem.name}
+          {this.hasSubNavItems && <Fragment>{this.isSubNavOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}</Fragment>}
+        </Button>
+        {this.hasSubNavItems && this.renderPopover()}
+      </Fragment>
+    );
+  }
+}
+
+export default DesktopNavigationItem;
diff --git a/src/components/Navigation/MobileNavigation.js b/src/components/Navigation/MobileNavigation.js
new file mode 100644
index 00000000..fb833f3f
--- /dev/null
+++ b/src/components/Navigation/MobileNavigation.js
@@ -0,0 +1,48 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { inject, observer } from "mobx-react";
+import { withStyles } from "material-ui/styles";
+import Drawer from "material-ui/Drawer";
+import MenuList from "material-ui/Menu/MenuList";
+import { MobileNavigationItem } from "components/Navigation";
+
+const styles = () => ({
+  nav: {
+    width: 320
+  }
+});
+
+@withStyles(styles)
+@inject("uiStore")
+@observer
+class MobileNavigation extends Component {
+  static propTypes = {
+    classes: PropTypes.object,
+    navItems: PropTypes.arrayOf(PropTypes.object),
+    uiStore: PropTypes.object
+  };
+
+  static defaultProps = {
+    classes: {},
+    navItems: [],
+    uiStore: {}
+  };
+
+  renderNavItem(navItem, index) {
+    return <MobileNavigationItem key={index} navItem={navItem} />;
+  }
+
+  render() {
+    const { classes, navItems, uiStore } = this.props;
+
+    return (
+      <Drawer open={uiStore.menuDrawerOpen} onClose={uiStore.toggleMenuDrawerOpen}>
+        <nav className={classes.nav}>
+          <MenuList>{navItems.map(this.renderNavItem)}</MenuList>
+        </nav>
+      </Drawer>
+    );
+  }
+}
+
+export default MobileNavigation;
diff --git a/src/components/Navigation/MobileNavigationItem.js b/src/components/Navigation/MobileNavigationItem.js
new file mode 100644
index 00000000..7b7c68a7
--- /dev/null
+++ b/src/components/Navigation/MobileNavigationItem.js
@@ -0,0 +1,118 @@
+import React, { Component, Fragment } from "react";
+import PropTypes from "prop-types";
+import Router from "next/router";
+import Divider from "material-ui/Divider";
+import ListItemIcon from "material-ui/List/ListItemIcon";
+import ListItemText from "material-ui/List/ListItemText";
+import Collapse from "material-ui/transitions/Collapse";
+import MenuList from "material-ui/Menu/MenuList";
+import MenuItem from "material-ui/Menu/MenuItem";
+import ChevronDownIcon from "mdi-material-ui/ChevronDown";
+import ChevronUpIcon from "mdi-material-ui/ChevronUp";
+
+import { observer } from "mobx-react";
+import { action, computed, observable } from "mobx";
+import { withStyles } from "material-ui/styles";
+
+const styles = (theme) => ({
+  subNav: {
+    marginBottom: theme.spacing.unit * 2
+  },
+  listItemTextInset: {
+    "&:first-child": {
+      paddingLeft: theme.spacing.unit * 3
+    }
+  }
+});
+
+@withStyles(styles)
+@observer
+class MobileNavigationItem extends Component {
+  static propTypes = {
+    classes: PropTypes.object,
+    navItem: PropTypes.object
+  };
+
+  static defaultProps = {
+    classes: {},
+    navItem: {}
+  };
+
+  get hasSubNavItems() {
+    const { navItem: { relatedTags } } = this.props;
+    return Array.isArray(relatedTags) && relatedTags.length > 0;
+  }
+
+  @observable _isSubNavOpen = false;
+
+  @computed
+  get isSubNavOpen() {
+    return this._isSubNavOpen;
+  }
+
+  set isSubNavOpen(value) {
+    this._isSubNavOpen = value;
+  }
+
+  @action
+  onClick = () => {
+    const { navItem } = this.props;
+
+    if (this.hasSubNavItems) {
+      this.isSubNavOpen = !this.isSubNavOpen;
+    } else {
+      Router.push(`/tag/${navItem.name}`);
+    }
+  };
+
+  renderSubNav(navItemGroup) {
+    const { classes } = this.props;
+    return (
+      <div className={classes.subNav}>
+        <Divider />
+        {navItemGroup.relatedTags.map((navItemGroupItem, index) => (
+          <MenuItem className={classes.nested} dense inset key={index}>
+            <ListItemText classes={{ inset: classes.listItemTextInset }} inset primary={navItemGroupItem.title} />
+          </MenuItem>
+        ))}
+      </div>
+    );
+  }
+
+  renderCollapse() {
+    const { classes, navItem: { relatedTags } } = this.props;
+    return (
+      <Collapse in={this.isSubNavOpen} timeout="auto" unmountOnExit>
+        <MenuList component="div" disablePadding>
+          {relatedTags.map((navItemGroup, index) => (
+            <MenuList disablePadding key={index}>
+              <MenuItem inset className={classes.nested}>
+                <ListItemText classes={{ inset: classes.listItemTextInset }} inset primary={navItemGroup.title} />
+              </MenuItem>
+              {Array.isArray(navItemGroup.relatedTags) && this.renderSubNav(navItemGroup)}
+            </MenuList>
+          ))}
+        </MenuList>
+      </Collapse>
+    );
+  }
+
+  render() {
+    const { classes, navItem } = this.props;
+    return (
+      <Fragment>
+        <MenuItem color="inherit" onClick={this.onClick}>
+          <ListItemText classes={{ primary: classes.primary }} primary={navItem.name} />
+          {this.hasSubNavItems && (
+            <ListItemIcon className={classes.icon}>
+              {this.isSubNavOpen ? <ChevronUpIcon /> : <ChevronDownIcon />}
+            </ListItemIcon>
+          )}
+        </MenuItem>
+        {this.hasSubNavItems && this.renderCollapse()}
+      </Fragment>
+    );
+  }
+}
+
+export default MobileNavigationItem;
diff --git a/src/components/Navigation/MobileNavigationToggle.js b/src/components/Navigation/MobileNavigationToggle.js
new file mode 100644
index 00000000..fc2f4b3e
--- /dev/null
+++ b/src/components/Navigation/MobileNavigationToggle.js
@@ -0,0 +1,28 @@
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import { inject, observer } from "mobx-react";
+import IconButton from "material-ui/IconButton";
+import MenuIcon from "mdi-material-ui/Menu";
+
+@inject("uiStore")
+@observer
+class MobileNavigationToggle extends Component {
+  static propTypes = {
+    uiStore: PropTypes.object
+  };
+
+  static defaultProps = {
+    uiStore: {}
+  };
+
+  render() {
+    const { uiStore } = this.props;
+    return (
+      <IconButton color="inherit" onClick={uiStore.toggleMenuDrawerOpen}>
+        <MenuIcon />
+      </IconButton>
+    );
+  }
+}
+
+export default MobileNavigationToggle;
diff --git a/src/components/Navigation/index.js b/src/components/Navigation/index.js
new file mode 100644
index 00000000..62f6db86
--- /dev/null
+++ b/src/components/Navigation/index.js
@@ -0,0 +1,5 @@
+export { default as DesktopNavigation } from "./DesktopNavigation";
+export { default as DesktopNavigationItem } from "./DesktopNavigationItem";
+export { default as MobileNavigation } from "./MobileNavigation";
+export { default as MobileNavigationItem } from "./MobileNavigationItem";
+export { default as MobileNavigationToggle } from "./MobileNavigationToggle";
diff --git a/src/components/Profile/Profile.js b/src/components/Profile/Profile.js
index 9d16766e..1b189292 100644
--- a/src/components/Profile/Profile.js
+++ b/src/components/Profile/Profile.js
@@ -5,12 +5,14 @@ import Typography from "material-ui/Typography";
 export default class Profile extends Component {
   static propTypes = {
     viewer: PropTypes.object
-  }
+  };
+
+  static defaultProps = {
+    viewer: {}
+  };
 
   render() {
     const { viewer: { name } } = this.props;
-    return (
-      <Typography variant="subheading">Hello {name}</Typography>
-    );
+    return <Typography variant="subheading">Hello {name}</Typography>;
   }
 }
diff --git a/src/lib/stores/UIStore.js b/src/lib/stores/UIStore.js
index c10f155a..60655c37 100644
--- a/src/lib/stores/UIStore.js
+++ b/src/lib/stores/UIStore.js
@@ -2,8 +2,10 @@ import { observable, computed, action } from "mobx";
 
 class UIStore {
   @observable _cartOpen = false;
+  @observable _menuDrawerOpen = false;
 
-  @computed get cartOpen() {
+  @computed
+  get cartOpen() {
     return this._cartOpen;
   }
 
@@ -11,9 +13,26 @@ class UIStore {
     this._cartOpen = value;
   }
 
-  @action toggleCartOpen = () => {
+  @action
+  toggleCartOpen = () => {
+    console.log("ui store toggle cart drawer");
     this.cartOpen = !this.cartOpen;
+  };
+
+  @computed
+  get menuDrawerOpen() {
+    return this._menuDrawerOpen;
+  }
+
+  set menuDrawerOpen(value) {
+    this._menuDrawerOpen = value;
   }
+
+  @action
+  toggleMenuDrawerOpen = () => {
+    console.log("ui store toggle menu drawer");
+    this.menuDrawerOpen = !this.menuDrawerOpen;
+  };
 }
 
 export default UIStore;
diff --git a/src/server.js b/src/server.js
index 3b8f9154..c36ba482 100644
--- a/src/server.js
+++ b/src/server.js
@@ -22,6 +22,12 @@ app.prepare()
     //   app.render(req, res, actualPage, queryParams);
     // });
 
+    server.get("/tag/:slug", (req, res) => {
+      const actualPage = "/";
+      const queryParams = { slug: req.params.slug };
+      app.render(req, res, actualPage, queryParams);
+    });
+
     server.get("*", (req, res) => handle(req, res));
 
     /* END EXPRESS ROUTES */


Test patch:
diff --git a/src/components/Cart/Cart.test.js b/src/components/Cart/Cart.test.js
new file mode 100644
index 00000000..96aca0f9
--- /dev/null
+++ b/src/components/Cart/Cart.test.js
@@ -0,0 +1,10 @@
+import React from "react";
+import renderer from "react-test-renderer";
+import { shallow } from "enzyme";
+import Cart from "./Cart";
+
+test("basic snapshot", () => {
+  const component = renderer.create(shallow(<Cart />).get(3));
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
diff --git a/src/components/Cart/CartToggle.test.js b/src/components/Cart/CartToggle.test.js
new file mode 100644
index 00000000..6dbe8b6f
--- /dev/null
+++ b/src/components/Cart/CartToggle.test.js
@@ -0,0 +1,14 @@
+import React from "react";
+import renderer from "react-test-renderer";
+import { shallow } from "enzyme";
+import CartToggle from "./CartToggle";
+
+const uiStore = {
+  toggleCartOpen() {}
+};
+
+test("basic snapshot", () => {
+  const component = renderer.create(shallow(<CartToggle uiStore={uiStore} />).get(3));
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
diff --git a/src/components/Cart/__snapshots__/Cart.test.js.snap b/src/components/Cart/__snapshots__/Cart.test.js.snap
new file mode 100644
index 00000000..86bd4192
--- /dev/null
+++ b/src/components/Cart/__snapshots__/Cart.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`basic snapshot 1`] = `null`;
diff --git a/src/components/Cart/__snapshots__/CartToggle.test.js.snap b/src/components/Cart/__snapshots__/CartToggle.test.js.snap
new file mode 100644
index 00000000..86bd4192
--- /dev/null
+++ b/src/components/Cart/__snapshots__/CartToggle.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`basic snapshot 1`] = `null`;
diff --git a/src/components/Header/Header.test.js b/src/components/Header/Header.test.js
index a014cc00..60d22185 100644
--- a/src/components/Header/Header.test.js
+++ b/src/components/Header/Header.test.js
@@ -1,11 +1,10 @@
-
 import React from "react";
 import renderer from "react-test-renderer";
+import { shallow } from "enzyme";
 import Header from "./Header";
 
 test("basic snapshot", () => {
-  const component = renderer.create(<Header />);
-
+  const component = renderer.create(shallow(<Header />).get(3));
   const tree = component.toJSON();
   expect(tree).toMatchSnapshot();
 });
diff --git a/src/components/Header/__snapshots__/Header.test.js.snap b/src/components/Header/__snapshots__/Header.test.js.snap
index 2fd5b6f9..86bd4192 100644
--- a/src/components/Header/__snapshots__/Header.test.js.snap
+++ b/src/components/Header/__snapshots__/Header.test.js.snap
@@ -1,17 +1,3 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`basic snapshot 1`] = `
-<header
-  className="MuiPaper-root-9 MuiPaper-elevation4-15 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-7"
->
-  <div
-    className="MuiToolbar-root-36 MuiToolbar-gutters-37"
-  >
-    <h2
-      className="MuiTypography-root-38 MuiTypography-title-44"
-    >
-      Reaction Commerce
-    </h2>
-  </div>
-</header>
-`;
+exports[`basic snapshot 1`] = `null`;
diff --git a/src/components/Layout/Layout.test.js b/src/components/Layout/Layout.test.js
index bad907d7..3d1311f9 100644
--- a/src/components/Layout/Layout.test.js
+++ b/src/components/Layout/Layout.test.js
@@ -1,10 +1,10 @@
 import React from "react";
 import renderer from "react-test-renderer";
+import { shallow } from "enzyme";
 import Layout from "./";
 
 test("basic snapshot", () => {
-  const component = renderer.create(<Layout />);
-
+  const component = renderer.create(shallow(<Layout />).get(3));
   const tree = component.toJSON();
   expect(tree).toMatchSnapshot();
 });
diff --git a/src/components/Layout/__snapshots__/Layout.test.js.snap b/src/components/Layout/__snapshots__/Layout.test.js.snap
index 14506819..86bd4192 100644
--- a/src/components/Layout/__snapshots__/Layout.test.js.snap
+++ b/src/components/Layout/__snapshots__/Layout.test.js.snap
@@ -1,19 +1,3 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`basic snapshot 1`] = `
-<div>
-  <header
-    className="MuiPaper-root-9 MuiPaper-elevation4-15 MuiAppBar-root-1 MuiAppBar-positionStatic-5 MuiAppBar-colorPrimary-7"
-  >
-    <div
-      className="MuiToolbar-root-36 MuiToolbar-gutters-37"
-    >
-      <h2
-        className="MuiTypography-root-38 MuiTypography-title-44"
-      >
-        Reaction Commerce
-      </h2>
-    </div>
-  </header>
-</div>
-`;
+exports[`basic snapshot 1`] = `null`;
diff --git a/src/components/Navigation/DesktopNavigation.test.js b/src/components/Navigation/DesktopNavigation.test.js
new file mode 100644
index 00000000..59e2d557
--- /dev/null
+++ b/src/components/Navigation/DesktopNavigation.test.js
@@ -0,0 +1,9 @@
+import React from "react";
+import renderer from "react-test-renderer";
+import DesktopNavigation from "./DesktopNavigation";
+
+test("basic snapshot", () => {
+  const component = renderer.create(<DesktopNavigation />);
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
diff --git a/src/components/Navigation/DesktopNavigationItem.test.js b/src/components/Navigation/DesktopNavigationItem.test.js
new file mode 100644
index 00000000..098e00f6
--- /dev/null
+++ b/src/components/Navigation/DesktopNavigationItem.test.js
@@ -0,0 +1,32 @@
+import React from "react";
+import renderer from "react-test-renderer";
+import DesktopNavigationItem from "./DesktopNavigationItem";
+
+const testMenuItem = {
+  _id: "123",
+  name: "Shop",
+  slug: "shop"
+};
+
+const testMenuItemWithRelatedTags = {
+  _id: "123",
+  name: "Shop",
+  slug: "shop",
+  relatedTags: [
+    { _id: "111", name: "Clothes", slug: "clothes" },
+    { _id: "222", name: "Shoes", slug: "shoes" },
+    { _id: "333", name: "Accessories", slug: "accessories" }
+  ]
+};
+
+test("basic snapshot", () => {
+  const component = renderer.create(<DesktopNavigationItem navItems={testMenuItem} />);
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
+
+test("snapshot with related items", () => {
+  const component = renderer.create(<DesktopNavigationItem navItems={testMenuItemWithRelatedTags} />);
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
diff --git a/src/components/Navigation/MobileNavigation.test.js b/src/components/Navigation/MobileNavigation.test.js
new file mode 100644
index 00000000..ccea15e8
--- /dev/null
+++ b/src/components/Navigation/MobileNavigation.test.js
@@ -0,0 +1,10 @@
+import React from "react";
+import renderer from "react-test-renderer";
+import { shallow } from "enzyme";
+import MobileNavigation from "./MobileNavigation";
+
+test("basic snapshot", () => {
+  const component = renderer.create(shallow(<MobileNavigation />).get(3));
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
diff --git a/src/components/Navigation/MobileNavigationItem.test.js b/src/components/Navigation/MobileNavigationItem.test.js
new file mode 100644
index 00000000..baa7bed3
--- /dev/null
+++ b/src/components/Navigation/MobileNavigationItem.test.js
@@ -0,0 +1,32 @@
+import React from "react";
+import renderer from "react-test-renderer";
+import MobileNavigationItem from "./MobileNavigationItem";
+
+const testMenuItem = {
+  _id: "123",
+  name: "Shop",
+  slug: "shop"
+};
+
+const testMenuItemWithRelatedTags = {
+  _id: "123",
+  name: "Shop",
+  slug: "shop",
+  relatedTags: [
+    { _id: "111", name: "Clothes", slug: "clothes" },
+    { _id: "222", name: "Shoes", slug: "shoes" },
+    { _id: "333", name: "Accessories", slug: "accessories" }
+  ]
+};
+
+test("basic snapshot", () => {
+  const component = renderer.create(<MobileNavigationItem navItmes={testMenuItem} />);
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
+
+test("snapshot with related items", () => {
+  const component = renderer.create(<MobileNavigationItem navItmes={testMenuItemWithRelatedTags} />);
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
diff --git a/src/components/Navigation/MobileNavigationToggle.test.js b/src/components/Navigation/MobileNavigationToggle.test.js
new file mode 100644
index 00000000..9079e0ab
--- /dev/null
+++ b/src/components/Navigation/MobileNavigationToggle.test.js
@@ -0,0 +1,14 @@
+import React from "react";
+import renderer from "react-test-renderer";
+import { shallow } from "enzyme";
+import MobileNavigationToggle from "./MobileNavigationToggle";
+
+const uiStore = {
+  toggleMenuDrawerOpen() {}
+};
+
+test("basic snapshot", () => {
+  const component = renderer.create(shallow(<MobileNavigationToggle uiStore={uiStore} />).get(3));
+  const tree = component.toJSON();
+  expect(tree).toMatchSnapshot();
+});
diff --git a/src/components/Navigation/__snapshots__/DesktopNavigation.test.js.snap b/src/components/Navigation/__snapshots__/DesktopNavigation.test.js.snap
new file mode 100644
index 00000000..b71005ac
--- /dev/null
+++ b/src/components/Navigation/__snapshots__/DesktopNavigation.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`basic snapshot 1`] = `<nav />`;
diff --git a/src/components/Navigation/__snapshots__/DesktopNavigationItem.test.js.snap b/src/components/Navigation/__snapshots__/DesktopNavigationItem.test.js.snap
new file mode 100644
index 00000000..4125961d
--- /dev/null
+++ b/src/components/Navigation/__snapshots__/DesktopNavigationItem.test.js.snap
@@ -0,0 +1,55 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`basic snapshot 1`] = `
+<button
+  className="MuiButtonBase-root-18 MuiButton-root-3 MuiButton-colorInherit-7"
+  disabled={false}
+  onBlur={[Function]}
+  onClick={[Function]}
+  onFocus={[Function]}
+  onKeyDown={[Function]}
+  onKeyUp={[Function]}
+  onMouseDown={[Function]}
+  onMouseLeave={[Function]}
+  onMouseUp={[Function]}
+  onTouchEnd={[Function]}
+  onTouchMove={[Function]}
+  onTouchStart={[Function]}
+  tabIndex="0"
+  type="button"
+>
+  <span
+    className="MuiButton-label-4"
+  />
+  <span
+    className="MuiTouchRipple-root-21"
+  />
+</button>
+`;
+
+exports[`snapshot with related items 1`] = `
+<button
+  className="MuiButtonBase-root-18 MuiButton-root-3 MuiButton-colorInherit-7"
+  disabled={false}
+  onBlur={[Function]}
+  onClick={[Function]}
+  onFocus={[Function]}
+  onKeyDown={[Function]}
+  onKeyUp={[Function]}
+  onMouseDown={[Function]}
+  onMouseLeave={[Function]}
+  onMouseUp={[Function]}
+  onTouchEnd={[Function]}
+  onTouchMove={[Function]}
+  onTouchStart={[Function]}
+  tabIndex="0"
+  type="button"
+>
+  <span
+    className="MuiButton-label-4"
+  />
+  <span
+    className="MuiTouchRipple-root-21"
+  />
+</button>
+`;
diff --git a/src/components/Navigation/__snapshots__/MobileNavigation.test.js.snap b/src/components/Navigation/__snapshots__/MobileNavigation.test.js.snap
new file mode 100644
index 00000000..86bd4192
--- /dev/null
+++ b/src/components/Navigation/__snapshots__/MobileNavigation.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`basic snapshot 1`] = `null`;
diff --git a/src/components/Navigation/__snapshots__/MobileNavigationItem.test.js.snap b/src/components/Navigation/__snapshots__/MobileNavigationItem.test.js.snap
new file mode 100644
index 00000000..8c3ce784
--- /dev/null
+++ b/src/components/Navigation/__snapshots__/MobileNavigationItem.test.js.snap
@@ -0,0 +1,55 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`basic snapshot 1`] = `
+<li
+  className="MuiButtonBase-root-15 MuiListItem-root-5 MuiListItem-default-8 MuiListItem-gutters-12 MuiListItem-button-13 MuiMenuItem-root-3"
+  color="inherit"
+  onBlur={[Function]}
+  onClick={[Function]}
+  onFocus={[Function]}
+  onKeyDown={[Function]}
+  onKeyUp={[Function]}
+  onMouseDown={[Function]}
+  onMouseLeave={[Function]}
+  onMouseUp={[Function]}
+  onTouchEnd={[Function]}
+  onTouchMove={[Function]}
+  onTouchStart={[Function]}
+  role="menuitem"
+  tabIndex={-1}
+>
+  <div
+    className="MuiListItemText-root-18"
+  />
+  <span
+    className="MuiTouchRipple-root-24"
+  />
+</li>
+`;
+
+exports[`snapshot with related items 1`] = `
+<li
+  className="MuiButtonBase-root-15 MuiListItem-root-5 MuiListItem-default-8 MuiListItem-gutters-12 MuiListItem-button-13 MuiMenuItem-root-3"
+  color="inherit"
+  onBlur={[Function]}
+  onClick={[Function]}
+  onFocus={[Function]}
+  onKeyDown={[Function]}
+  onKeyUp={[Function]}
+  onMouseDown={[Function]}
+  onMouseLeave={[Function]}
+  onMouseUp={[Function]}
+  onTouchEnd={[Function]}
+  onTouchMove={[Function]}
+  onTouchStart={[Function]}
+  role="menuitem"
+  tabIndex={-1}
+>
+  <div
+    className="MuiListItemText-root-18"
+  />
+  <span
+    className="MuiTouchRipple-root-24"
+  />
+</li>
+`;
diff --git a/src/components/Navigation/__snapshots__/MobileNavigationToggle.test.js.snap b/src/components/Navigation/__snapshots__/MobileNavigationToggle.test.js.snap
new file mode 100644
index 00000000..86bd4192
--- /dev/null
+++ b/src/components/Navigation/__snapshots__/MobileNavigationToggle.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`basic snapshot 1`] = `null`;
diff --git a/src/components/Profile/Profile.test.js b/src/components/Profile/Profile.test.js
index a34c44f0..bb834e3c 100644
--- a/src/components/Profile/Profile.test.js
+++ b/src/components/Profile/Profile.test.js
@@ -1,16 +1,10 @@
 import React from "react";
 import renderer from "react-test-renderer";
-import { Profile } from "./Profile";
-
-const props = {
-  viewer: {
-    name: "Test User"
-  }
-};
+import { shallow } from "enzyme";
+import Profile from "./Profile";
 
 test("basic snapshot", () => {
-  const component = renderer.create(<Profile {...props} />);
-
+  const component = renderer.create(shallow(<Profile />).get(3));
   const tree = component.toJSON();
   expect(tree).toMatchSnapshot();
 });
diff --git a/src/components/Profile/__snapshots__/Profile.test.js.snap b/src/components/Profile/__snapshots__/Profile.test.js.snap
index e4929fef..86bd4192 100644
--- a/src/components/Profile/__snapshots__/Profile.test.js.snap
+++ b/src/components/Profile/__snapshots__/Profile.test.js.snap
@@ -1,10 +1,3 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
-exports[`basic snapshot 1`] = `
-<h3
-  className="MuiTypography-root-1 MuiTypography-subheading-8"
->
-  Hello 
-  Test User
-</h3>
-`;
+exports[`basic snapshot 1`] = `null`;
diff --git a/src/setupTests.js b/src/setupTests.js
new file mode 100644
index 00000000..ee60053c
--- /dev/null
+++ b/src/setupTests.js
@@ -0,0 +1,4 @@
+import { configure } from "enzyme";
+import Adapter from "enzyme-adapter-react-16";
+
+configure({ adapter: new Adapter() });
\ No newline at end of file