diff options
| author | Wes Bos <wesbos@gmail.com> | 2018-03-14 12:19:54 -0400 |
|---|---|---|
| committer | Wes Bos <wesbos@gmail.com> | 2018-03-14 12:19:54 -0400 |
| commit | 331da87bd53bc1fb79063d142764c75df9f32170 (patch) | |
| tree | d80aac98154a811fdf57b10e90ac0350fe14568e | |
| parent | 46a0ba30ef54a7e36206481a4f5b2bf1f9702fca (diff) | |
tests
26 files changed, 815 insertions, 118 deletions
diff --git a/frontend/.babelrc b/frontend/.babelrc deleted file mode 100644 index d9c3f91..0000000 --- a/frontend/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "presets": ["next/babel"], - "plugins": [ - [ - "styled-components", - { "ssr": true, "displayName": true, "preprocess": false } - ] - ] -} diff --git a/frontend/__tests__/CartCount.test.js b/frontend/__tests__/CartCount.test.js new file mode 100644 index 0000000..f3c4978 --- /dev/null +++ b/frontend/__tests__/CartCount.test.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import toJSON from 'enzyme-to-json'; +import { CartCount } from '../components/CartCount'; + +describe('<CartCount></CartCount>', () => { + it('renders okay', () => { + shallow(<CartCount count="10" />); + }); + + it('matches snapshot', () => { + const wrapper = shallow(<CartCount count="10" />); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); + it('updates via props', () => { + const wrapper = mount(<CartCount count="50" />); + expect(toJSON(wrapper)).toMatchSnapshot(); + wrapper.setProps({ count: 10 }); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); +}); diff --git a/frontend/__tests__/CreateItem.test.js b/frontend/__tests__/CreateItem.test.js new file mode 100644 index 0000000..0994d9c --- /dev/null +++ b/frontend/__tests__/CreateItem.test.js @@ -0,0 +1,85 @@ +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import toJSON from 'enzyme-to-json'; +import { CreateItem } from '../components/CreateItem'; + +// Wait func +const wait = amount => new Promise(resolve => setTimeout(resolve, amount)); + +const dogImage = 'https://dog.com/dog.jpg'; + +// Mock fetch to resolve data +global.fetch = jest.fn().mockResolvedValue({ + json: () => ({ + secure_url: dogImage, + eager: [{ secure_url: dogImage }], + }), +}); + +const fakeItem = { + title: 'Testing', + description: 'This is a really nice item', + image: dogImage, + largeImage: dogImage, + price: 50000, +}; + +describe('<Createitem/>', () => { + it('renders the form out', () => { + const createItemMutation = jest.fn(); + const wrapper = shallow(<CreateItem createItemMutation={createItemMutation} />); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); + + it('uploads a file when changed', async () => { + const createItemMutation = jest.fn(); + const wrapper = shallow(<CreateItem createItemMutation={createItemMutation} />); + + const input = wrapper.find('input[type="file"]'); + // internally this does some setState() calls + input.simulate('change', { currentTarget: { files: ['fakedog.jpg'] } }); + await wait(0); + expect(wrapper.state('image')).toEqual(dogImage); + expect(wrapper.state('largeImage')).toEqual(dogImage); + }); + + it('handles state updating', async () => { + const createItemMutation = jest.fn(); + const wrapper = shallow(<CreateItem createItemMutation={createItemMutation} />); + + wrapper.find('#title').simulate('change', { target: { value: 'Testing', name: 'title' } }); + wrapper.find('#price').simulate('change', { + target: { + value: 50000, + name: 'price', + type: 'number', + }, + }); + wrapper.find('#description').simulate('change', { + target: { + value: 'This is a really nice item', + name: 'description', + }, + }); + expect(wrapper.state().item).toMatchObject({ + title: 'Testing', + description: 'This is a really nice item', + image: '', + largeImage: '', + price: 50000, + }); + }); + + it('creates an item with all the inputs', () => { + const createItemMutation = jest.fn(() => Promise.resolve({})); + const wrapper = shallow(<CreateItem createItemMutation={createItemMutation} />); + wrapper.setState({ item: fakeItem }); + const form = wrapper.find('Form'); + // console.log(form.debug()); + form.simulate('submit', { + preventDefault() {}, + }); + expect(createItemMutation).toHaveBeenCalled(); + expect(createItemMutation).toHaveBeenCalledWith({ variables: fakeItem }); + }); +}); diff --git a/frontend/__tests__/Item.test.js b/frontend/__tests__/Item.test.js new file mode 100644 index 0000000..2990b1f --- /dev/null +++ b/frontend/__tests__/Item.test.js @@ -0,0 +1,31 @@ +/* eslint-env jest */ + +import React from 'react'; +import Enzyme, { shallow, mount } from 'enzyme'; +import toJSON from 'enzyme-to-json'; +import { ItemComponent } from '../components/Item'; + +const fakeItem = { + id: 'ABC123', + title: 'A Cool Item', + price: 5000, + description: 'This item is really cool!', + image: 'dog.jpg', + largeImage: 'largedog.jpg', +}; + +describe('<Item/>', () => { + it('Renders an item', () => { + const removeItem = jest.fn(); + const wrapper = shallow(<ItemComponent item={fakeItem} removeItem={removeItem} />); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); + it('handles button clicks', async () => { + const removeItem = jest.fn(); + global.confirm = jest.fn().mockReturnValue(true); + const wrapper = shallow(<ItemComponent item={fakeItem} removeItem={removeItem} />); + wrapper.find('button').simulate('click'); + expect(removeItem).toHaveBeenCalled(); + expect(global.confirm).toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/Nav.test.js b/frontend/__tests__/Nav.test.js new file mode 100644 index 0000000..49d16d6 --- /dev/null +++ b/frontend/__tests__/Nav.test.js @@ -0,0 +1,35 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import toJSON from 'enzyme-to-json'; +import { Nav } from '../components/Nav'; + +const currentUserLoggedOut = { + refetch() {}, +}; + +const currentUser = { + me: {}, + refetch() {}, +}; + +describe('<Nav></Nav>', () => { + it('renders', () => { + shallow(<Nav currentUser={currentUserLoggedOut} />); + }); + + it('Renders minimal nav when logged out', () => { + const wrapper = shallow(<Nav currentUser={currentUserLoggedOut} />); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); + + it('renders full nav when logged in', () => { + const wrapper = shallow(<Nav currentUser={currentUser} />); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); + + it('tries to refetch the current user when it mounts', () => { + const refetch = jest.fn(); + shallow(<Nav currentUser={{ ...currentUser, refetch }} />); + expect(refetch).toHaveBeenCalled(); + }); +}); diff --git a/frontend/__tests__/Pagination.test.js b/frontend/__tests__/Pagination.test.js new file mode 100644 index 0000000..e2ef7af --- /dev/null +++ b/frontend/__tests__/Pagination.test.js @@ -0,0 +1,46 @@ +import React from 'react'; +import { shallow, mount } from 'enzyme'; +import toJSON from 'enzyme-to-json'; +import { Pagination } from '../components/Pagination'; + +const fakeQuery = { + itemsConnection: { aggregate: { count: 18 } }, +}; + +describe('<Pagination/>', () => { + it('displays loading message', () => { + const wrapper = shallow(<Pagination loading page={1} itemsQuery={fakeQuery} />); + expect(toJSON(wrapper)).toMatchSnapshot(); + }); + + it('renders pagination for 18 items', () => { + const wrapper = shallow(<Pagination loading={false} page={1} itemsQuery={fakeQuery} />); + expect(toJSON(wrapper)).toMatchSnapshot(); + expect(wrapper.find('.totalPages').text()).toEqual('2'); + }); + + it('renders pagination for 28 items', () => { + const fakeQuery2 = { + itemsConnection: { aggregate: { count: 28 } }, + }; + const wrapper = shallow(<Pagination loading={false} page={1} itemsQuery={fakeQuery2} />); + expect(wrapper.find('.totalPages').text()).toEqual('4'); + }); + + it('disables and enables next/prev buttons', () => { + const fakeQuery3 = { + itemsConnection: { aggregate: { count: 100 } }, + }; + const wrapper = shallow(<Pagination loading={false} page={1} itemsQuery={fakeQuery} />); + + expect(wrapper.find('a.prev').props()['aria-disabled']).toEqual(true); + expect(wrapper.find('a.next').props()['aria-disabled']).toEqual(false); + wrapper.setProps({ page: 2 }); + expect(wrapper.find('a.prev').props()['aria-disabled']).toEqual(false); + expect(wrapper.find('a.next').props()['aria-disabled']).toEqual(true); + // when in the middle, both should work + wrapper.setProps({ itemsQuery: fakeQuery3, page: 3 }); + expect(wrapper.find('a.prev').props()['aria-disabled']).toEqual(false); + expect(wrapper.find('a.next').props()['aria-disabled']).toEqual(false); + }); +}); diff --git a/frontend/__tests__/__snapshots__/CartCount.test.js.snap b/frontend/__tests__/__snapshots__/CartCount.test.js.snap new file mode 100644 index 0000000..ddb6710 --- /dev/null +++ b/frontend/__tests__/__snapshots__/CartCount.test.js.snap @@ -0,0 +1,201 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<CartCount></CartCount> matches snapshot 1`] = ` +<styled.span> + <TransitionGroup + childFactory={[Function]} + component="div" + > + <CSSTransition + className="count" + classNames="count" + key="10" + timeout={ + Object { + "enter": 2000, + "exit": 2000, + } + } + > + <styled.div> + 10 + </styled.div> + </CSSTransition> + </TransitionGroup> +</styled.span> +`; + +exports[`<CartCount></CartCount> updates via props 1`] = ` +<CartCount + count="50" +> + <styled.span> + <span + className="sc-bwzfXH fWZfOZ" + > + <TransitionGroup + childFactory={[Function]} + component="div" + > + <div> + <CSSTransition + className="count" + classNames="count" + in={true} + key=".$50" + onExited={[Function]} + timeout={ + Object { + "enter": 2000, + "exit": 2000, + } + } + > + <Transition + appear={false} + className="count" + enter={true} + exit={true} + in={true} + mountOnEnter={false} + onEnter={[Function]} + onEntered={[Function]} + onEntering={[Function]} + onExit={[Function]} + onExited={[Function]} + onExiting={[Function]} + timeout={ + Object { + "enter": 2000, + "exit": 2000, + } + } + unmountOnExit={false} + > + <styled.div + className="count" + > + <div + className="count sc-bdVaJa cYFOmX" + > + 50 + </div> + </styled.div> + </Transition> + </CSSTransition> + </div> + </TransitionGroup> + </span> + </styled.span> +</CartCount> +`; + +exports[`<CartCount></CartCount> updates via props 2`] = ` +<CartCount + count={10} +> + <styled.span> + <span + className="sc-bwzfXH fWZfOZ" + > + <TransitionGroup + childFactory={[Function]} + component="div" + > + <div> + <CSSTransition + className="count" + classNames="count" + in={true} + key=".$10" + onExited={[Function]} + timeout={ + Object { + "enter": 2000, + "exit": 2000, + } + } + > + <Transition + appear={false} + className="count" + enter={true} + exit={true} + in={true} + mountOnEnter={false} + onEnter={[Function]} + onEntered={[Function]} + onEntering={[Function]} + onExit={[Function]} + onExited={[Function]} + onExiting={[Function]} + timeout={ + Object { + "enter": 2000, + "exit": 2000, + } + } + unmountOnExit={false} + > + <styled.div + className="count" + > + <div + className="count sc-bdVaJa cYFOmX" + > + 10 + </div> + </styled.div> + </Transition> + </CSSTransition> + <CSSTransition + className="count" + classNames="count" + in={false} + key=".$50" + onExited={[Function]} + timeout={ + Object { + "enter": 2000, + "exit": 2000, + } + } + > + <Transition + appear={false} + className="count" + enter={true} + exit={true} + in={false} + mountOnEnter={false} + onEnter={[Function]} + onEntered={[Function]} + onEntering={[Function]} + onExit={[Function]} + onExited={[Function]} + onExiting={[Function]} + timeout={ + Object { + "enter": 2000, + "exit": 2000, + } + } + unmountOnExit={false} + > + <styled.div + className="count" + > + <div + className="count sc-bdVaJa cYFOmX" + > + 50 + </div> + </styled.div> + </Transition> + </CSSTransition> + </div> + </TransitionGroup> + </span> + </styled.span> +</CartCount> +`; diff --git a/frontend/__tests__/__snapshots__/CreateItem.test.js.snap b/frontend/__tests__/__snapshots__/CreateItem.test.js.snap new file mode 100644 index 0000000..66b7965 --- /dev/null +++ b/frontend/__tests__/__snapshots__/CreateItem.test.js.snap @@ -0,0 +1,58 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Createitem/> renders the form out 1`] = ` +<div> + <Form + onSubmit={[Function]} + > + <DisplayError + error={ + Object { + "message": null, + } + } + onButtonClick={[Function]} + /> + <p> + Image + <input + accept=".png, .jpg, .jpeg" + onChange={[Function]} + type="file" + /> + </p> + <p> + Title + <input + id="title" + name="title" + onChange={[Function]} + placeholder="Title" + type="text" + /> + </p> + <label> + Price + <input + id="price" + min="0" + name="price" + onChange={[Function]} + type="number" + /> + </label> + <textarea + id="description" + name="description" + onChange={[Function]} + placeholder="The desc for this item" + /> + <button + disabled={false} + type="submit" + > + Submit + </button> + </Form> +</div> +`; diff --git a/frontend/__tests__/__snapshots__/Item.test.js.snap b/frontend/__tests__/__snapshots__/Item.test.js.snap new file mode 100644 index 0000000..f088121 --- /dev/null +++ b/frontend/__tests__/__snapshots__/Item.test.js.snap @@ -0,0 +1,60 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Item/> Renders an item 1`] = ` +<styled.div + key="ABC123" +> + <img + alt="A Cool Item" + src="dog.jpg" + /> + <styled.h3> + <Link + href={ + Object { + "pathname": "/item", + "query": Object { + "id": "ABC123", + }, + } + } + > + <a> + A Cool Item + </a> + </Link> + </styled.h3> + <styled.span> + $50 + </styled.span> + <p> + This item is really cool! + </p> + <div + className="buttonList" + > + <Link + href={ + Object { + "pathname": "/admin/update", + "query": Object { + "id": "ABC123", + }, + } + } + > + <a> + Edit ✏️ + </a> + </Link> + <Apollo(Apollo(AddToCart)) + id="ABC123" + /> + <button + onClick={[Function]} + > + × Delete item + </button> + </div> +</styled.div> +`; diff --git a/frontend/__tests__/__snapshots__/Nav.test.js.snap b/frontend/__tests__/__snapshots__/Nav.test.js.snap new file mode 100644 index 0000000..d8520ac --- /dev/null +++ b/frontend/__tests__/__snapshots__/Nav.test.js.snap @@ -0,0 +1,69 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Nav></Nav> Renders minimal nav when logged out 1`] = ` +<styled.ul> + <Link + href="/items" + prefetch={true} + > + <a> + Shop + </a> + </Link> + <Link + href="/add" + prefetch={true} + > + <a> + Sell + </a> + </Link> + <Link + href="/signup" + prefetch={true} + > + <a> + Sign In + </a> + </Link> +</styled.ul> +`; + +exports[`<Nav></Nav> renders full nav when logged in 1`] = ` +<styled.ul> + <Link + href="/items" + prefetch={true} + > + <a> + Shop + </a> + </Link> + <Link + href="/add" + prefetch={true} + > + <a> + Sell + </a> + </Link> + <React.Fragment> + <Link + href="/orders" + > + <a> + Orders + </a> + </Link> + <Link + href="/me" + > + <a> + My Account + </a> + </Link> + <Apollo(Signout) /> + <[object Object] /> + </React.Fragment> +</styled.ul> +`; diff --git a/frontend/__tests__/__snapshots__/Pagination.test.js.snap b/frontend/__tests__/__snapshots__/Pagination.test.js.snap new file mode 100644 index 0000000..f2abcdf --- /dev/null +++ b/frontend/__tests__/__snapshots__/Pagination.test.js.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Pagination/> displays loading message 1`] = ` +<p> + Loading... +</p> +`; + +exports[`<Pagination/> renders pagination for 18 items 1`] = ` +<styled.div> + <Link + href={ + Object { + "pathname": "items", + "query": Object { + "page": 0, + }, + } + } + prefetch={true} + > + <a + aria-disabled={true} + className="prev" + > + ←Prev + </a> + </Link> + <p> + Page + <strong> + 1 + + </strong> + of + <strong + className="totalPages" + > + 2 + </strong> + </p> + <p> + <strong> + 18 + </strong> + Items Total + </p> + <Link + href={ + Object { + "pathname": "items", + "query": Object { + "page": 2, + }, + } + } + prefetch={true} + > + <a + aria-disabled={false} + className="next" + > + Next → + </a> + </Link> +</styled.div> +`; diff --git a/frontend/__tests__/formatMoney.test.js b/frontend/__tests__/formatMoney.test.js new file mode 100644 index 0000000..c5c79b6 --- /dev/null +++ b/frontend/__tests__/formatMoney.test.js @@ -0,0 +1,21 @@ +import formatMoney from '../lib/formatMoney'; + +describe('formatMoney', () => { + it('works with fractional dollars', () => { + expect(formatMoney(1)).toEqual('$0.01'); + expect(formatMoney(10)).toEqual('$0.10'); + }); + + it('leaves cents off for whole dollars', () => { + expect(formatMoney(5000)).toEqual('$50'); + expect(formatMoney(100)).toEqual('$1'); + expect(formatMoney(50000000)).toEqual('$500,000'); + }); + + it('works with whole and fractional dollars', () => { + expect(formatMoney(5012)).toEqual('$50.12'); + expect(formatMoney(101)).toEqual('$1.01'); + expect(formatMoney(110)).toEqual('$1.10'); + expect(formatMoney(54345345)).toEqual('$543,453.45'); + }); +}); diff --git a/frontend/components/AddToCart.js b/frontend/components/AddToCart.js index b87d4e9..9c52f83 100644 --- a/frontend/components/AddToCart.js +++ b/frontend/components/AddToCart.js @@ -1,37 +1,8 @@ import { Component } from 'react'; import { graphql, compose } from 'react-apollo'; -import Transition from 'react-transition-group/Transition'; import styled from 'styled-components'; import PropTypes from 'prop-types'; -import { CURRENT_USER_QUERY, SINGLE_ITEM_QUERY } from '../queries'; -import { removeFromCartEnhancer, userEnhancer, addtoCartEnhancer } from '../enhancers/enhancers'; - -const JumpImg = styled.img` - border: 0 solid black; - max-width: 100px; - transition: all 0.5s; - position: fixed; - left: ${props => props.x}px; - top: -100%; - &.jump-entered { - border-color: green; - transform-origin: 0 0; - transform: scale(0); - top: ${props => props.y}px; - left: ${props => props.x}px; - } - &.jump-entering { - border-color: yellow; - top: ${props => props.y}px; - left: ${props => props.x}px; - } - &.jump-exited { - border-color: red; - } - &.jump-exiting { - border-color: yellow; - } -`; +import { removeFromCartEnhancer, userEnhancer, addToCartEnhancer, singleItemEnhancer } from '../enhancers/enhancers'; class AddToCart extends Component { static propTypes = { @@ -57,12 +28,4 @@ class AddToCart extends Component { } } -const singleItemEnhancer = graphql(SINGLE_ITEM_QUERY, { - name: 'singleItemQuery', - options: ({ id }) => ({ - variables: { - id, - }, - }), -}); -export default compose(userEnhancer, addtoCartEnhancer, removeFromCartEnhancer, singleItemEnhancer)(AddToCart); +export default compose(userEnhancer, addToCartEnhancer)(AddToCart); diff --git a/frontend/components/CartCount.js b/frontend/components/CartCount.js index 245b07b..7949307 100644 --- a/frontend/components/CartCount.js +++ b/frontend/components/CartCount.js @@ -50,3 +50,4 @@ const CartCount = ({ count }) => ( ); export default CartCount; +export { CartCount }; diff --git a/frontend/components/Count.js b/frontend/components/Count.js index 800a902..7dfd33f 100644 --- a/frontend/components/Count.js +++ b/frontend/components/Count.js @@ -1,3 +1,4 @@ +// TODO THis is not needed import React, { Component } from 'react' import { graphql, gql } from 'react-apollo' @@ -33,5 +34,4 @@ class Count extends Component { // We export the graphQL HOC - this will fetch the data and inject it into the Count compeont via props -export { ALL_ITEMS_QUERY } -export default graphql(ALL_ITEMS_QUERY, { name: 'allLinksQuery' }) (Count) +export default graphql(ALL_ITEMS_QUERY, { name: 'allLinksQuery' })(Count) diff --git a/frontend/components/CreateItem.js b/frontend/components/CreateItem.js index 26b2c88..74a6cee 100644 --- a/frontend/components/CreateItem.js +++ b/frontend/components/CreateItem.js @@ -1,33 +1,41 @@ import React, { Component } from 'react'; import { graphql, gql } from 'react-apollo'; -import { ALL_ITEMS_QUERY, CREATE_ITEM_MUTATION } from '../queries'; +import { CREATE_ITEM_MUTATION } from '../queries'; import ErrorMessage from './ErrorMessage'; -import { fileEndpoint } from '../config'; import Form from './styles/Form'; +import PropTypes from 'prop-types'; + +class CreateItem extends Component { + static propTypes = { + createItemMutation: PropTypes.func.isRequired, + }; -class CreateLink extends Component { state = { - description: 'test', - title: 'testing title', - image: '', - largeImage: '', - price: 500, - fullPrice: 0, + item: { + title: '', + description: '', + image: '', + largeImage: '', + price: 0, + }, loading: false, error: { - message: '', + message: null, }, }; - componentWillReceiveProps(nextProps) { - console.log(nextProps); - } + handleChange = e => { + const { name, value, type } = e.target; + const updatedItem = { + ...this.state.item, + [name]: type === 'number' ? parseFloat(value) : value, + }; + this.setState({ item: updatedItem }); + }; uploadFile = async e => { this.setState({ loading: true }); - const files = e.currentTarget.files; - const data = new FormData(); data.append('file', files[0]); data.append('upload_preset', 'sickfits'); @@ -38,33 +46,23 @@ class CreateLink extends Component { body: data, }); const file = await res.json(); - console.log(file); this.setState({ image: file.secure_url, largeImage: file.eager[0].secure_url, loading: false }); }; createItem = async e => { e.preventDefault(); - // pull the values from state - const { description, title, price, image, largeImage } = this.state; - // create a mutation // TODO: handle any errors // turn loading on this.setState({ loading: true }); try { - console.log('About to call create item mutation'); const res = await this.props.createItemMutation({ // pass in those variables from state variables: { - description, - title, - image, - largeImage, - price: parseInt(price), + ...this.state.item, }, }); } catch (error) { this.setState({ error }); - console.log(error); } this.setState({ loading: false }); }; @@ -84,23 +82,29 @@ class CreateLink extends Component { Title <input value={this.state.title} - onChange={e => this.setState({ title: e.target.value })} + onChange={this.handleChange} type="text" + name="title" + id="title" placeholder="Title" /> </p> <label> - Price<input + Price + <input type="number" + id="price" + name="price" min="0" value={this.state.price} - onChange={e => this.setState({ price: e.target.value })} + onChange={this.handleChange} /> </label> <textarea + id="description" + name="description" value={this.state.description} - onChange={e => this.setState({ description: e.target.value })} - type="text" + onChange={this.handleChange} placeholder="The desc for this item" /> <button disabled={this.state.loading} type="submit"> @@ -117,4 +121,6 @@ export default graphql(CREATE_ITEM_MUTATION, { options: { refetchQueries: ['AllItemsQuery'], }, -})(CreateLink); +})(CreateItem); + +export { CreateItem }; diff --git a/frontend/components/Item.js b/frontend/components/Item.js index 935930d..33b3951 100644 --- a/frontend/components/Item.js +++ b/frontend/components/Item.js @@ -1,10 +1,8 @@ import React from 'react'; -import Title from './styles/Title'; import styled from 'styled-components'; -import slugify from 'slugify'; import { compose } from 'react-apollo'; -// import { Link } from '../routes'; import Link from 'next/link'; +import Title from './styles/Title'; import AddToCart from './AddToCart'; import formatMoney from '../lib/formatMoney'; import { removeItemMutation } from '../enhancers/enhancers'; @@ -101,4 +99,5 @@ class ItemComponent extends React.Component { } } +export { ItemComponent }; export default compose(removeItemMutation)(ItemComponent); diff --git a/frontend/components/Items.js b/frontend/components/Items.js index 01d9786..1b0c8bb 100644 --- a/frontend/components/Items.js +++ b/frontend/components/Items.js @@ -18,7 +18,7 @@ const Center = styled.div` `; class ItemList extends React.Component { - something() { } + something() {} render() { const { loading, error } = this.props.itemsQuery; // 1 @@ -31,7 +31,6 @@ class ItemList extends React.Component { console.log(this.props.itemsQuery.error); return <div>Error</div>; } - console.log(this.props); // 3 const itemsToRender = this.props.itemsQuery.items; diff --git a/frontend/components/Nav.js b/frontend/components/Nav.js index aa0300b..3d7f0f6 100644 --- a/frontend/components/Nav.js +++ b/frontend/components/Nav.js @@ -1,8 +1,9 @@ +import React, { Fragment } from 'react'; import Link from 'next/link'; import styled from 'styled-components'; -import { Fragment } from 'react'; -import { userEnhancer } from '../enhancers/enhancers'; import { compose } from 'react-apollo'; +import PropTypes from 'prop-types'; +import { userEnhancer } from '../enhancers/enhancers'; import CartCount from './CartCount'; import Signout from './Signout'; import { UIContext } from './UIContext'; @@ -58,6 +59,9 @@ const StyledUl = styled.ul` `; class Nav extends React.Component { + static propTypes = { + currentUser: PropTypes.object.isRequired, + }; componentDidMount() { this.props.currentUser.refetch(); } @@ -106,3 +110,4 @@ class Nav extends React.Component { } export default compose(userEnhancer)(Nav); +export { Nav }; diff --git a/frontend/components/Pagination.js b/frontend/components/Pagination.js index dab7084..abc22d0 100644 --- a/frontend/components/Pagination.js +++ b/frontend/components/Pagination.js @@ -2,6 +2,7 @@ import React from 'react'; import { compose } from 'react-apollo'; import styled from 'styled-components'; import Link from 'next/link'; +import PropTypes from 'prop-types'; import { itemEnhancer } from '../enhancers/enhancers'; import { perPage } from '../config'; @@ -30,12 +31,11 @@ const PaginationStyles = styled.div` `; const Pagination = props => { - if (props.loading) return <p>Loading Item...</p>; - const { aggregate, pageInfo } = props.itemsQuery.itemsConnection; + if (props.loading) return <p>Loading...</p>; + const { aggregate } = props.itemsQuery.itemsConnection; const { page } = props; - const pages = Math.ceil(aggregate.count / perPage); - console.log({ pageInfo }); + return ( <PaginationStyles> <Link @@ -45,10 +45,12 @@ const Pagination = props => { query: { page: page - 1 }, }} > - <a aria-disabled={page <= 1}>←Prev</a> + <a className="prev" aria-disabled={page <= 1}> + ←Prev + </a> </Link> <p> - Page <strong>{page} </strong> of <strong>{pages} </strong> + Page <strong>{page} </strong> of <strong className="totalPages">{pages}</strong> </p> <p> <strong>{aggregate.count}</strong> Items Total @@ -60,12 +62,24 @@ const Pagination = props => { query: { page: page + 1 }, }} > - <a aria-disabled={page >= pages}>Next →</a> + <a className="next" aria-disabled={page >= pages}> + Next → + </a> </Link> </PaginationStyles> ); }; -const ComponentWithMutations = compose(itemEnhancer)(Pagination); +Pagination.propTypes = { + itemsQuery: PropTypes.shape({ + itemsConnection: PropTypes.shape({ + aggregate: PropTypes.shape({ + count: PropTypes.number.isRequired, + }), + }), + }).isRequired, + page: PropTypes.number.isRequired, +}; -export default ComponentWithMutations; +export default compose(itemEnhancer)(Pagination); +export { Pagination }; diff --git a/frontend/components/Search.js b/frontend/components/Search.js index 33017dd..d30789c 100644 --- a/frontend/components/Search.js +++ b/frontend/components/Search.js @@ -1,15 +1,15 @@ import Downshift from 'downshift'; import { graphql, compose } from 'react-apollo'; -import slugify from 'slugify'; import styled from 'styled-components'; import { SEARCH_ITEMS_QUERY } from '../queries'; import { Router } from '../routes'; function routeToItem(item) { - Router.pushRoute('item', { - slug: slugify(item.title), - itemId: item.id, - }); + console.log('TODO: UPdate routeToItem function'); + // Router.pushRoute('item', { + // slug: slugify(item.title), + // itemId: item.id, + // }); } const DropDown = styled.div` diff --git a/frontend/components/SingleItem.js b/frontend/components/SingleItem.js index add1707..f591f55 100644 --- a/frontend/components/SingleItem.js +++ b/frontend/components/SingleItem.js @@ -1,13 +1,11 @@ -import { graphql, compose } from 'react-apollo'; -import { SINGLE_ITEM_QUERY } from '../queries'; +import { compose } from 'react-apollo'; import { singleItemEnhancer } from '../enhancers/enhancers'; -const SingleItem = props => { - console.log(props); - if (props.loading) return <p>Loading...</p>; - if (props.error) return <p>Error...</p>; - const item = props.findItem.items[0]; - console.log(item); +const SingleItem = ({ findItem: { error, loading, items } }) => { + if (loading) return <p>Loading...</p>; + if (error) return <p>Error...</p>; + const item = items[0]; + return ( <div> <img src={item.largeImage || item.image} alt={item.title} /> @@ -17,6 +15,4 @@ const SingleItem = props => { ); }; -const ComponentWithMutations = compose(singleItemEnhancer)(SingleItem); - -export default ComponentWithMutations; +export default compose(singleItemEnhancer)(SingleItem); diff --git a/frontend/components/styles/Form.js b/frontend/components/styles/Form.js index db0eeb4..09085ad 100644 --- a/frontend/components/styles/Form.js +++ b/frontend/components/styles/Form.js @@ -37,4 +37,7 @@ const Form = styled.form` padding: 1rem 2rem; } `; + +Form.displayName = 'Form'; + export default Form; diff --git a/frontend/enhancers/enhancers.js b/frontend/enhancers/enhancers.js index b96a747..2651b6c 100644 --- a/frontend/enhancers/enhancers.js +++ b/frontend/enhancers/enhancers.js @@ -61,7 +61,7 @@ export const removeFromCartEnhancer = graphql(REMOVE_FROM_CART_MUTATION, { }, }); -export const addtoCartEnhancer = graphql(ADD_TO_CART_MUTATION, { +export const addToCartEnhancer = graphql(ADD_TO_CART_MUTATION, { name: 'addToCart', options: { update: (proxy, payload) => { diff --git a/frontend/lib/formatMoney.js b/frontend/lib/formatMoney.js index edddf48..301f01f 100644 --- a/frontend/lib/formatMoney.js +++ b/frontend/lib/formatMoney.js @@ -1,3 +1,11 @@ -export default function(amount) { - return '$' + (amount / 100).toLocaleString(); +export default function (amount) { + const options = { + style: 'currency', + currency: 'USD', + minimumFractionDigits: 2, + }; + // if its a whole, dollar amount, leave off the .00 + if (amount % 100 === 0) options.minimumFractionDigits = 0; + const formatter = new Intl.NumberFormat('en-US', options); + return formatter.format(amount / 100); } diff --git a/frontend/package.json b/frontend/package.json index 9de6f9a..f032a04 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -65,11 +65,29 @@ "development": { "presets": [ "next/babel" + ], + "plugins": [ + [ + "styled-components", + { + "ssr": true, + "displayName": true + } + ] ] }, "production": { "presets": [ "next/babel" + ], + "plugins": [ + [ + "styled-components", + { + "ssr": true, + "displayName": false + } + ] ] }, "test": { @@ -86,4 +104,4 @@ } } } -} +}
\ No newline at end of file |
