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
22,970 changes: 22,942 additions & 28 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion 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 Down
53 changes: 52 additions & 1 deletion src/api/helpers.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,60 @@
/**
* A utility function to filter out broken apiUrl in vehicles array
*
* @param {Array.<vehicleSummaryPayload>} vehicles
* @param {string} apiUrl
* @return {Array.<vehicleSummaryPayload>}
*/
export const filterOutApiUrl = (vehicles, apiUrl) => {
return vehicles.filter((v) => v.apiUrl !== apiUrl);
};

/**
* Pull vehicle information by id
*
* @param {string} id
* @return {Promise<vehicleSummaryPayload>}
*/
export const getDataDetail = async (id) => {
const data = await fetch(`/api/vehicle_${id}.json`);
const vehicle = data.json();

return vehicle;
};

export const mapVehicleWithExtraProps = async (vehicleParam, props) => {
const vehicle = { ...vehicleParam };
const detail = await getDataDetail(vehicleParam.id);

props.forEach((prop) => {
vehicle[prop] = detail[prop];
});

return vehicle;
};

export const vehicleHasPrice = (vehicleParam) => {
return Boolean(vehicleParam.price);
};

export const filterVehicles = async (vehicles, brokenApiUrl) => {
let filtered = filterOutApiUrl(vehicles, brokenApiUrl);

filtered = await Promise.all(filtered.map((i) => mapVehicleWithExtraProps(i, ['price', 'description'])));
filtered = filtered.filter(vehicleHasPrice);

return filtered;
};

/**
* A utility function to make a network api call
*
* @param {string} apiUrl
* @return {Promise<Object>}
*/
export async function request(apiUrl) {
return apiUrl;
const data = await fetch(apiUrl);
const vehicles = data.json();

return vehicles;
}
8 changes: 6 additions & 2 deletions src/api/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// eslint-disable-next-line no-unused-vars
import { request } from './helpers';
import { filterVehicles, request } from './helpers';

const BROKEN_API_URL = '/api/vehicle_problematic.json';

/**
* Pull vehicles information
Expand All @@ -8,5 +10,7 @@ import { request } from './helpers';
*/
// TODO: All API related logic should be made inside this function.
export default async function getData() {
return [];
const vehicles = await request('/api/vehicles.json');
const filteredVehicles = await filterVehicles(vehicles, BROKEN_API_URL);
return filteredVehicles;
}
40 changes: 40 additions & 0 deletions src/components/Vehicle/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import './style.scss';

const INDX_IMG_16X9 = 0;
const INDX_IMG_1X1 = 1;

/**
*
* @typedef {object} Props
* @property {vehicleSummaryPayload} vehicle - The vehicle properties.
*/

/**
* Pull vehicles information
* @type {React.FC<Props>}
* @return {React.ReactElement} Vehicle
*/
export default function Vehicle({ vehicle }) {
return (
<>
<article className="vehicle">
<div>
<img src={vehicle.media[INDX_IMG_1X1].url} className="vehicle__image-1x1" alt="Vehicle in 1x1 format" />
<img src={vehicle.media[INDX_IMG_16X9].url} className="vehicle__image-16x9" alt="Vehicle in 16x9 format" />
</div>
{/* <img src={vehicle.media[INDX_IMG_16X9].url} /> */}
<div className="vehicle__info">
<h2 className="vehicle__name">{vehicle.id.toUpperCase()}</h2>
<p className="vehicle__price">
From
{' '}
{vehicle.price}
</p>
<p className="vehicle__description">The pinnacle of refined capability</p>
</div>
</article>
</>

);
}
85 changes: 85 additions & 0 deletions src/components/Vehicle/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
$light-gray: #D3D3D3;
$gray: #696969;

.vehicle {
border-bottom: 1px solid $light-gray;
display: grid;
grid-column-gap: 1.5em;
grid-template-columns: 110px 75%;

&__image-1x1 {
height: 100%;
width: 100%;
}

&__image-16x9 {
display: none;
}

&__name {
font-size: 1.2rem;
font-weight: 600;
margin-bottom: 0;
margin-top: .5rem;
}

&__price {
color: $gray;
font-size: .9rem;
margin: 0;
}

&__description {
color: $gray;
font-size: .9rem;
margin-bottom: 1rem;
margin-top: 1rem;
}

&:last-of-type {
border: 0;
}
}

$breakpoint-tablet: 768px;

@media (min-width: $breakpoint-tablet) {

.vehicle {
border: 0;
grid-template-columns: auto;

&__image-1x1 {
display: none;
}

&__image-16x9 {
display: block;
height: 100%;
width: 100%;
}

&__name {
border-bottom: 1px solid;
border-top: 1px solid;
display: inline-block;
margin-bottom: 1rem;
margin-top: 1rem;
}

&__price {
margin-bottom: 1rem;
margin-top: 1rem;
}

&__description {
margin-bottom: .5rem;
margin-left: .5rem;
margin-right: .5rem;
}

&__info {
text-align: center;
}
}
}
2 changes: 1 addition & 1 deletion src/components/VehicleList/__tests__/VehicleList.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('<VehicleList /> Tests', () => {
});

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

expect(queryByTestId('loading')).toBeNull();
Expand Down
28 changes: 7 additions & 21 deletions src/components/VehicleList/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import React from 'react';
import useData from './useData';
import './style.scss';

import Vehicle from '../Vehicle';

export default function VehicleList() {
// eslint-disable-next-line no-unused-vars
const [loading, error, vehicles] = useData();
Expand All @@ -11,30 +13,14 @@ export default function VehicleList() {
}

if (error) {
return <div data-testid="error">{ error }</div>;
return <div data-testid="error">{error}</div>;
}

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 data-testid="results" className="VehicleList">
{vehicles.map((v) => (
<Vehicle vehicle={v} key={v.id} />
))}
</div>
);
}
19 changes: 19 additions & 0 deletions src/components/VehicleList/style.scss
Original file line number Diff line number Diff line change
@@ -1,3 +1,22 @@
.VehicleList {
display: grid;
width: 100%;
}

$breakpoint-tablet: 768px;

@media (min-width: $breakpoint-tablet) {

.VehicleList {
grid-template-columns: repeat(2, 50%);
}
}

$breakpoint-desktops: 992px;

@media (min-width: $breakpoint-desktops) {

.VehicleList {
grid-template-columns: repeat(4, 25%);
}
}
13 changes: 13 additions & 0 deletions src/global-styles.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
@import url(http://fonts.googleapis.com/css?family=Roboto+Slab|Open+Sans:400italic,700italic,400,700);

$roboto-slab: "Roboto Slab", serif;
$open-sans: "Open Sans", sans-serif;

body {
font-family: $open-sans;
}

.root {
margin: 14px;
padding: 12px;
z-index: 12;
}

h2 {
font-weight: normal;
}