diff --git a/README.md b/README.md index ef01338..550abbd 100644 --- a/README.md +++ b/README.md @@ -4,5 +4,32 @@ Please refer to the provided document for the code challenge requirements. ## Available scripts +- `npm install` - Start the application (Port 3000) - `npm start` - Start the application (Port 3000) - `npm test` - Runs available tests + +## Highlights of improvements + +- Instead of looping through data in the frontend side, have used that data in the backend part to fetch the data from API. So instead of multiple calls, we will get data in a single call. +- Data returned has all the values of the temperature and the beer now. +- Frontend is focused now more on rendering rather than on logical aspect. +- Added test cases to increase coverge + +## What would you improve next if you had more time? + +- I could have refactored way more on the backend side. The status logic could have been added in backend side. +- I could have added end to end testing as well. +- API failure scenario is not covered. + +## Questions you would ask and your own answers and assumptions + +- Since styling is missing, so I have assumed that is not required at this point. +- Instead of using setInterval, web sockets could have been used? Probably + +## Explanations of decisions or the approach you took + +- In order to make frontend light, I decided to remove multiple calls. Instead I moved that logic to backend so that only one API is called at a time. +- In test cases, tried to cover the number of API calls based on setInterval +- Also tried to cover the rendering of table based on the mock data. + +## Any other notes you feel relevant to evaluate your test improvements diff --git a/package.json b/package.json index 9be543e..b7978a9 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "test:coverage": "react-scripts test --env=jsdom --watchAll=false --coverage" }, "eslintConfig": { "extends": [ diff --git a/src/App.js b/src/App.js index 232c96d..5d4961e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,62 +1,17 @@ import { useEffect, useState } from 'react'; -const data = [ - { - id: '1', - name: 'Pilsner', - minimumTemperature: 4, - maximumTemperature: 6, - }, - { - id: '2', - name: 'IPA', - minimumTemperature: 5, - maximumTemperature: 6, - }, - { - id: '3', - name: 'Lager', - minimumTemperature: 4, - maximumTemperature: 7, - }, - { - id: '4', - name: 'Stout', - minimumTemperature: 6, - maximumTemperature: 8, - }, - { - id: '5', - name: 'Wheat beer', - minimumTemperature: 3, - maximumTemperature: 5, - }, - { - id: '6', - name: 'Pale Ale', - minimumTemperature: 4, - maximumTemperature: 6, - }, -]; - function App() { const [items, setItems] = useState({}); useEffect(() => { - const request = () => - data.forEach((product) => { - fetch(`http://localhost:8081/temperature/${product.id}`) - .then((response) => response.json()) - .then((response) => - setItems((prevItems) => ({ - ...prevItems, - [product.id]: { - ...product, - ...response, - }, - })) - ); - }); + + const request = () =>{ + fetch(`http://localhost:8081/temperature`) + .then((response) => response.json()) + .then((response) => + setItems(response) + ); + } setInterval(request, 5000); diff --git a/src/App.test.js b/src/App.test.js index 7248a5c..868b7d1 100644 --- a/src/App.test.js +++ b/src/App.test.js @@ -1,8 +1,92 @@ -import {render} from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react' import App from './App'; +const beerMockData =[ + { + id: '1', + name: 'Pilsner', + minimumTemperature: 4, + maximumTemperature: 6, + temperature: 5 + }, + { + id: '2', + name: 'IPA', + minimumTemperature: 5, + maximumTemperature: 6, + temperature: 3, + }, + { + id: '3', + name: 'Lager', + minimumTemperature: 4, + maximumTemperature: 7, + temperature: 8 + } + ]; + describe('', () => { it('renders without errors', () => { render() }); + + it('renders the heading Beers', () => { + render(); + const heading = screen.getByRole('heading', { level: 2 }, { name: 'Beers' }); + expect(heading).toBeInTheDocument(); + }); + + it('fetches and displays the data from the API', async () => { + jest.spyOn(global, 'fetch').mockImplementation(() => + Promise.resolve({ + json: () => Promise.resolve(beerMockData), + }) + ); + + render(); + await waitFor(() => expect(global.fetch).toHaveBeenCalledTimes(1)); + + const rows = screen.getAllByRole('row'); + expect(rows).toHaveLength(4); + + const headers = screen.getAllByRole('columnheader'); + expect(headers[0]).toHaveTextContent('Product'); + expect(headers[1]).toHaveTextContent('Temperature'); + expect(headers[2]).toHaveTextContent('Status'); + + const cells = screen.getAllByRole('cell'); + expect(cells[0]).toHaveTextContent('Pilsner'); + expect(cells[1]).toHaveTextContent('5'); + expect(cells[2]).toHaveTextContent('all good'); + expect(cells[3]).toHaveTextContent('IPA'); + expect(cells[4]).toHaveTextContent('3'); + expect(cells[5]).toHaveTextContent('too low'); + expect(cells[6]).toHaveTextContent('Lager'); + expect(cells[7]).toHaveTextContent('8'); + expect(cells[8]).toHaveTextContent('too high'); + + global.fetch.mockRestore(); + }); + + + it('fetches the data from the API every 5 seconds', async () => { + jest.useFakeTimers(); + + jest.spyOn(global, 'fetch').mockImplementation(() => + Promise.resolve({ + json: () => Promise.resolve(beerMockData), + }) + ); + + render(); + await waitFor(() => expect(global.fetch).toHaveBeenCalledTimes(1)); + + jest.advanceTimersByTime(5000); + await waitFor(() => expect(global.fetch).toHaveBeenCalledTimes(2)); + + jest.advanceTimersByTime(5000); + await waitFor(() => expect(global.fetch).toHaveBeenCalledTimes(3)); + + global.fetch.mockRestore(); + }); }); diff --git a/src/index.test.js b/src/index.test.js new file mode 100644 index 0000000..c5bf141 --- /dev/null +++ b/src/index.test.js @@ -0,0 +1,34 @@ +import { render, screen, waitFor } from '@testing-library/react' +import App from './App'; +import React from 'react'; +import ReactDOM from 'react-dom'; + + +describe('index.js', () => { + it('renders without errors', () => { + const div = document.createElement('div'); + ReactDOM.render( + +
+

SensorTech

+
+ +
, + div + ); + ReactDOM.unmountComponentAtNode(div); + }); + + it('renders the header with text "SensorTech"', () => { + const { getByText } = render( + +
+

SensorTech

+
+ +
+ ); + const headerElement = getByText(/SensorTech/i); + expect(headerElement).toBeInTheDocument(); + }); +}) \ No newline at end of file