Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/api/__tests__/api.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { request } from '../helpers';

jest.mock('../helpers');

describe.skip('getData Tests', () => {
describe('getData Tests', () => {
const safelyCallApi = async () => {
try {
return await getData();
Expand All @@ -28,7 +28,7 @@ describe.skip('getData Tests', () => {

it('Should traverse and make further api calls on main results', async () => {
expect.assertions(3);
request.mockResolvedValueOnce([{ apiUrl: '/api/vehicle_ftype.json' }, { apiUrl: '/api/vehicle_xj.json' }]);
request.mockResolvedValueOnce([{ apiUrl: '/api/vehicle_ftype.json', id: 'ftype' }, { apiUrl: '/api/vehicle_xj.json', id: 'xj' }]);
request.mockResolvedValueOnce({ id: 'ftype', price: '£36,000' });
request.mockResolvedValueOnce({ id: 'xj', price: '£40,000' });
await safelyCallApi();
Expand Down
3 changes: 2 additions & 1 deletion src/api/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@
* @return {Promise<Object>}
*/
export async function request(apiUrl) {
return apiUrl;
const res = await fetch(apiUrl);
return res.json();
}
15 changes: 14 additions & 1 deletion src/api/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,18 @@ import { request } from './helpers';
*/
// TODO: All API related logic should be made inside this function.
export default async function getData() {
return [];
let vehicles = await request('/api/vehicles.json');
const vehiclesWithDetailsPromise = vehicles.map(async vehicle => {
try {
const details = await request(`/api/vehicle_${vehicle.id}.json`);
return {
...vehicle,
...details
}
} catch (error) {
return null;
}
});
vehicles = await Promise.all(vehiclesWithDetailsPromise);
return vehicles.filter(v => !!v && !!v.price);
}
57 changes: 57 additions & 0 deletions src/components/Vehicle/__tests__/Vehicle.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from 'react';
import {render} from '@testing-library/react';
import Vehicle from '..';

const mockVehicle = {
id: "xe",
modelYear: "k17",
description: "Luxury business saloon with distinctive design, dynamic drive and state-of-the-art technologies.",
price: "£30,000",
apiUrl: "/api/vehicles/xe",
media: [{name: "vehicle", url: "/images/16x9/xe_k17.jpg"}, {
name: "vehicle",
url: "/images/1x1/xe_k17.jpg"
}]
};

const mockVehicleNoPrice = {
...mockVehicle,
price: null
};

describe('<Vehicle /> Tests', () => {

it('should render vehicle name', () => {
const {queryByTestId} = render(<Vehicle data={mockVehicle}/>);

expect(queryByTestId('vehicle-name')).not.toBeNull();
expect(queryByTestId('vehicle-name').innerHTML).toBe(mockVehicle.id);
});

it('should render vehicle price', () => {
const {queryByTestId} = render(<Vehicle data={mockVehicle}/>);

expect(queryByTestId('vehicle-price')).not.toBeNull();
expect(queryByTestId('vehicle-price').innerHTML).toBe(`From: ${mockVehicle.price}`);
});

it('should render vehicle description', () => {
const {queryByTestId} = render(<Vehicle data={mockVehicle}/>);

expect(queryByTestId('vehicle-description')).not.toBeNull();
expect(queryByTestId('vehicle-description').innerHTML).toBe(mockVehicle.description);
});

it('should show the vehicle image', () => {
const {queryByTestId} = render(<Vehicle data={mockVehicle}/>);

expect(queryByTestId('vehicle-image')).not.toBeNull();
expect(queryByTestId('vehicle-image').querySelector('img').src).toContain(mockVehicle.media[1].url);
});

it('should hide price if not set', () => {
const {queryByTestId} = render(<Vehicle data={mockVehicleNoPrice}/>);

expect(queryByTestId('vehicle-price')).toBeNull();
});
});
21 changes: 21 additions & 0 deletions src/components/Vehicle/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import styles from './vehicle.module.scss';

export default function Vehicle({data}) {
const {media, id, description, price} = data;

return (
<div className={styles.vehicle} data-testid="vehicle">
<picture className={styles.image} data-testid="vehicle-image">
<source media="(min-width: 769px)" srcSet={media[0].url} />
<img src={media[1].url} alt={media[1].name} />
</picture>

<div className={styles.details}>
<div className={styles.name} data-testid="vehicle-name">{id}</div>
{price && <div className={styles.price} data-testid="vehicle-price">From: {price}</div>}
{<div className={styles.description} data-testid="vehicle-description">{description}</div>}
</div>
</div>
)
}
79 changes: 79 additions & 0 deletions src/components/Vehicle/vehicle.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
@import "../../styles/mixins";

$borderColor: #b7b7b7;

.vehicle {
display: flex;
font-size: 1rem;

@include md-up {
flex-direction: column;
}
}

.image {
display: flex;
width: 30%;

@include md-up {
width: 100%;
}

img {
width: 100%;
aspect-ratio: 1/1;

@include md-up {
aspect-ratio: 16/9;
}
}
}

.details {
width: 70%;
display: flex;
flex-direction: column;
justify-content: space-evenly;
padding: 25px;
border-bottom: 1px solid $borderColor;

@include md-up {
width: 100%;
border-bottom: none;
height: 100%;
align-items: center;
justify-content: space-around;

&:nth-child(even) {
border-left: 1px solid $borderColor;
}
}
}

.name {
text-transform: uppercase;
font-size: 1.1rem;
font-weight: bolder;

@include md-up {
border-width: 2px 0 2px 0;
border-style: solid;
padding: 5px;
margin-bottom: 1.1rem;
}
}

.price {
margin-bottom: 1.2rem;
font-size: 1.0rem;
color: rgba(0, 0, 0, 0.65);

@include md-up {
margin-bottom: 0.5rem;
}
}

.description {
color: rgba(0, 0, 0, 0.55);
font-size: 0.9rem;
}
36 changes: 33 additions & 3 deletions src/components/VehicleList/__tests__/VehicleList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,32 @@ import useData from '../useData';

jest.mock('../useData');

const mockVehicles = [
{
"id": "xe",
"name": "JAGUAR XE",
"modelYear": "k17",
"apiUrl": "/api/vehicles/xe",
"media": [{"name": "vehicle", "url": "/images/16x9/xe_k17.jpg"}, {
"name": "vehicle",
"url": "/images/1x1/xe_k17.jpg"
}]
},
{
"id": "xf",
"name": "JAGUAR XF",
"modelYear": "k17",
"apiUrl": "/api/vehicles/xf",
"media": [{"name": "vehicle", "url": "/images/16x9/xf_k17.jpg"}, {
"name": "vehicle",
"url": "/images/1x1/xf_k17.jpg"
}]
}
];

describe('<VehicleList /> Tests', () => {
it('Should show loading state if it not falsy', () => {
useData.mockReturnValue([true, 'An error occurred', 'results']);
useData.mockReturnValue([true, 'An error occurred', mockVehicles]);
const { queryByTestId } = render(<VehicleList />);

expect(queryByTestId('loading')).not.toBeNull();
Expand All @@ -16,7 +39,7 @@ describe('<VehicleList /> Tests', () => {
});

it('Should show error if it is not falsy and loading is finished', () => {
useData.mockReturnValue([false, 'An error occurred', 'results']);
useData.mockReturnValue([false, 'An error occurred', mockVehicles]);
const { queryByTestId } = render(<VehicleList />);

expect(queryByTestId('loading')).toBeNull();
Expand All @@ -25,11 +48,18 @@ describe('<VehicleList /> Tests', () => {
});

it('Should show results if loading successfully finished', () => {
useData.mockReturnValue([false, false, 'results']);
useData.mockReturnValue([false, false, mockVehicles]);
const { queryByTestId } = render(<VehicleList />);

expect(queryByTestId('loading')).toBeNull();
expect(queryByTestId('error')).toBeNull();
expect(queryByTestId('results')).not.toBeNull();
});

it('should show 2 vehicles', () => {
useData.mockReturnValue([false, false, mockVehicles]);
const {queryAllByTestId} = render(<VehicleList/>);

expect(queryAllByTestId('vehicle')).toHaveLength(2);
});
});
26 changes: 4 additions & 22 deletions src/components/VehicleList/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import useData from './useData';
import './style.scss';
import styles from './vehicle-list.module.scss';
import Vehicle from '../Vehicle';

export default function VehicleList() {
// eslint-disable-next-line no-unused-vars
const [loading, error, vehicles] = useData();

if (loading) {
Expand All @@ -15,26 +15,8 @@ export default function VehicleList() {
}

return (
<div data-testid="results">
<p>List of vehicles will be displayed here</p>
<p>
Visit
<a href="/api/vehicles.json" target="_blank"> /api/vehicles.json</a>
{' '}
(main endpoint)
</p>
<p>
Visit
<a href="/api/vehicle_fpace.json" target="_blank">/api/vehicle_fpace.json</a>
{' '}
(detail endpoint - apiUrl)
</p>
<p>
Visit
<a href="/api/vehicle_xf.json" target="_blank">/api/vehicle_xf.json</a>
{' '}
(vehicle without any price)
</p>
<div className={styles.vehicleList} data-testid="results">
{vehicles.map(v => <Vehicle data={v} key={v.id} />)}
</div>
);
}
3 changes: 0 additions & 3 deletions src/components/VehicleList/style.scss

This file was deleted.

19 changes: 19 additions & 0 deletions src/components/VehicleList/vehicle-list.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
@import "../../styles/mixins";

.vehicleList {
display: flex;
flex-direction: column;
flex-wrap: wrap;

@include md-up {
flex-direction: row;

> * {
width: 50%;
}
}

@include lg-up {
flex-wrap: nowrap;
}
}
13 changes: 9 additions & 4 deletions src/global-styles.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
.root {
margin: 14px;
padding: 12px;
z-index: 12;
@import "styles/typography";

html, body {
margin: 0;
padding: 0;
}

* {
box-sizing: border-box;
}
13 changes: 13 additions & 0 deletions src/styles/mixins.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
@import "variables";

@mixin md-up {
@media screen and (min-width: $md) {
@content;
}
}

@mixin lg-up {
@media screen and (min-width: $lg) {
@content;
}
}
18 changes: 18 additions & 0 deletions src/styles/typography.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
html {
font-size: 12px;
font-family: helvetica, arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;

@media screen and (min-width: 400px) {
font-size: 16px;
}

@media screen and (min-width: 500px) {
font-size: 20px;
}

@media screen and (min-width: 1280px) {
font-size: 24px;
}
}
3 changes: 3 additions & 0 deletions src/styles/variables.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
$md: 769px;
$lg: 960px;
$xl: 1280px;