Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
semiautomatix committed Jan 31, 2021
1 parent 7956114 commit b5d4192
Show file tree
Hide file tree
Showing 12 changed files with 789 additions and 68 deletions.
1 change: 1 addition & 0 deletions .eslintcache
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[{"E:\\timga\\Git\\Easier\\takehome\\src\\pages\\Character.tsx":"1","E:\\timga\\Git\\Easier\\takehome\\src\\graphql\\client.ts":"2","E:\\timga\\Git\\Easier\\takehome\\src\\App.tsx":"3","E:\\timga\\Git\\Easier\\takehome\\src\\pages\\Location.tsx":"4","E:\\timga\\Git\\Easier\\takehome\\src\\graphql\\queries.ts":"5","E:\\timga\\Git\\Easier\\takehome\\src\\index.tsx":"6","E:\\timga\\Git\\Easier\\takehome\\src\\components\\ResidentCard.tsx":"7"},{"size":2677,"mtime":1612069681061,"results":"8","hashOfConfig":"9"},{"size":285,"mtime":1612068880577,"results":"10","hashOfConfig":"9"},{"size":1036,"mtime":1612070724126,"results":"11","hashOfConfig":"9"},{"size":2638,"mtime":1612074942650,"results":"12","hashOfConfig":"9"},{"size":788,"mtime":1612056516053,"results":"13","hashOfConfig":"9"},{"size":500,"mtime":1611974251949,"results":"14","hashOfConfig":"9"},{"size":654,"mtime":1612064790655,"results":"15","hashOfConfig":"9"},{"filePath":"16","messages":"17","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1avk79",{"filePath":"18","messages":"19","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"20","messages":"21","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"22","messages":"23","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"24","messages":"25","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"26","messages":"27","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"28"},{"filePath":"29","messages":"30","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"E:\\timga\\Git\\Easier\\takehome\\src\\pages\\Character.tsx",[],"E:\\timga\\Git\\Easier\\takehome\\src\\graphql\\client.ts",[],"E:\\timga\\Git\\Easier\\takehome\\src\\App.tsx",[],"E:\\timga\\Git\\Easier\\takehome\\src\\pages\\Location.tsx",[],"E:\\timga\\Git\\Easier\\takehome\\src\\graphql\\queries.ts",[],"E:\\timga\\Git\\Easier\\takehome\\src\\index.tsx",[],["31","32"],"E:\\timga\\Git\\Easier\\takehome\\src\\components\\ResidentCard.tsx",[],{"ruleId":"33","replacedBy":"34"},{"ruleId":"35","replacedBy":"36"},"no-native-reassign",["37"],"no-negated-in-lhs",["38"],"no-global-assign","no-unsafe-negation"]
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
# Working example

# Additional Notes
- Based on https://rickandmortyapi.com/
- In the interest of time, tests have been kept to a minimum and are more of an example than comprehensive
- Also for time Static Type is somewhat broad, though not just `any`!

# Getting Started with Create React App

This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
Expand Down
16 changes: 16 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,23 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@apollo/client": "^3.3.7",
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/react": "^16.9.53",
"@types/react-dom": "^16.9.8",
"graphql": "^15.5.0",
"grommet": "^2.16.2",
"grommet-icons": "^4.5.0",
"polished": "^4.1.0",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "4.0.1",
"styled-components": "^5.2.1",
"typescript": "^4.0.3",
"web-vitals": "^0.2.4"
},
Expand All @@ -23,6 +30,11 @@
"eject": "react-scripts eject"
},
"eslintConfig": {
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
},
"extends": [
"react-app",
"react-app/jest"
Expand All @@ -39,5 +51,9 @@
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/react-router-dom": "^5.1.7",
"@types/react-transition-group": "^4.4.0"
}
}
38 changes: 0 additions & 38 deletions src/App.css

This file was deleted.

68 changes: 63 additions & 5 deletions src/App.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,67 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import { MockedProvider } from '@apollo/client/testing';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App';
import Location from './pages/Location';
import { GET_LOCATION_COUNT, GET_LOCATION } from './graphql/queries';

test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
const mocks = [
{
request: {
query: GET_LOCATION_COUNT,
},
result: {
data: {
locations: {
info: {
count: 1
}
}
},
},
},
{
request: {
query: GET_LOCATION,
variables: {
id: 1,
},
},
result: {
data: {
location: {
id: '1',
name: 'Earth (C-137)',
type: 'Planet',
dimension: 'Dimension C-137',
residents: [
{
id: '38',
name: 'Beth Smith',
image: 'https://rickandmortyapi.com/api/character/avatar/38.jpeg'
},
],
},
},
},
},
];

test('renders without error', async () => {
render(<MockedProvider mocks={mocks} addTypename={false}>
<App />
</MockedProvider>);
expect(screen.getByText('Loading ...')).toBeInTheDocument();
});

test('should render location and residents', async () => {
render(<MockedProvider mocks={mocks} addTypename={false}>
<Router>
<Location />
</Router>
</MockedProvider>);
await waitFor(() => {
expect(screen.getByText('Beth Smith')).toBeInTheDocument();
});
});
54 changes: 36 additions & 18 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
import React from 'react';
import logo from './logo.svg';
import './App.css';
import { ApolloProvider } from '@apollo/client';
import {
BrowserRouter as Router,
Switch,
Route,
Redirect
} from 'react-router-dom';
import { Grommet } from 'grommet';

// graphql client
import client from './graphql/client';

// pages
import Character from './pages/Character';
import Location from './pages/Location';

const routes = [
{ path: '/', name: 'Location', Component: Location },
{ path: '/character/:id', name: 'Character', Component: Character }
]

function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
<ApolloProvider client={client}>
<Router>
<Grommet plain>
<div className="App">
<Switch>
{routes.map(({ path, Component }) => (
<Route key={path} exact path={path}>
<Component />
</Route>
))}
<Route component={() => <Redirect from='*' to='/' />} />
</Switch>
</div>
</Grommet>
</Router>
</ApolloProvider>
);
}

Expand Down
19 changes: 19 additions & 0 deletions src/components/ResidentCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Card, CardFooter, CardBody, Image, Text } from 'grommet';
import React, { FunctionComponent } from 'react';
import { Link } from 'react-router-dom';

const ResidentCard: FunctionComponent<{ id: number, name: string, imageUri: string }> = ({ id, name, imageUri }) => {
return <Link to={`/character/${id}`}>
<Card height='small' width='small' background='light-1'>
<CardBody pad='none'><Image
fit='cover'
src={imageUri}
/></CardBody>
<CardFooter pad='medium' align='center' margin='auto'>
<Text>{name}</Text>
</CardFooter>
</Card>
</Link>
}

export default ResidentCard
10 changes: 10 additions & 0 deletions src/graphql/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { ApolloClient, InMemoryCache, makeVar } from '@apollo/client';

const client = new ApolloClient({
uri: 'https://rickandmortyapi.com/graphql',
cache: new InMemoryCache(),
});

export default client

export const locationIdVar = makeVar<number|undefined>(undefined);
57 changes: 57 additions & 0 deletions src/graphql/queries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { gql } from "@apollo/client";

const GET_CHARACTER = gql`
query GetCharacter ($id: ID!) {
character (id: $id) {
id
name
status
species
type
gender
origin {
name
dimension
id
}
location {
name
dimension
id
}
image
}
}
`

const GET_LOCATION = gql`
query GetLocation ($id: ID!) {
location (id: $id) {
id
name
type
dimension
residents {
id
name
image
}
}
}
`

const GET_LOCATION_COUNT = gql`
query {
locations {
info {
count
}
}
}
`

export {
GET_LOCATION,
GET_CHARACTER,
GET_LOCATION_COUNT
}
66 changes: 66 additions & 0 deletions src/pages/Character.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useQuery } from '@apollo/client';
import { Header, Box, Button, Text, Image, Card, CardBody, ResponsiveContext } from 'grommet';
import { LinkPrevious } from 'grommet-icons';
import React, { FunctionComponent } from 'react';
import { useHistory, useParams } from 'react-router-dom';

import { GET_CHARACTER } from '../graphql/queries';

const Character: FunctionComponent = () => {
const { id } = useParams() as Record<string, unknown>;
const history = useHistory();
const { loading, data } = useQuery(GET_CHARACTER, {
variables: { id },
});
const { name, species, status, origin, location, image } = data?.character ?? {};
return <>
<Header background='brand'>
<Box direction='row'>
<Button icon={<LinkPrevious />} hoverIndicator onClick={() => history.goBack()} />
<Box alignSelf='center' direction='row' flex='grow' justify='center'>
<Text size='xlarge'>{name}</Text>
</Box>
</Box>
</Header>
<ResponsiveContext.Consumer>
{(size) => {
if (loading) return <><p>Loading ...</p></>;
return <Box direction="row" justify='center' margin={size}>
<Card height={size === 'small' ? 'large' : 'medium'} width={size === 'small' ? 'medium' : 'large'} background='light-1'>
<CardBody pad='none'>
<Box direction={size === 'small' ? 'column' : 'row'}>
<Box direction='row'>
<Box>
<Image
alignSelf='start'
fill='horizontal'
src={image}
/>
</Box>
</Box>
<Box margin='medium'>
<Text weight='bold'>Species: </Text>
<Text >{species}</Text>
<Text>&nbsp;</Text>
<Text weight='bold'>Status: </Text>
<Box direction='row' alignContent='center'>
<Text size='xxlarge' color={status === 'Alive' ? 'green' : 'red'} style={{ lineHeight: '24px' }}>•&nbsp;</Text>
<Text>{status}</Text>
</Box>
<Text>&nbsp;</Text>
<Text weight='bold'>Origin: </Text>
<Text>{origin?.name}</Text>
<Text>&nbsp;</Text>
<Text weight='bold'>Last known location: </Text>
<Text>{location?.name}</Text>
</Box>
</Box>
</CardBody>
</Card>
</Box>
}}
</ResponsiveContext.Consumer>
</>
}

export default Character
Loading

0 comments on commit b5d4192

Please sign in to comment.