Score (self-assessed): 400 / 400 · Live: https://vontanne.github.io/async-race/
Single-page application for the EPAM Campus "Async Race" test task. Manages a garage of radio-controlled cars and runs animated drag races against a local mock REST API. UI state — pagination, sort, form input — persists across navigation.
- Garage CRUD with name validation and RGB color picker
- Bulk-create 100 random cars (10 × 10 brand × model combinations)
- Per-car engine controls with
requestAnimationFrameanimation - Race orchestrator with
Promise.anyfirst-finisher semantics - Winner persistence — increment wins, keep best time
- Winners table with server-side sort and pagination
- Modal winner banner
- Responsive down to 500 px
- Angular 21 — standalone components, signals, OnPush, lazy routes
- TypeScript 5.9 —
strict,noImplicitAny,strictTemplates - RxJS 7
- Vitest 4 + jsdom
- ESLint 9 (flat config) +
typescript-eslint+@angular-eslint - Prettier 3
- Node.js ≥ 20.19 (Node 22 LTS recommended; CI uses 22)
- npm ≥ 10 (project pins
npm@11.12.1)
git clone https://github.com/vontanne/async-race.git
cd async-race
npm ciAPI base URL is a single constant in src/app/core/constants/api.constants.ts:
export const API_BASE_URL = 'http://localhost:3000';Change this value if the mock backend runs on a different host or port. No .env or environment.ts setup is used.
Mock REST API: https://github.com/mikhama/async-race-api
git clone https://github.com/mikhama/async-race-api.git
cd async-race-api
npm install
npm startListens on http://localhost:3000. Leave the process running.
In a separate terminal, inside this repo:
npm startOpen http://localhost:4200.
- Local frontend + local backend. Default development mode. Run
npm starthere, runnpm startinasync-race-api. - Deployed frontend + local backend. Open the live URL and run
npm startinasync-race-apilocally. Modern browsers treathttp://localhostas a secure origin, so HTTPS → HTTP requests work without mixed-content blocking. This is the reviewer's evaluation setup. - Fully deployed. Not supported — per the task brief the mock backend is intentionally not hosted; every reviewer runs it locally.
| Script | Purpose |
|---|---|
npm start |
Dev server at http://localhost:4200 |
npm run build |
Production build → dist/async-race/browser/ |
npm test |
Vitest one-shot (CI mode) |
npm run test:watch |
Vitest watch mode |
npm run lint |
ESLint over src/**/*.{ts,html} |
npm run format |
Prettier write |
npm run ci:format |
Prettier check (CI) |
npm run buildOutput: dist/async-race/browser/. Production budgets: 500 kB warn / 1 MB error initial; 4 / 8 kB per-component style.
GitHub Pages via GitHub Actions (.github/workflows/deploy.yml). Every push to master runs lint + tests + build, then publishes through actions/deploy-pages@v4. No separate deployment branch.
Live: https://vontanne.github.io/async-race/
- UI deployed to GitHub Pages
- Conventional Commits
- Checklist in README
- Score at top of README
- Deployment link at top of README
- Two views — Garage + Winners (10)
- Garage view content — title, create/edit panel, race controls, garage section (30)
- Winners view content — title, table, pagination (10)
- Persistent state across navigation (30)
- CRUD with name validation (20)
- RGB color picker (10)
- Random 100-car generator from 10 × 10 brand × model (20)
- Per-car Select / Delete (10)
- Pagination, 7 per page (10)
- EXTRA — empty-garage message (10)
- EXTRA — auto-back when last car on a page is deleted (10)
- Display winners after race (15)
- Pagination, 10 per page (10)
- Increment wins, keep best time (15)
- Server-side sort by wins and time, ASC / DESC (10)
- Start engine animation; 500 stops the animation (20)
- Stop engine returns the car to its starting position (20)
- Responsive animation at ≤ 500 px (30)
- Start race button — races every car on the current page (10)
- Reset race button — restores every car (15)
- Winner announcement (5)
- Button states reflect engine state (20)
- Predictable behaviour for actions during a race (50)
- Prettier
format+ci:formatscripts (5) - ESLint
lintscript — 40-line function cap, no-magic-numbers (5)