Skip to content

Commit 14b7155

Browse files
Merge pull request #3 from Front2FullStack/feature/testing
Basic testing done with github actions
2 parents ecbce28 + 64e625f commit 14b7155

44 files changed

Lines changed: 16010 additions & 3859 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main, master, feature/*]
6+
pull_request:
7+
branches: [main, master]
8+
9+
jobs:
10+
server-tests:
11+
name: Server tests
12+
runs-on: ubuntu-latest
13+
defaults:
14+
run:
15+
working-directory: server/market-trading-service
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
20+
- name: Use Node.js 20
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: "20"
24+
25+
- name: Cache server node modules
26+
uses: actions/cache@v4
27+
with:
28+
path: server/market-trading-service/node_modules
29+
key: ${{ runner.os }}-server-${{ hashFiles('server/market-trading-service/package-lock.json') }}
30+
restore-keys: |
31+
${{ runner.os }}-server-
32+
33+
- name: Install server dependencies
34+
run: npm ci
35+
36+
- name: Run server tests
37+
run: npm test --silent
38+
39+
client-tests:
40+
name: Client tests (conditional)
41+
runs-on: ubuntu-latest
42+
needs: server-tests
43+
steps:
44+
- name: Checkout
45+
uses: actions/checkout@v4
46+
47+
- name: Detect client test script
48+
id: detect_client_tests
49+
run: |
50+
set -e
51+
PKG=client/trading-dashboard/package.json
52+
if [ -f "$PKG" ] && grep -q '"test"' "$PKG"; then
53+
echo "has_tests=true" >> $GITHUB_OUTPUT
54+
else
55+
echo "has_tests=false" >> $GITHUB_OUTPUT
56+
fi
57+
58+
- name: Use Node.js 20
59+
if: steps.detect_client_tests.outputs.has_tests == 'true'
60+
uses: actions/setup-node@v4
61+
with:
62+
node-version: "20"
63+
64+
- name: Cache client node modules
65+
if: steps.detect_client_tests.outputs.has_tests == 'true'
66+
uses: actions/cache@v4
67+
with:
68+
path: client/trading-dashboard/node_modules
69+
key: ${{ runner.os }}-client-${{ hashFiles('client/trading-dashboard/package-lock.json') }}
70+
restore-keys: |
71+
${{ runner.os }}-client-
72+
73+
- name: Install client dependencies
74+
if: steps.detect_client_tests.outputs.has_tests == 'true'
75+
run: |
76+
cd client/trading-dashboard
77+
npm ci
78+
79+
- name: Run client tests
80+
if: steps.detect_client_tests.outputs.has_tests == 'true'
81+
run: |
82+
cd client/trading-dashboard
83+
npm test --silent

.github/workflows/e2e.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: E2E (Cypress)
2+
3+
on:
4+
push:
5+
branches: [main, master, feature/*]
6+
pull_request:
7+
branches: [main, master]
8+
9+
jobs:
10+
cypress-run:
11+
runs-on: ubuntu-latest
12+
timeout-minutes: 20
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
17+
- name: Setup Node 20
18+
uses: actions/setup-node@v4
19+
with:
20+
node-version: "20"
21+
22+
- name: Start client & run Cypress (no backend)
23+
uses: cypress-io/github-action@v6
24+
with:
25+
working-directory: client/trading-dashboard
26+
build: npm run build
27+
start: npm start
28+
wait-on: "http://localhost:3000"
29+
wait-on-timeout: 120
30+
browser: chrome
31+
spec: cypress/e2e/**/*.cy.ts
32+
env:
33+
# Point to an unreachable host so fetch fails and client falls back to MOCK_TICKERS
34+
NEXT_PUBLIC_MARKET_TRADING_URL: http://127.0.0.1:59999
35+
CYPRESS_BASE_URL: http://localhost:3000
36+
37+
- name: Upload Cypress videos/screenshots (if failed)
38+
if: failure()
39+
uses: actions/upload-artifact@v4
40+
with:
41+
name: cypress-artifacts
42+
path: |
43+
client/trading-dashboard/cypress/videos/**
44+
client/trading-dashboard/cypress/screenshots/**

README.md

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,56 @@
11
# multi-bank-coding-challenge
2-
Full Stack real-time trading dashboard
2+
3+
Full-stack real-time trading dashboard used for the Multi Bank coding challenge. This repository contains two main projects and supporting documentation.
4+
5+
Project layout
6+
7+
- `client/trading-dashboard` — Next.js frontend (UI, E2E + unit tests). See: `client/trading-dashboard/README.md` and `client/trading-dashboard/README.md` for developer instructions.
8+
- `server/market-trading-service` — Node + TypeScript microservice that simulates market data and exposes a small REST API. See: `server/market-trading-service/README.md`.
9+
- `docs/` — architecture and project-scope documentation.
10+
- `docker-compose.yml` & `README.DOCKER.md` — Docker setup for running both services together.
11+
12+
Quick start (recommended: Docker)
13+
14+
1. From the repository root, build and start both services with docker-compose:
15+
16+
```bash
17+
docker-compose build
18+
docker-compose up
19+
```
20+
21+
This will start:
22+
23+
- Frontend: http://localhost:3000
24+
- Backend API: http://localhost:3005 (health endpoint: `/health`)
25+
26+
Local development (without Docker)
27+
28+
- Frontend:
29+
30+
```bash
31+
cd client/trading-dashboard
32+
npm install
33+
npm run dev
34+
```
35+
36+
- Backend:
37+
38+
```bash
39+
cd server/market-trading-service
40+
npm install
41+
npm run dev
42+
```
43+
44+
Tests
45+
46+
- Frontend unit tests: run in `client/trading-dashboard` with `npm run test` (Jest)
47+
- Frontend E2E: run Cypress after starting the frontend (see `client/trading-dashboard/cypress.config.ts`)
48+
49+
Notes
50+
51+
- Each subproject includes its own README with more detailed developer and testing instructions. If you plan to contribute, please follow the subproject README before opening a PR.
52+
- If you want CI/CD integration, I can add GitHub Actions workflows that run unit tests and optional Cypress E2E tests.
53+
54+
License
55+
56+
This project is available under the repository LICENSE file.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { render, screen } from "@testing-library/react";
2+
import Index from "../page";
3+
// IMPORTANT: mock child components BEFORE importing the page so the page uses the mocked versions.
4+
jest.mock("@/components/Hero", () => ({
5+
__esModule: true,
6+
default: () => <div data-testid="hero">Hero Component</div>,
7+
}));
8+
9+
jest.mock("@/components/TickerGrid", () => ({
10+
__esModule: true,
11+
default: () => <div data-testid="ticker-grid">TickerGrid Component</div>,
12+
}));
13+
14+
jest.mock("@/components/NewsFeed", () => ({
15+
__esModule: true,
16+
default: () => <div data-testid="news-feed">NewsFeed Component</div>,
17+
}));
18+
19+
describe("Homepage (Index)", () => {
20+
it("renders all main sections: Hero, TickerGrid, and NewsFeed", () => {
21+
render(<Index />);
22+
expect(screen.getByTestId("hero")).toBeInTheDocument();
23+
expect(screen.getByTestId("ticker-grid")).toBeInTheDocument();
24+
expect(screen.getByTestId("news-feed")).toBeInTheDocument();
25+
});
26+
27+
it("has correct layout structure with min-h-screen and bg-background", () => {
28+
const { container } = render(<Index />);
29+
const mainDiv = container.querySelector(".min-h-screen.bg-background");
30+
expect(mainDiv).toBeInTheDocument();
31+
});
32+
});
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"use client";
2+
import { useEffect } from "react";
3+
import Link from "next/link";
4+
import { usePathname } from "next/navigation";
5+
import { AlertTriangle, Home } from "lucide-react";
6+
7+
const NotFound = () => {
8+
const pathname = usePathname();
9+
10+
// Log the error to the console for debugging purposes
11+
useEffect(() => {
12+
console.error(
13+
`404 Not Found: User attempted to access a non-existent route: ${pathname}`
14+
);
15+
}, [pathname]);
16+
17+
return (
18+
<div className="flex min-h-screen items-center justify-center bg-gray-900">
19+
<div className="mx-4 w-full max-w-md rounded-2xl p-8 text-center shadow-xl bg-gray-800/60 dark:backdrop-blur-sm">
20+
<div className="mb-6 flex justify-center">
21+
<AlertTriangle
22+
aria-label="Warning: Page not found"
23+
className="h-16 w-16 text-yellow-500"
24+
/>
25+
</div>
26+
27+
<h1 className="bg-gradient-to-r from-purple-500 to-pink-500 bg-clip-text text-8xl font-extrabold text-transparent">
28+
404
29+
</h1>
30+
31+
<h2 className="mt-4 text-2xl font-bold text-gray-100">
32+
Page Not Found
33+
</h2>
34+
35+
<p className="mt-2 text-gray-500 dark:text-gray-400">
36+
Sorry, the page you are looking for doesn&apos;t exist or has been
37+
moved.
38+
</p>
39+
40+
<div className="mt-8">
41+
<Link
42+
href="/"
43+
className="inline-flex items-center gap-2 rounded-lg bg-blue-600 px-6 py-3 font-semibold text-white shadow-md transition-all duration-300 hover:bg-blue-700 hover:shadow-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800"
44+
>
45+
<Home className="h-5 w-5" />
46+
Return to Homepage
47+
</Link>
48+
</div>
49+
</div>
50+
</div>
51+
);
52+
};
53+
54+
export default NotFound;

client/trading-dashboard/app/page.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,13 @@ import Hero from "@/components/Hero";
22
import NewsFeed from "@/components/NewsFeed";
33
import TickerGrid from "@/components/TickerGrid";
44

5-
const Index = async () => {
5+
// Converted to a synchronous Server Component (no data fetching here) so Jest can render it.
6+
export default function Index() {
67
return (
78
<div className="min-h-screen bg-background">
89
<Hero />
910
<TickerGrid />
1011
<NewsFeed />
1112
</div>
1213
);
13-
};
14-
15-
export default Index;
14+
}

client/trading-dashboard/components/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ const Header = () => {
77
const pathname = usePathname();
88
return (
99
<header className="sticky top-0 z-50 w-full border-b border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
10-
<div className="container mx-auto max-w-7xl flex h-16 items-center justify-between">
10+
<div className="container px-6 mx-auto max-w-7xl flex h-16 items-center justify-between">
1111
<div className="flex items-center gap-2">
1212
<div className="flex h-9 w-9 items-center justify-center rounded-lg bg-primary">
1313
<span className="text-lg font-bold text-primary-foreground">

client/trading-dashboard/components/Hero.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ import { TrendingUp, Activity, BarChart3 } from "lucide-react";
22

33
const Hero = () => {
44
return (
5-
<section className="relative overflow-hidden border-b bg-gradient-to-br from-background via-background to-secondary py-20 px-4">
5+
<section
6+
data-testid="hero"
7+
className="relative overflow-hidden border-b bg-gradient-to-br from-background via-background to-secondary py-20 px-4"
8+
>
69
<div className="absolute inset-0 bg-[radial-gradient(circle_at_30%_20%,hsl(210_100%_56%/0.1),transparent_50%)]" />
710

811
<div className="container mx-auto max-w-7xl relative z-10">

client/trading-dashboard/components/NewsFeed.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const NewsFeed = () => {
4949
},
5050
{
5151
title: "Energy Sector Stocks Rise on Supply Chain Improvements",
52-
source: "Financial Times",
52+
source: "The Guardian",
5353
time: "10 hours ago",
5454
category: "Energy",
5555
url: "#",
@@ -62,7 +62,7 @@ const NewsFeed = () => {
6262
setTimeout(() => {
6363
setNews((prevNews) => [...prevNews].sort(() => Math.random() - 0.5));
6464
}, 500); // optional internal visual delay
65-
}, 2000); // Auto-refresh every 2 seconds
65+
}, 5000); // Auto-refresh every 5 seconds
6666

6767
return () => clearInterval(interval); // Clean up on unmount
6868
}, []);

0 commit comments

Comments
 (0)