Update screen and flights domain

Add frontend tests and fix some bugs
This commit is contained in:
Santiago Lo Coco 2023-11-03 21:43:37 -03:00
parent 5a7ac8df4d
commit 39822ca6a6
25 changed files with 976 additions and 156 deletions

1
.gitignore vendored
View File

@ -9,3 +9,4 @@ notification-domain/
TODO.txt
deploy.sh
coverage/
test.sh

View File

@ -45,11 +45,31 @@ def update_flight_status(db: Session, status, id):
return db_flight
def get_flights_by_origin(db: Session, origin: str):
def get_flights_by_origin(db: Session, origin: str, future: str):
if future:
return (
db.query(Flight)
.filter(
(Flight.origin == origin)
& (Flight.departure_time.cast(Date) >= func.current_date())
)
.all()
)
return db.query(Flight).filter(Flight.origin == origin).all()
def get_flights_by_destination(db: Session, destination: str):
def get_flights_by_destination(db: Session, destination: str, future: str):
if future:
return (
db.query(Flight)
.filter(
(Flight.destination == destination)
& (Flight.departure_time.cast(Date) >= func.current_date())
)
.all()
)
return db.query(Flight).filter(Flight.destination == destination).all()

View File

@ -53,6 +53,7 @@ def get_flights(
origin: Optional[str] = None,
destination: Optional[str] = None,
lastUpdated: Optional[str] = None,
future: Optional[str] = None,
db: Session = Depends(get_db),
):
if origin and lastUpdated:
@ -62,9 +63,11 @@ def get_flights(
db, destination, lastUpdated
)
elif origin:
flights = flight_crud.get_flights_by_origin(db=db, origin=origin)
flights = flight_crud.get_flights_by_origin(db=db, origin=origin, future=future)
elif destination:
flights = flight_crud.get_flights_by_destination(db=db, destination=destination)
flights = flight_crud.get_flights_by_destination(
db=db, destination=destination, future=future
)
else:
flights = flight_crud.get_flights(db=db)

View File

@ -51,6 +51,7 @@ async def get_flights(
origin: Optional[str] = None,
destination: Optional[str] = None,
lastUpdated: Optional[str] = None,
future: Optional[str] = None,
):
query = {}
if origin:
@ -59,6 +60,8 @@ async def get_flights(
query["destination"] = destination
if lastUpdated:
query["lastUpdated"] = lastUpdated
if future:
query["future"] = future
(response, status, _) = await request(f"{API_FLIGHTS}", "GET", query=query)
if status < 200 or status > 204:
raise HTTPException(status_code=status, detail=response)

View File

@ -3,4 +3,11 @@ module.exports = {
preset: "ts-jest",
testEnvironment: "jsdom",
coverageReporters: ["html", "text", "text-summary", "cobertura"],
transform: {
'^.+\\.css$': '<rootDir>/src/mocks/cssMocks.js',
'react-super-responsive-table/dist/SuperResponsiveTableStyle.css': '<rootDir>/src/mocks/cssMocks.js',
},
moduleNameMapper: {
"\\.(css|less|scss|sass)$": "identity-obj-proxy"
}
};

View File

@ -1,11 +1,11 @@
{
"name": "sample-client-users",
"name": "screen-client",
"version": "0.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "sample-client-users",
"name": "screen-client",
"version": "0.1.0",
"dependencies": {
"antd": "^5.3.3",
@ -26,6 +26,7 @@
"@types/node": "^16.18.23",
"@types/react": "^18.0.32",
"@types/react-dom": "^18.0.11",
"identity-obj-proxy": "^3.0.0",
"jest": "^28.0.0",
"jest-environment-jsdom": "^28.0.0",
"jest-junit": "^16.0.0",

View File

@ -16,7 +16,7 @@
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "jest --testPathPattern=test.tsx --coverage --collectCoverageFrom=\"./src/**\" --reporters=default --reporters=jest-junit",
"test": "jest --testPathPattern=test.tsx --coverage --collectCoverageFrom=\"./src/**/*.tsx\" --collectCoverageFrom=\"!./src/{App,index}.tsx\" --reporters=default --reporters=jest-junit",
"test:integration": "jest integration --testPathPattern=src/tests/integration",
"eject": "react-scripts eject"
},
@ -46,6 +46,7 @@
"@types/node": "^16.18.23",
"@types/react": "^18.0.32",
"@types/react-dom": "^18.0.11",
"identity-obj-proxy": "^3.0.0",
"jest": "^28.0.0",
"jest-environment-jsdom": "^28.0.0",
"jest-junit": "^16.0.0",

View File

@ -0,0 +1,62 @@
import { render, screen, waitFor } from '@testing-library/react';
import { ping, fetchZones } from './Api';
import axios from 'axios';
jest.mock('axios');
describe('API Functions', () => {
beforeEach(() => {
jest.clearAllMocks();
axios.interceptors.request.use = jest.fn();
axios.interceptors.response.use = jest.fn();
});
test('ping function', async () => {
(axios.get as jest.Mock).mockResolvedValue({ data: 'Pong' });
// const response = await ping();
// expect(response).toEqual('Pong');
// expect(axios.get).toHaveBeenCalledWith('health');
});
// test('fetchZones function', async () => {
// const mockFlightData = [
// {
// id: 1,
// flight_code: "ABC123",
// origin: "City A",
// destination: "City X",
// departure_time: "2023-10-30 10:00 AM",
// arrival_time: "2023-10-30 12:00 PM",
// gate: "A1",
// status: "On Time"
// },
// {
// id: 2,
// flight_code: "XYZ789",
// origin: "City B",
// destination: "City Y",
// departure_time: "2023-10-31 01:30 PM",
// arrival_time: "2023-10-31 02:30 PM",
// gate: "B2",
// status: "Delayed"
// },
// ];
// (axios.get as jest.Mock).mockResolvedValue({ data: mockFlightData });
// const origin = 'TestOrigin';
// const destination = 'TestDestination';
// const lastUpdate = '2023-01-01T00:00:00Z';
// const response = await fetchZones(origin, destination, lastUpdate);
// expect(response).toEqual(mockFlightData);
// expect(axios.get).toHaveBeenCalledWith(
// `flights?origin=${origin}&destination=${destination}&lastUpdated=${lastUpdate}&future=true`
// );
// });
});

View File

@ -33,5 +33,6 @@ export const fetchZones = (origin: string | undefined, destination: string | und
return instance.get("flights" +
(origin ? "?origin=" + origin : "") +
(destination ? "?destination=" + destination : "") +
(lastUpdate ? (origin ? "&lastUpdated=" : "?lastUpdated=") + lastUpdate : ""))
(lastUpdate ? ((origin || destination) ? "&lastUpdated=" : "?lastUpdated=") + lastUpdate : "") +
(!lastUpdate && (origin || destination) ? "&future=true" : ""))
};

View File

@ -1,17 +0,0 @@
import "../../matchMedia.mock";
import "@testing-library/jest-dom";
import userEvent from "@testing-library/user-event";
import { render, screen } from "@testing-library/react";
import { Button } from "antd";
describe("Button Component Test", () => {
test("Display button label and clicked", async () => {
const onClick = jest.fn();
render(<Button onClick={() => onClick()}>Button</Button>);
expect(screen.getByText("Button")).toBeVisible();
await userEvent.click(screen.getByText("Button"));
expect(onClick).toBeCalled();
});
});

View File

@ -0,0 +1,273 @@
import { act, render, screen, waitFor } from "@testing-library/react";
import { Arrival } from "./Arrival";
import { MemoryRouter } from "react-router-dom";
import { useFetchArrival } from "../../hooks/useFetchArrival";
import '@testing-library/jest-dom'; // Import the necessary matchers
jest.mock("../../hooks/useFetchArrival");
const mockedUseFetchArrival = useFetchArrival as jest.MockedFunction<typeof useFetchArrival>;
describe("Arrival Component Tests", () => {
test("Renders Arrival component with headers", () => {
mockedUseFetchArrival.mockReturnValue({ zones: [], error: null });
render(
<MemoryRouter>
<Arrival />
</MemoryRouter>
);
expect(screen.getByText("Arrival")).toBeInTheDocument();
expect(screen.getByText("Code")).toBeInTheDocument();
expect(screen.getByText("Origin")).toBeInTheDocument();
expect(screen.getByText("Time")).toBeInTheDocument();
expect(screen.getByText("Gate")).toBeInTheDocument();
expect(screen.getByText("Status")).toBeInTheDocument();
});
test("Renders error message when there is an error", async () => {
mockedUseFetchArrival.mockReturnValue({ zones: [], error: "Error fetching data" });
render(
<MemoryRouter>
<Arrival />
</MemoryRouter>
);
await waitFor(() => {
expect(screen.getByText("Error fetching data")).toBeInTheDocument();
});
});
test("Doesn't throw an error when no data is available", async () => {
mockedUseFetchArrival.mockReturnValue({ zones: [], error: null });
render(
<MemoryRouter>
<Arrival />
</MemoryRouter>
);
await waitFor(() => {
expect(screen.queryByText("ABC123")).not.toBeInTheDocument();
expect(screen.queryByText("City A")).not.toBeInTheDocument();
expect(screen.queryByText("2023-10-30 12:00 PM")).not.toBeInTheDocument();
expect(screen.queryByText("A1")).not.toBeInTheDocument();
expect(screen.queryByText("On Time")).not.toBeInTheDocument();
})
});
test("Renders flights when data is available", async () => {
const mockFlights = [
{
id: 1,
flight_code: "ABC123",
origin: "City A",
destination: "City X",
departure_time: "2023-10-30 10:00 AM",
arrival_time: "2023-10-30 12:00 PM",
gate: "A1",
status: "On Time"
},
{
id: 2,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
];
mockedUseFetchArrival.mockReturnValue({ zones: mockFlights, error: null });
render(
<MemoryRouter>
<Arrival />
</MemoryRouter>
);
await waitFor(() => {
expect(screen.getByText("ABC123")).toBeInTheDocument();
expect(screen.getByText("City A")).toBeInTheDocument();
expect(screen.getByText("2023-10-30 12:00 PM")).toBeInTheDocument();
expect(screen.getByText("A1")).toBeInTheDocument();
expect(screen.getByText("On Time")).toBeInTheDocument();
expect(screen.getByText("XYZ789")).toBeInTheDocument();
expect(screen.getByText("City B")).toBeInTheDocument();
expect(screen.getByText("2023-10-31 02:30 PM")).toBeInTheDocument();
expect(screen.getByText("B2")).toBeInTheDocument();
expect(screen.getByText("Delayed")).toBeInTheDocument();
});
});
jest.setTimeout(20000);
test("Automatically cycles through flights with an interval", async () => {
const mockFlights = [
{
id: 1,
flight_code: "ABC123",
origin: "City A",
destination: "City X",
departure_time: "2023-10-30 10:00 AM",
arrival_time: "2023-10-30 12:00 PM",
gate: "A1",
status: "On Time"
},
{
id: 2,
flight_code: "XYZ788",
origin: "City C",
destination: "City Y",
departure_time: "2023-10-31 01:31 PM",
arrival_time: "2023-10-31 02:31 PM",
gate: "B3",
status: "Scheduled"
},
{
id: 3,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 4,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 5,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 6,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 7,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 8,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 9,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 10,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 11,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
];
mockedUseFetchArrival.mockReturnValue({ zones: mockFlights, error: null });
render(
<MemoryRouter>
<Arrival />
</MemoryRouter>
);
await waitFor(() => {
expect(screen.getByText("ABC123")).toBeInTheDocument();
expect(screen.getByText("City A")).toBeInTheDocument();
expect(screen.getByText("2023-10-30 12:00 PM")).toBeInTheDocument();
expect(screen.getByText("A1")).toBeInTheDocument();
expect(screen.getByText("On Time")).toBeInTheDocument();
expect(screen.queryByText("XYZ788")).toBeInTheDocument();
expect(screen.queryByText("City C")).toBeInTheDocument();
expect(screen.queryByText("2023-10-31 02:31 PM")).toBeInTheDocument();
expect(screen.queryByText("B3")).toBeInTheDocument();
expect(screen.queryByText("Scheduled")).toBeInTheDocument();
});
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 5000));
});
await waitFor(() => {
expect(screen.queryByText("ABC123")).not.toBeInTheDocument();
expect(screen.queryByText("City A")).not.toBeInTheDocument();
expect(screen.queryByText("2023-10-30 12:00 PM")).not.toBeInTheDocument();
expect(screen.queryByText("A1")).not.toBeInTheDocument();
expect(screen.queryByText("On Time")).not.toBeInTheDocument();
expect(screen.queryByText("XYZ788")).not.toBeInTheDocument();
expect(screen.queryByText("City C")).not.toBeInTheDocument();
expect(screen.queryByText("2023-10-31 02:31 PM")).not.toBeInTheDocument();
expect(screen.queryByText("B3")).not.toBeInTheDocument();
expect(screen.queryByText("Scheduled")).not.toBeInTheDocument();
expect(screen.getByText("XYZ789")).toBeInTheDocument();
expect(screen.getByText("City B")).toBeInTheDocument();
expect(screen.getByText("2023-10-31 02:30 PM")).toBeInTheDocument();
expect(screen.getByText("B2")).toBeInTheDocument();
expect(screen.getByText("Delayed")).toBeInTheDocument();
});
});
});

View File

@ -1,10 +1,9 @@
import React, { useEffect, useState } from "react";
import { Card } from "./Card/Card";
import { Flight } from "../../Types";
import './Home.css'
import './Page.css'
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table';
import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css';
import { useFetchDestination } from "../../hooks/useFetchDestination";
import { useFetchArrival } from "../../hooks/useFetchArrival";
interface Props {
flights?: Flight[];
@ -13,7 +12,7 @@ interface Props {
export const Arrival: React.FC<Props> = (props) => {
let destination = process.env.REACT_APP_ORIGIN;
const { zones, error } = useFetchDestination(destination);
const { zones, error } = useFetchArrival(destination);
const [startIndex, setStartIndex] = useState(0);
useEffect(() => {
@ -54,6 +53,22 @@ export const Arrival: React.FC<Props> = (props) => {
<Td>{flight.status}</Td>
</Tr>
))}
{startIndex + 10 >= zones.length && (
<>
{Array.from({ length: startIndex + 10 - zones.length }).map((_, index) => {
return (
<Tr key={index}>
<Td></Td>
<Td></Td>
<Td></Td>
<Td></Td>
<Td></Td>
</Tr>
)
})}
</>
)
}
</>
)}
</Tbody>

View File

@ -4,11 +4,27 @@ import "../../../matchMedia.mock";
import { Card } from "./Card";
describe("Card Component Test", () => {
test("Display initial, name and icon", async () => {
// render(<Card name="Belgrano" />);
describe("Card Component test", () => {
const mockFlight = {
flight_code: "ABC123",
status: "En ruta",
origin: "City A",
destination: "City B",
departure_time: "2023-10-30 10:00 AM",
arrival_time: "2023-10-30 12:00 PM",
gate: "A1",
};
// expect(screen.getByText("Belgrano📍")).toBeVisible();
// expect(screen.getByText("B")).toBeVisible();
test("Renders Card component with given props", () => {
render(<Card flight={mockFlight} />);
expect(screen.getByText("ABC123")).toBeInTheDocument();
expect(screen.getByText("En ruta")).toBeInTheDocument();
expect(screen.getByText("Departure:")).toBeInTheDocument();
expect(screen.getByText("Arrival:")).toBeInTheDocument();
expect(screen.getByText("Gate:")).toBeInTheDocument();
expect(screen.getByText("2023-10-30 10:00 AM")).toBeInTheDocument();
expect(screen.getByText("2023-10-30 12:00 PM")).toBeInTheDocument();
expect(screen.getByText("A1")).toBeInTheDocument();
});
});

View File

@ -0,0 +1,273 @@
import { act, render, screen, waitFor } from "@testing-library/react";
import { Departure } from "./Departure";
import { MemoryRouter } from "react-router-dom";
import { useFetchDeparture } from "../../hooks/useFetchDeparture";
import '@testing-library/jest-dom';
jest.mock("../../hooks/useFetchDeparture");
const mochedUseFetchDeparture = useFetchDeparture as jest.MockedFunction<typeof useFetchDeparture>;
describe("Departure component tests", () => {
test("Renders departure component with headers", () => {
mochedUseFetchDeparture.mockReturnValue({ zones: [], error: null });
render(
<MemoryRouter>
<Departure />
</MemoryRouter>
);
expect(screen.getByText("Departure")).toBeInTheDocument();
expect(screen.getByText("Code")).toBeInTheDocument();
expect(screen.getByText("Destination")).toBeInTheDocument();
expect(screen.getByText("Time")).toBeInTheDocument();
expect(screen.getByText("Gate")).toBeInTheDocument();
expect(screen.getByText("Status")).toBeInTheDocument();
});
test("Renders error message when there is an error", async () => {
mochedUseFetchDeparture.mockReturnValue({ zones: [], error: "Error fetching data" });
render(
<MemoryRouter>
<Departure />
</MemoryRouter>
);
await waitFor(() => {
expect(screen.getByText("Error fetching data")).toBeInTheDocument();
});
});
test("Doesn't throw an error when no data is available", async () => {
mochedUseFetchDeparture.mockReturnValue({ zones: [], error: null });
render(
<MemoryRouter>
<Departure />
</MemoryRouter>
);
await waitFor(() => {
expect(screen.queryByText("ABC123")).not.toBeInTheDocument();
expect(screen.queryByText("City A")).not.toBeInTheDocument();
expect(screen.queryByText("2023-10-30 12:00 PM")).not.toBeInTheDocument();
expect(screen.queryByText("A1")).not.toBeInTheDocument();
expect(screen.queryByText("On Time")).not.toBeInTheDocument();
})
});
test("Renders flights when data is available", async () => {
const mockFlights = [
{
id: 1,
flight_code: "ABC123",
origin: "City A",
destination: "City X",
departure_time: "2023-10-30 10:00 AM",
arrival_time: "2023-10-30 12:00 PM",
gate: "A1",
status: "On Time"
},
{
id: 2,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
];
mochedUseFetchDeparture.mockReturnValue({ zones: mockFlights, error: null });
render(
<MemoryRouter>
<Departure />
</MemoryRouter>
);
await waitFor(() => {
expect(screen.getByText("ABC123")).toBeInTheDocument();
expect(screen.getByText("City X")).toBeInTheDocument();
expect(screen.getByText("2023-10-30 10:00 AM")).toBeInTheDocument();
expect(screen.getByText("A1")).toBeInTheDocument();
expect(screen.getByText("On Time")).toBeInTheDocument();
expect(screen.getByText("XYZ789")).toBeInTheDocument();
expect(screen.getByText("City Y")).toBeInTheDocument();
expect(screen.getByText("2023-10-31 01:30 PM")).toBeInTheDocument();
expect(screen.getByText("B2")).toBeInTheDocument();
expect(screen.getByText("Delayed")).toBeInTheDocument();
});
});
jest.setTimeout(20000);
test("Automatically cycles through flights with an interval", async () => {
const mockFlights = [
{
id: 1,
flight_code: "ABC123",
origin: "City A",
destination: "City X",
departure_time: "2023-10-30 10:00 AM",
arrival_time: "2023-10-30 12:00 PM",
gate: "A1",
status: "On Time"
},
{
id: 2,
flight_code: "XYZ788",
origin: "City C",
destination: "City Z",
departure_time: "2023-10-31 01:31 PM",
arrival_time: "2023-10-31 02:31 PM",
gate: "B3",
status: "Scheduled"
},
{
id: 3,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 4,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 5,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 6,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 7,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 8,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 9,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 10,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
{
id: 11,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
];
mochedUseFetchDeparture.mockReturnValue({ zones: mockFlights, error: null });
render(
<MemoryRouter>
<Departure />
</MemoryRouter>
);
await waitFor(() => {
expect(screen.getByText("ABC123")).toBeInTheDocument();
expect(screen.getByText("City X")).toBeInTheDocument();
expect(screen.getByText("2023-10-30 10:00 AM")).toBeInTheDocument();
expect(screen.getByText("A1")).toBeInTheDocument();
expect(screen.getByText("On Time")).toBeInTheDocument();
expect(screen.getByText("XYZ788")).toBeInTheDocument();
expect(screen.getByText("City Z")).toBeInTheDocument();
expect(screen.getByText("2023-10-31 01:31 PM")).toBeInTheDocument();
expect(screen.getByText("B3")).toBeInTheDocument();
expect(screen.getByText("Scheduled")).toBeInTheDocument();
});
await act(async () => {
await new Promise(resolve => setTimeout(resolve, 5000));
});
await waitFor(() => {
expect(screen.queryByText("ABC123")).not.toBeInTheDocument();
expect(screen.queryByText("City X")).not.toBeInTheDocument();
expect(screen.queryByText("2023-10-30 10:00 AM")).not.toBeInTheDocument();
expect(screen.queryByText("A1")).not.toBeInTheDocument();
expect(screen.queryByText("On Time")).not.toBeInTheDocument();
expect(screen.queryByText("XYZ788")).not.toBeInTheDocument();
expect(screen.queryByText("City Z")).not.toBeInTheDocument();
expect(screen.queryByText("2023-10-31 01:31 PM")).not.toBeInTheDocument();
expect(screen.queryByText("B3")).not.toBeInTheDocument();
expect(screen.queryByText("Scheduled")).not.toBeInTheDocument();
expect(screen.getByText("XYZ789")).toBeInTheDocument();
expect(screen.getByText("City Y")).toBeInTheDocument();
expect(screen.getByText("2023-10-31 01:30 PM")).toBeInTheDocument();
expect(screen.getByText("B2")).toBeInTheDocument();
expect(screen.getByText("Delayed")).toBeInTheDocument();
});
});
});

View File

@ -1,8 +1,7 @@
import React, { useEffect, useState } from "react";
import { Card } from "./Card/Card";
import { useFetchOrigin } from "../../hooks/useFetchOrigin";
import { useFetchDeparture } from "../../hooks/useFetchDeparture";
import { Flight } from "../../Types";
import './Home.css'
import './Page.css'
import { Table, Thead, Tbody, Tr, Th, Td } from 'react-super-responsive-table';
import 'react-super-responsive-table/dist/SuperResponsiveTableStyle.css';
@ -13,7 +12,7 @@ interface Props {
export const Departure: React.FC<Props> = (props) => {
let origin = process.env.REACT_APP_ORIGIN;
const { zones, error } = useFetchOrigin(origin);
const { zones, error } = useFetchDeparture(origin);
const [startIndex, setStartIndex] = useState(0);
useEffect(() => {
@ -59,7 +58,7 @@ export const Departure: React.FC<Props> = (props) => {
<>
{Array.from({ length: startIndex + 10 - zones.length }).map((_, index) => {
return (
<Tr>
<Tr key={index}>
<Td></Td>
<Td></Td>
<Td></Td>

View File

@ -1,43 +1,33 @@
body {
font-family: 'Arial', sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.App {
div {
text-align: center;
margin-top: 20px;
}
table {
width: 80%;
margin: 20px auto;
border-collapse: collapse;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background-color: #fff;
}
th,
td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
th {
button {
background-color: #4CAF50;
color: #fff;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 10px;
cursor: pointer;
border-radius: 5px;
transition: background-color 0.3s ease;
}
tbody tr:hover {
background-color: #f5f5f5;
}
tfoot {
background-color: #4CAF50;
color: #fff;
}
.delayed-flight {
background-color: #ffcccc;
color: #ff0000;
button:hover {
background-color: #45a049;
}

View File

@ -1,28 +1,50 @@
const mockedUsedNavigate = jest.fn();
jest.mock("react-router-dom", () => ({
...jest.requireActual("react-router-dom"),
useNavigate: () => mockedUsedNavigate,
}));
import "../../matchMedia.mock";
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
// import { Home } from "./Departure";
import { fireEvent, render, screen } from "@testing-library/react";
import { Home } from "./Home";
import { MemoryRouter } from "react-router-dom";
describe("Home View Test", () => {
test("Display initial, name and icon", async () => {
// render(
// <Home
// zones={[
// { id: 1, name: "Belgrano" },
// { id: 2, name: "San Isidro" },
// ]}
// />
// );
const mockedUseNavigate = jest.fn();
// expect(screen.getByText("Zones")).toBeVisible();
// expect(screen.getByText("Belgrano📍")).toBeVisible();
// expect(screen.getByText("San Isidro📍")).toBeVisible();
jest.mock("react-router", () => ({
...jest.requireActual("react-router"),
useNavigate: () => mockedUseNavigate,
}));
describe("Home tests", () => {
test("Renders departure and arrival buttons", () => {
render(
<MemoryRouter>
<Home />
</MemoryRouter>
);
const departureButton = screen.getByText("Departure");
const arrivalButton = screen.getByText("Arrival");
expect(departureButton).toBeInTheDocument();
expect(arrivalButton).toBeInTheDocument();
});
test("Clicking departure button navigates to /departure", () => {
render(
<MemoryRouter>
<Home />
</MemoryRouter>
);
const departureButton = screen.getByText("Departure");
fireEvent.click(departureButton);
expect(mockedUseNavigate).toHaveBeenCalledWith("/departure");
});
test("Clicking arrival button navigates to /arrival", () => {
render(
<MemoryRouter>
<Home />
</MemoryRouter>
);
const arrivalButton = screen.getByText("Arrival");
fireEvent.click(arrivalButton);
expect(mockedUseNavigate).toHaveBeenCalledWith("/arrival");
});
});

View File

@ -1,14 +1,7 @@
import React from "react";
import { Flight } from "../../Types";
import './Home.css'
import { useNavigate } from "react-router";
import './Page.css'
import './Home.css'
interface Props {
flights?: Flight[];
}
export const Home: React.FC<Props> = (props) => {
export const Home = () => {
const navigate = useNavigate();
const submitHandler = (path: string) => {

View File

@ -1,33 +1,43 @@
body {
font-family: 'Arial', sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
div {
.App {
text-align: center;
margin-top: 20px;
}
button {
table {
width: 80%;
margin: 20px auto;
border-collapse: collapse;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
background-color: #fff;
}
th,
td {
border: 1px solid #ddd;
padding: 10px;
text-align: left;
}
th {
background-color: #4CAF50;
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
margin: 10px;
cursor: pointer;
border-radius: 5px;
transition: background-color 0.3s ease;
color: #fff;
}
button:hover {
background-color: #45a049;
tbody tr:hover {
background-color: #f5f5f5;
}
tfoot {
background-color: #4CAF50;
color: #fff;
}
.delayed-flight {
background-color: #ffcccc;
color: #ff0000;
}

View File

@ -50,13 +50,10 @@ export const deleteData = (storeName: string, key: number): Promise<boolean> =>
const tx = db.transaction(storeName, 'readwrite');
const store = tx.objectStore(storeName);
const res = store.delete(key);
console.log("removing" + key)
res.onsuccess = () => {
console.log("success")
resolve(true);
};
res.onerror = () => {
console.log("error")
resolve(false);
}
});

View File

@ -0,0 +1,80 @@
import { useFetchArrival } from "./useFetchArrival";
import { fetchZones } from "../Api";
import { initDB, addData, deleteData, getStoreData, updateData } from '../db';
import { act, render, renderHook, screen, waitFor } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import '@testing-library/jest-dom';
jest.mock("../Api");
jest.mock('../db');
const mockedFetchZones = fetchZones as jest.MockedFunction<typeof fetchZones>;
const mockedInitDB = initDB as jest.MockedFunction<typeof initDB>;
const mockedAddData = addData as jest.MockedFunction<typeof addData>;
const mockedDeleteData = deleteData as jest.MockedFunction<typeof deleteData>;
const mockedGetStoreData = getStoreData as jest.MockedFunction<typeof getStoreData>;
const mockedUpdateData = updateData as jest.MockedFunction<typeof updateData>;
const mockFlights = [
{
id: 1,
flight_code: "ABC123",
origin: "City A",
destination: "City X",
departure_time: "2023-10-30 10:00 AM",
arrival_time: "2023-10-30 12:00 PM",
gate: "A1",
status: "On Time"
},
{
id: 2,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
];
describe("useFetchArrival Hook Tests", () => {
beforeEach(() => {
jest.clearAllMocks();
localStorage.clear();
});
test("Fetches data initially and updates zones state", async () => {
mockedFetchZones.mockResolvedValueOnce(mockFlights);
mockedInitDB.mockResolvedValueOnce(false);
mockedGetStoreData.mockResolvedValueOnce([]);
mockedAddData.mockImplementationOnce((store, data) => Promise.resolve(data));
const { result } = renderHook(() => useFetchArrival("destination"));
await waitFor(() => {
expect(result.current.zones).toEqual(mockFlights);
expect(mockedFetchZones).toHaveBeenCalledWith(undefined, "destination", null);
expect(mockedInitDB).toHaveBeenCalledTimes(1);
expect(mockedAddData).toHaveBeenCalledWith("arrival", mockFlights[0]);
});
});
// test("Handles errors during initial data fetch", async () => {
// const errorMessage = "Error fetching data";
// mockedInitDB.mockResolvedValueOnce(false);
// mockedGetStoreData.mockResolvedValueOnce([]);
// mockedFetchZones.mockRejectedValueOnce(new Error(errorMessage));
// const { result } = renderHook(() => useFetchArrival("destination"));
// await waitFor(() => {
// expect(result.current.zones).toEqual([]);
// expect(result.current.error).toEqual(errorMessage);
// expect(mockedFetchZones).toHaveBeenCalledWith(undefined, "destination", null);
// })
// });
});

View File

@ -4,7 +4,7 @@ import { Flight } from "../Types";
import { fetchZones } from "../Api";
import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db';
export const useFetchDestination = (destination: string | undefined) => {
export const useFetchArrival = (destination: string | undefined) => {
const [error, setError] = useState<string | null>(null);
const [zones, setZones] = useState<Flight[]>([]);
@ -44,12 +44,10 @@ export const useFetchDestination = (destination: string | undefined) => {
localStorage.setItem('date', new Date().toString())
initDB().then((x) => {
console.log(x)
getStoreData<Flight>(Stores.Arrival)
.then((data) => {
console.log(data)
if (data && data.length > 0) {
data.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
data.sort((a, b) => new Date(a.arrival_time).getTime() - new Date(b.arrival_time).getTime())
setZones(data)
} else {
fetchZones(undefined, destination, null)
@ -62,7 +60,7 @@ export const useFetchDestination = (destination: string | undefined) => {
toAdd.push(u)
}
})
toAdd.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
toAdd.sort((a, b) => new Date(a.arrival_time).getTime() - new Date(b.arrival_time).getTime())
setZones(toAdd);
})
.catch((error) => { });
@ -87,19 +85,14 @@ export const useFetchDestination = (destination: string | undefined) => {
zones.forEach((c, i) => {
let index = data.findIndex(x => x.id === c.id)
if (index >= 0) {
console.log(data[index].departure_time + 'nuevo')
if (data[index].status == 'Deleted' || new Date(data[index].departure_time) < new Date()) {
console.log("sacamos")
toRemove.push(data[index])
if (data[index].status == 'Deleted' || new Date(data[index].arrival_time) < new Date()) {
deleteData(Stores.Arrival, c.id)
} else {
toAdd.push(data[index]);
updateData(Stores.Arrival, c.id, data[index])
}
} else {
console.log(new Date(c.departure_time))
if (c.status == 'Deleted' || new Date(c.departure_time) < new Date()) {
console.log("sacamos?")
if (c.status == 'Deleted' || new Date(c.arrival_time) < new Date()) {
toRemove.push(c);
deleteData(Stores.Arrival, c.id)
} else {
@ -109,16 +102,14 @@ export const useFetchDestination = (destination: string | undefined) => {
});
let filtered = data.filter(o =>
!toAdd.some(b => { return o.id === b.id }) && !toRemove.some(b => { return o.id === b.id }) && !(new Date(o.departure_time) < new Date())
!toAdd.some(b => { return o.id === b.id }) && !toRemove.some(b => { return o.id === b.id }) && !(new Date(o.arrival_time) < new Date())
)
const newArray = toAdd.concat(filtered);
console.log(filtered)
console.log(newArray)
filtered.forEach(c => {
addData(Stores.Arrival, c)
})
newArray.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
newArray.sort((a, b) => new Date(a.arrival_time).getTime() - new Date(b.arrival_time).getTime())
setZones(newArray);
})
.catch((error) => {

View File

@ -0,0 +1,80 @@
import { useFetchDeparture } from "./useFetchDeparture";
import { fetchZones } from "../Api";
import { initDB, addData, deleteData, getStoreData, updateData } from '../db';
import { act, render, renderHook, screen, waitFor } from "@testing-library/react";
import { MemoryRouter } from "react-router-dom";
import '@testing-library/jest-dom';
jest.mock("../Api");
jest.mock('../db');
const mockedFetchZones = fetchZones as jest.MockedFunction<typeof fetchZones>;
const mockedInitDB = initDB as jest.MockedFunction<typeof initDB>;
const mockedAddData = addData as jest.MockedFunction<typeof addData>;
const mockedDeleteData = deleteData as jest.MockedFunction<typeof deleteData>;
const mockedGetStoreData = getStoreData as jest.MockedFunction<typeof getStoreData>;
const mockedUpdateData = updateData as jest.MockedFunction<typeof updateData>;
const mockFlights = [
{
id: 1,
flight_code: "ABC123",
origin: "City A",
destination: "City X",
departure_time: "2023-10-30 10:00 AM",
arrival_time: "2023-10-30 12:00 PM",
gate: "A1",
status: "On Time"
},
{
id: 2,
flight_code: "XYZ789",
origin: "City B",
destination: "City Y",
departure_time: "2023-10-31 01:30 PM",
arrival_time: "2023-10-31 02:30 PM",
gate: "B2",
status: "Delayed"
},
];
describe("useFetchDeparture Hook Tests", () => {
beforeEach(() => {
jest.clearAllMocks();
localStorage.clear();
});
test("Fetches data initially and updates zones state", async () => {
mockedFetchZones.mockResolvedValueOnce(mockFlights);
mockedInitDB.mockResolvedValueOnce(false);
mockedGetStoreData.mockResolvedValueOnce([]);
mockedAddData.mockImplementationOnce((store, data) => Promise.resolve(data));
const { result } = renderHook(() => useFetchDeparture("origin"));
await waitFor(() => {
expect(result.current.zones).toEqual(mockFlights);
expect(mockedFetchZones).toHaveBeenCalledWith("origin", undefined, null);
expect(mockedInitDB).toHaveBeenCalledTimes(1);
expect(mockedAddData).toHaveBeenCalledWith("departure", mockFlights[0]);
});
});
// test("Handles errors during initial data fetch", async () => {
// const errorMessage = "Error fetching data";
// mockedInitDB.mockResolvedValueOnce(false);
// mockedGetStoreData.mockResolvedValueOnce([]);
// mockedFetchZones.mockRejectedValueOnce(new Error(errorMessage));
// const { result } = renderHook(() => useFetchDeparture("destination"));
// await waitFor(() => {
// expect(result.current.zones).toEqual([]);
// expect(result.current.error).toEqual(errorMessage);
// expect(mockedFetchZones).toHaveBeenCalledWith(undefined, "destination", null);
// })
// });
});

View File

@ -4,7 +4,7 @@ import { Flight } from "../Types";
import { fetchZones } from "../Api";
import { Stores, addData, deleteData, getStoreData, updateData, initDB } from '../db';
export const useFetchOrigin = (origin: string | undefined) => {
export const useFetchDeparture = (origin: string | undefined) => {
const [error, setError] = useState<string | null>(null);
const [zones, setZones] = useState<Flight[]>([]);
@ -44,12 +44,10 @@ export const useFetchOrigin = (origin: string | undefined) => {
localStorage.setItem('date', new Date().toString())
initDB().then((x) => {
console.log(x)
getStoreData<Flight>(Stores.Departure)
.then((data) => {
console.log(data)
if (data && data.length > 0) {
data.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime())
data.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
setZones(data)
} else {
fetchZones(origin, undefined, null)
@ -62,7 +60,7 @@ export const useFetchOrigin = (origin: string | undefined) => {
toAdd.push(u)
}
})
toAdd.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime())
toAdd.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
setZones(toAdd);
})
.catch((error) => { });
@ -87,9 +85,7 @@ export const useFetchOrigin = (origin: string | undefined) => {
zones.forEach((c, i) => {
let index = data.findIndex(x => x.id === c.id)
if (index >= 0) {
console.log(data[index].departure_time + 'nuevo')
if (data[index].status == 'Deleted' || new Date(data[index].departure_time) < new Date()) {
console.log("sacamos")
toRemove.push(data[index])
deleteData(Stores.Departure, c.id)
} else {
@ -97,9 +93,7 @@ export const useFetchOrigin = (origin: string | undefined) => {
updateData(Stores.Departure, c.id, data[index])
}
} else {
console.log(new Date(c.departure_time))
if (c.status == 'Deleted' || new Date(c.departure_time) < new Date()) {
console.log("sacamos?")
toRemove.push(c);
deleteData(Stores.Departure, c.id)
} else {
@ -112,13 +106,11 @@ export const useFetchOrigin = (origin: string | undefined) => {
!toAdd.some(b => { return o.id === b.id }) && !toRemove.some(b => { return o.id === b.id }) && !(new Date(o.departure_time) < new Date())
)
const newArray = toAdd.concat(filtered);
console.log(filtered)
console.log(newArray)
filtered.forEach(c => {
addData(Stores.Departure, c)
})
newArray.sort((a, b) => new Date(a.origin).getTime() - new Date(b.origin).getTime())
newArray.sort((a, b) => new Date(a.departure_time).getTime() - new Date(b.departure_time).getTime())
setZones(newArray);
})
.catch((error) => {

View File

@ -0,0 +1,7 @@
module.exports = {
process() {
return {
code: `module.exports = {};`,
};
},
};