diff options
Diffstat (limited to 'stepped-solutions')
| -rwxr-xr-x | stepped-solutions/62/frontend/__tests__/Nav.test.js | 76 | ||||
| -rwxr-xr-x | stepped-solutions/62/frontend/__tests__/Pagination.test.js | 89 | ||||
| -rwxr-xr-x | stepped-solutions/62/frontend/__tests__/PleaseSignIn.test.js | 51 | ||||
| -rwxr-xr-x | stepped-solutions/62/frontend/__tests__/__snapshots__/Nav.test.js.snap | 37 | ||||
| -rwxr-xr-x | stepped-solutions/62/frontend/__tests__/__snapshots__/Pagination.test.js.snap | 67 | ||||
| -rwxr-xr-x | stepped-solutions/62/frontend/components/Nav.js | 49 | ||||
| -rwxr-xr-x | stepped-solutions/62/frontend/components/Pagination.js | 67 | ||||
| -rw-r--r-- | stepped-solutions/63/update.zip | bin | 0 -> 6085 bytes | |||
| -rwxr-xr-x | stepped-solutions/64/frontend/__tests__/Signup.test.js | 80 | ||||
| -rwxr-xr-x | stepped-solutions/64/frontend/__tests__/__snapshots__/Signup.test.js.snap | 62 | ||||
| -rwxr-xr-x | stepped-solutions/64/frontend/components/Signup.js | 87 | ||||
| -rwxr-xr-x | stepped-solutions/64/frontend/components/User.js | 41 |
12 files changed, 706 insertions, 0 deletions
diff --git a/stepped-solutions/62/frontend/__tests__/Nav.test.js b/stepped-solutions/62/frontend/__tests__/Nav.test.js new file mode 100755 index 0000000..d30cb39 --- /dev/null +++ b/stepped-solutions/62/frontend/__tests__/Nav.test.js @@ -0,0 +1,76 @@ +import { mount } from 'enzyme'; +import wait from 'waait'; +import toJSON from 'enzyme-to-json'; +import Nav from '../components/Nav'; +import { CURRENT_USER_QUERY } from '../components/User'; +import { MockedProvider } from 'react-apollo/test-utils'; +import { fakeUser, fakeCartItem } from '../lib/testUtils'; + +const notSignedInMocks = [ + { + request: { query: CURRENT_USER_QUERY }, + result: { data: { me: null } }, + }, +]; + +const signedInMocks = [ + { + request: { query: CURRENT_USER_QUERY }, + result: { data: { me: fakeUser() } }, + }, +]; + +const signedInMocksWithCartItems = [ + { + request: { query: CURRENT_USER_QUERY }, + result: { + data: { + me: { + ...fakeUser(), + cart: [fakeCartItem(), fakeCartItem(), fakeCartItem()], + }, + }, + }, + }, +]; + +describe('<Nav/>', () => { + it('renders a minimal nav when signed out', async () => { + const wrapper = mount( + <MockedProvider mocks={notSignedInMocks}> + <Nav /> + </MockedProvider> + ); + await wait(); + wrapper.update(); + // console.log(wrapper.debug()); + const nav = wrapper.find('ul[data-test="nav"]'); + expect(toJSON(nav)).toMatchSnapshot(); + }); + + it('renders full nav when signed in', async () => { + const wrapper = mount( + <MockedProvider mocks={signedInMocks}> + <Nav /> + </MockedProvider> + ); + await wait(); + wrapper.update(); + const nav = wrapper.find('ul[data-test="nav"]'); + expect(nav.children().length).toBe(6); + expect(nav.text()).toContain('Sign Out'); + }); + + it('renders the amount of items in the cart', async () => { + const wrapper = mount( + <MockedProvider mocks={signedInMocksWithCartItems}> + <Nav /> + </MockedProvider> + ); + await wait(); + wrapper.update(); + const nav = wrapper.find('[data-test="nav"]'); + const count = nav.find('div.count'); + expect(toJSON(count)).toMatchSnapshot(); + }); +}); diff --git a/stepped-solutions/62/frontend/__tests__/Pagination.test.js b/stepped-solutions/62/frontend/__tests__/Pagination.test.js new file mode 100755 index 0000000..7117db8 --- /dev/null +++ b/stepped-solutions/62/frontend/__tests__/Pagination.test.js @@ -0,0 +1,89 @@ +import { mount } from 'enzyme'; +import wait from 'waait'; +import toJSON from 'enzyme-to-json'; +import Router from 'next/router'; +import Pagination, { PAGINATION_QUERY } from '../components/Pagination'; +import { MockedProvider } from 'react-apollo/test-utils'; + +Router.router = { + push() {}, + prefetch() {}, +}; + +function makeMocksFor(length) { + return [ + { + request: { query: PAGINATION_QUERY }, + result: { + data: { + itemsConnection: { + __typename: 'aggregate', + aggregate: { + count: length, + __typename: 'count', + }, + }, + }, + }, + }, + ]; +} + +describe('<Pagination/>', () => { + it('displays a loading message', () => { + const wrapper = mount( + <MockedProvider mocks={makeMocksFor(1)}> + <Pagination page={1} /> + </MockedProvider> + ); + const pagination = wrapper.find('[data-test="pagination"]'); + expect(wrapper.text()).toContain('Loading...'); + }); + + it('renders pagination for 18 items', async () => { + const wrapper = mount( + <MockedProvider mocks={makeMocksFor(18)}> + <Pagination page={1} /> + </MockedProvider> + ); + await wait(); + wrapper.update(); + expect(wrapper.find('.totalPages').text()).toEqual('5'); + const pagination = wrapper.find('div[data-test="pagination"]'); + expect(toJSON(pagination)).toMatchSnapshot(); + }); + + it('disables prev button on first page', async () => { + const wrapper = mount( + <MockedProvider mocks={makeMocksFor(18)}> + <Pagination page={1} /> + </MockedProvider> + ); + await wait(); + wrapper.update(); + expect(wrapper.find('a.prev').prop('aria-disabled')).toEqual(true); + expect(wrapper.find('a.next').prop('aria-disabled')).toEqual(false); + }); + it('disables next button on last page', async () => { + const wrapper = mount( + <MockedProvider mocks={makeMocksFor(18)}> + <Pagination page={5} /> + </MockedProvider> + ); + await wait(); + wrapper.update(); + expect(wrapper.find('a.prev').prop('aria-disabled')).toEqual(false); + expect(wrapper.find('a.next').prop('aria-disabled')).toEqual(true); + }); + it('enables all buttons on a middle page', async () => { + const wrapper = mount( + <MockedProvider mocks={makeMocksFor(18)}> + <Pagination page={3} /> + </MockedProvider> + ); + await wait(); + wrapper.update(); + expect(wrapper.find('a.prev').prop('aria-disabled')).toEqual(false); + expect(wrapper.find('a.next').prop('aria-disabled')).toEqual(false); + }); +}); diff --git a/stepped-solutions/62/frontend/__tests__/PleaseSignIn.test.js b/stepped-solutions/62/frontend/__tests__/PleaseSignIn.test.js new file mode 100755 index 0000000..7d476b4 --- /dev/null +++ b/stepped-solutions/62/frontend/__tests__/PleaseSignIn.test.js @@ -0,0 +1,51 @@ +import { mount } from 'enzyme'; +import wait from 'waait'; +import PleaseSignIn from '../components/PleaseSignIn'; +import { CURRENT_USER_QUERY } from '../components/User'; +import { MockedProvider } from 'react-apollo/test-utils'; +import { fakeUser } from '../lib/testUtils'; + +const notSignedInMocks = [ + { + request: { query: CURRENT_USER_QUERY }, + result: { data: { me: null } }, + }, +]; + +const signedInMocks = [ + { + request: { query: CURRENT_USER_QUERY }, + result: { data: { me: fakeUser() } }, + }, +]; + +describe('<PleaseSignIn/>', () => { + it('renders the sign in dialog to logged out users', async () => { + const wrapper = mount( + <MockedProvider mocks={notSignedInMocks}> + <PleaseSignIn /> + </MockedProvider> + ); + await wait(); + wrapper.update(); + expect(wrapper.text()).toContain('Please Sign In before Continuing'); + const SignIn = wrapper.find('Signin'); + expect(SignIn.exists()).toBe(true); + }); + + it('renders the child component when the user is signed in', async () => { + const Hey = () => <p>Hey!</p>; + const wrapper = mount( + <MockedProvider mocks={signedInMocks}> + <PleaseSignIn> + <Hey /> + </PleaseSignIn> + </MockedProvider> + ); + + await wait(); + wrapper.update(); + // expect(wrapper.find('Hey').exists()).toBe(true); + expect(wrapper.contains(<Hey />)).toBe(true); + }); +}); diff --git a/stepped-solutions/62/frontend/__tests__/__snapshots__/Nav.test.js.snap b/stepped-solutions/62/frontend/__tests__/__snapshots__/Nav.test.js.snap new file mode 100755 index 0000000..9a5a14c --- /dev/null +++ b/stepped-solutions/62/frontend/__tests__/__snapshots__/Nav.test.js.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Nav/> renders a minimal nav when signed out 1`] = ` +<ul + className="NavStyles-s11c0d2g-0 ddqXnG" + data-test="nav" +> + <Link + href="/items" + > + <a + href="/items" + onClick={[Function]} + > + Shop + </a> + </Link> + <Link + href="/signup" + > + <a + href="/signup" + onClick={[Function]} + > + Sign In + </a> + </Link> +</ul> +`; + +exports[`<Nav/> renders the amount of items in the cart 1`] = ` +<div + className="count CartCount__Dot-xxvp4g-1 fJsVOg" +> + 9 +</div> +`; diff --git a/stepped-solutions/62/frontend/__tests__/__snapshots__/Pagination.test.js.snap b/stepped-solutions/62/frontend/__tests__/__snapshots__/Pagination.test.js.snap new file mode 100755 index 0000000..110f59d --- /dev/null +++ b/stepped-solutions/62/frontend/__tests__/__snapshots__/Pagination.test.js.snap @@ -0,0 +1,67 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Pagination/> renders pagination for 18 items 1`] = ` +<div + className="PaginationStyles-aduuar-0 ewJsCc" + data-test="pagination" +> + <SideEffect(Head)> + <Head /> + </SideEffect(Head)> + <Link + href={ + Object { + "pathname": "items", + "query": Object { + "page": 0, + }, + } + } + prefetch={true} + > + <a + aria-disabled={true} + className="prev" + href="items?page=0" + onClick={[Function]} + > + ← Prev + </a> + </Link> + <p> + Page + 1 + of + <span + className="totalPages" + > + 5 + </span> + ! + </p> + <p> + 18 + Items Total + </p> + <Link + href={ + Object { + "pathname": "items", + "query": Object { + "page": 2, + }, + } + } + prefetch={true} + > + <a + aria-disabled={false} + className="next" + href="items?page=2" + onClick={[Function]} + > + Next → + </a> + </Link> +</div> +`; diff --git a/stepped-solutions/62/frontend/components/Nav.js b/stepped-solutions/62/frontend/components/Nav.js new file mode 100755 index 0000000..118db5a --- /dev/null +++ b/stepped-solutions/62/frontend/components/Nav.js @@ -0,0 +1,49 @@ +import Link from 'next/link'; +import { Mutation } from 'react-apollo'; +import { TOGGLE_CART_MUTATION } from './Cart'; +import NavStyles from './styles/NavStyles'; +import User from './User'; +import CartCount from './CartCount'; +import Signout from './Signout'; + +const Nav = () => ( + <User> + {({ data: { me } }) => ( + <NavStyles data-test="nav"> + <Link href="/items"> + <a>Shop</a> + </Link> + {me && ( + <> + <Link href="/sell"> + <a>Sell</a> + </Link> + <Link href="/orders"> + <a>Orders</a> + </Link> + <Link href="/me"> + <a>Account</a> + </Link> + <Signout /> + <Mutation mutation={TOGGLE_CART_MUTATION}> + {(toggleCart) => ( + <button onClick={toggleCart}> + My Cart + <CartCount count={me.cart.reduce((tally, cartItem) => tally + cartItem.quantity, 0)}></CartCount> + </button> + )} + </Mutation> + </> + )} + {!me && ( + <Link href="/signup"> + <a>Sign In</a> + </Link> + + )} + </NavStyles> + )} + </User> +); + +export default Nav; diff --git a/stepped-solutions/62/frontend/components/Pagination.js b/stepped-solutions/62/frontend/components/Pagination.js new file mode 100755 index 0000000..b84af76 --- /dev/null +++ b/stepped-solutions/62/frontend/components/Pagination.js @@ -0,0 +1,67 @@ +import React from 'react'; +import gql from 'graphql-tag'; +import { Query } from 'react-apollo'; +import Head from 'next/head'; +import Link from 'next/link'; +import PaginationStyles from './styles/PaginationStyles'; +import { perPage } from '../config'; + +const PAGINATION_QUERY = gql` + query PAGINATION_QUERY { + itemsConnection { + aggregate { + count + } + } + } +`; + +const Pagination = props => ( + <Query query={PAGINATION_QUERY}> + {({ data, loading, error }) => { + if (loading) return <p>Loading...</p>; + const count = data.itemsConnection.aggregate.count; + const pages = Math.ceil(count / perPage); + const page = props.page; + return ( + <PaginationStyles data-test="pagination"> + <Head> + <title> + Sick Fits! — Page {page} of {pages} + </title> + </Head> + <Link + prefetch + href={{ + pathname: 'items', + query: { page: page - 1 }, + }} + > + <a className="prev" aria-disabled={page <= 1}> + ← Prev + </a> + </Link> + <p> + Page {props.page} of + <span className="totalPages">{pages}</span>! + </p> + <p>{count} Items Total</p> + <Link + prefetch + href={{ + pathname: 'items', + query: { page: page + 1 }, + }} + > + <a className="next" aria-disabled={page >= pages}> + Next → + </a> + </Link> + </PaginationStyles> + ); + }} + </Query> +); + +export default Pagination; +export { PAGINATION_QUERY }; diff --git a/stepped-solutions/63/update.zip b/stepped-solutions/63/update.zip Binary files differnew file mode 100644 index 0000000..a07440c --- /dev/null +++ b/stepped-solutions/63/update.zip diff --git a/stepped-solutions/64/frontend/__tests__/Signup.test.js b/stepped-solutions/64/frontend/__tests__/Signup.test.js new file mode 100755 index 0000000..e18bdee --- /dev/null +++ b/stepped-solutions/64/frontend/__tests__/Signup.test.js @@ -0,0 +1,80 @@ +import { mount } from 'enzyme'; +import wait from 'waait'; +import toJSON from 'enzyme-to-json'; +import { MockedProvider } from 'react-apollo/test-utils'; +import { ApolloConsumer } from 'react-apollo'; +import Signup, { SIGNUP_MUTATION } from '../components/Signup'; +import { CURRENT_USER_QUERY } from '../components/User'; +import { fakeUser } from '../lib/testUtils'; + +function type(wrapper, name, value) { + wrapper.find(`input[name="${name}"]`).simulate('change', { + target: { name, value }, + }); +} + +const me = fakeUser(); +const mocks = [ + // signup mock mutation + { + request: { + query: SIGNUP_MUTATION, + variables: { + name: me.name, + email: me.email, + password: 'wes', + }, + }, + result: { + data: { + signup: { + __typename: 'User', + id: 'abc123', + email: me.email, + name: me.name, + }, + }, + }, + }, + // current user query mock + { + request: { query: CURRENT_USER_QUERY }, + result: { data: { me } }, + }, +]; + +describe('<Signup/>', () => { + it('renders and matches snapshot', async () => { + const wrapper = mount( + <MockedProvider> + <Signup /> + </MockedProvider> + ); + expect(toJSON(wrapper.find('form'))).toMatchSnapshot(); + }); + + it('calls the mutation properly', async () => { + let apolloClient; + const wrapper = mount( + <MockedProvider mocks={mocks}> + <ApolloConsumer> + {client => { + apolloClient = client; + return <Signup />; + }} + </ApolloConsumer> + </MockedProvider> + ); + await wait(); + wrapper.update(); + type(wrapper, 'name', me.name); + type(wrapper, 'email', me.email); + type(wrapper, 'password', 'wes'); + wrapper.update(); + wrapper.find('form').simulate('submit'); + await wait(); + // query the user out of the apollo client + const user = await apolloClient.query({ query: CURRENT_USER_QUERY }); + expect(user.data.me).toMatchObject(me); + }); +}); diff --git a/stepped-solutions/64/frontend/__tests__/__snapshots__/Signup.test.js.snap b/stepped-solutions/64/frontend/__tests__/__snapshots__/Signup.test.js.snap new file mode 100755 index 0000000..14c5912 --- /dev/null +++ b/stepped-solutions/64/frontend/__tests__/__snapshots__/Signup.test.js.snap @@ -0,0 +1,62 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`<Signup/> renders and matches snapshot 1`] = ` +<form + className="Form-s1xszr8q-0 dIYPmt" + method="post" + onSubmit={[Function]} +> + <fieldset + aria-busy={false} + disabled={false} + > + <h2> + Sign Up for An Account + </h2> + <DisplayError + error={Object {}} + /> + <label + htmlFor="email" + > + Email + <input + name="email" + onChange={[Function]} + placeholder="email" + type="email" + value="" + /> + </label> + <label + htmlFor="name" + > + Name + <input + name="name" + onChange={[Function]} + placeholder="name" + type="text" + value="" + /> + </label> + <label + htmlFor="password" + > + Password + <input + name="password" + onChange={[Function]} + placeholder="password" + type="password" + value="" + /> + </label> + <button + type="submit" + > + Sign Up! + </button> + </fieldset> +</form> +`; diff --git a/stepped-solutions/64/frontend/components/Signup.js b/stepped-solutions/64/frontend/components/Signup.js new file mode 100755 index 0000000..e2f1a3f --- /dev/null +++ b/stepped-solutions/64/frontend/components/Signup.js @@ -0,0 +1,87 @@ +import React, { Component } from 'react'; +import { Mutation } from 'react-apollo'; +import gql from 'graphql-tag'; +import Form from './styles/Form'; +import Error from './ErrorMessage'; +import { CURRENT_USER_QUERY } from './User'; + +const SIGNUP_MUTATION = gql` + mutation SIGNUP_MUTATION($email: String!, $name: String!, $password: String!) { + signup(email: $email, name: $name, password: $password) { + id + email + name + } + } +`; + +class Signup extends Component { + state = { + name: '', + email: '', + password: '', + }; + saveToState = e => { + this.setState({ [e.target.name]: e.target.value }); + }; + render() { + return ( + <Mutation + mutation={SIGNUP_MUTATION} + variables={this.state} + refetchQueries={[{ query: CURRENT_USER_QUERY }]} + > + {(signup, { error, loading }) => ( + <Form + method="post" + onSubmit={async e => { + e.preventDefault(); + await signup(); + this.setState({ name: '', email: '', password: '' }); + }} + > + <fieldset disabled={loading} aria-busy={loading}> + <h2>Sign Up for An Account</h2> + <Error error={error} /> + <label htmlFor="email"> + Email + <input + type="email" + name="email" + placeholder="email" + value={this.state.email} + onChange={this.saveToState} + /> + </label> + <label htmlFor="name"> + Name + <input + type="text" + name="name" + placeholder="name" + value={this.state.name} + onChange={this.saveToState} + /> + </label> + <label htmlFor="password"> + Password + <input + type="password" + name="password" + placeholder="password" + value={this.state.password} + onChange={this.saveToState} + /> + </label> + + <button type="submit">Sign Up!</button> + </fieldset> + </Form> + )} + </Mutation> + ); + } +} + +export default Signup; +export { SIGNUP_MUTATION }; diff --git a/stepped-solutions/64/frontend/components/User.js b/stepped-solutions/64/frontend/components/User.js new file mode 100755 index 0000000..6f8a4fb --- /dev/null +++ b/stepped-solutions/64/frontend/components/User.js @@ -0,0 +1,41 @@ +import { Query } from 'react-apollo'; +import gql from 'graphql-tag'; +import PropTypes from 'prop-types'; + +const CURRENT_USER_QUERY = gql` + query { + me { + id + email + name + permissions + orders { + id + } + cart { + id + quantity + item { + id + price + image + title + description + } + } + } + } +`; + +const User = props => ( + <Query {...props} query={CURRENT_USER_QUERY}> + {payload => props.children(payload)} + </Query> +); + +User.propTypes = { + children: PropTypes.func.isRequired, +}; + +export default User; +export { CURRENT_USER_QUERY }; |
