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 changes: 22 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
FROM node:18-alpine AS build

WORKDIR /app

COPY package.json ./
RUN npm install

COPY . .
RUN npm run build

FROM node:18-alpine AS production

WORKDIR /app

COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./
COPY --from=build /app/vite.config.ts ./

EXPOSE 5173

CMD ["npx", "vite", "preview", "--host", "0.0.0.0", "--port", "5173"]
73 changes: 60 additions & 13 deletions RUNNING.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,80 @@
# Running the Todo API
# Yellow World Web App

A minimal React/Vite/TypeScript application that displays "yellow world" with yellow-themed styling.

## TEAM_BRIEF
stack: TypeScript/React+Vite
test_runner: npx vitest run
lint_tool: none
coverage_tool: none
coverage_threshold: 0
coverage_applies: false

## Prerequisites

- Python 3.10 or later
- Node.js 18+ and npm
- Docker and Docker Compose (for containerised run)

## Install dependencies
## Local Development

```bash
pip install fastapi uvicorn pydantic
# Install dependencies
npm install

# Start dev server
npm run dev

# Run tests
npm test

# Build for production
npm run build

# Preview production build
npm run preview
```

For running the test suite you will also need:
## Docker

```bash
pip install httpx pytest
# Build and run with Docker Compose
docker compose up --build

# Open in browser
# http://localhost:5173
```

## Start the server
## Project Structure

```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000
```
├── index.html # HTML entry point
├── src/
│ ├── main.tsx # React entry point
│ ├── main.css # Global styles
│ ├── App.tsx # Main App component
│ ├── App.module.css # Yellow-themed CSS module
│ ├── setupTests.ts # Test setup (jest-dom matchers)
│ └── __tests__/
│ └── App.test.tsx # App component test suite
├── vite.config.ts # Vite + Vitest configuration
├── tsconfig.json # TypeScript configuration
├── package.json # Dependencies and scripts
├── Dockerfile # Multi-stage Docker build
└── docker-compose.yml # Docker Compose config
```

## Testing

The API will be available at <http://localhost:8000>.
Tests use Vitest with React Testing Library and jest-dom matchers.
The test suite verifies:

Interactive docs are served at <http://localhost:8000/docs>.
1. The "yellow world" text is rendered in the DOM
2. The text appears inside an `<h1>` element
3. CSS module classes (`container`, `heading`) are correctly applied
4. The DOM structure is correct (div wrapping h1)

## Run the tests
Run tests:

```bash
pytest tests/
npm test
```
21 changes: 10 additions & 11 deletions SETUP.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
# Setup Instructions

## Install Dependencies
This project uses npm for dependency management. Lock files are generated
by the package manager and must not be hand-written.

```bash
pip install -r requirements.txt
```

## Install Test Dependencies
## Generate lock file and install dependencies

```bash
pip install pytest httpx
npm install
```

## Run Tests
This will create `package-lock.json` and populate `node_modules/`.

## Run tests

```bash
pytest tests/ -v
npm test
```

## Run the Application
## Build

```bash
uvicorn main:app --reload
npm run build
```
10 changes: 10 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: "3.9"

services:
yellow-world:
build:
context: .
dockerfile: Dockerfile
ports:
- "5173:5173"
restart: unless-stopped
12 changes: 12 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Yellow World</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
27 changes: 27 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "yellow-world",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest run"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.1.2",
"@types/react": "^18.2.43",
"@types/react-dom": "^18.2.17",
"@vitejs/plugin-react": "^4.2.1",
"jsdom": "^23.0.1",
"typescript": "^5.3.3",
"vite": "^5.0.8",
"vitest": "^1.1.0"
}
}
20 changes: 20 additions & 0 deletions src/App.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/* Yellow-themed styling for the App component */

.container {
display: flex;
align-items: center;
justify-content: center;
min-height: 100vh;
background-color: #ffd700;
}

.heading {
color: #8b7500;
font-size: 3rem;
font-weight: bold;
text-align: center;
padding: 2rem;
background-color: #ffecb3;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
20 changes: 20 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import styles from './App.module.css';

/**
* Main application component.
*
* Renders a "yellow world" heading inside a yellow-themed container.
* Styling is applied via CSS modules defined in App.module.css.
*/
const App: React.FC = () => {
return (
<div className={styles.container} data-testid="app-container">
<h1 className={styles.heading} data-testid="app-heading">
yellow world
</h1>
</div>
);
};

export default App;
78 changes: 78 additions & 0 deletions src/__tests__/App.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import React from 'react';
import { render, screen } from '@testing-library/react';
import { describe, it, expect } from 'vitest';
import App from '../App';

/**
* Test suite for the App component.
*
* Verifies that the 'yellow world' text is rendered in the DOM and
* that the correct yellow-themed CSS module class names are applied
* to the container and heading elements.
*/
describe('App Component', () => {
it('renders the yellow world text', () => {
render(<App />);
const heading = screen.getByText('yellow world');
expect(heading).toBeInTheDocument();
});

it('renders the yellow world text in an h1 element', () => {
render(<App />);
const heading = screen.getByRole('heading', { level: 1 });
expect(heading).toBeInTheDocument();
expect(heading).toHaveTextContent('yellow world');
});

it('applies the container CSS module class to the wrapper div', () => {
render(<App />);
const container = screen.getByTestId('app-container');
expect(container).toBeInTheDocument();
expect(container.className).toContain('container');
});

it('applies the heading CSS module class to the h1 element', () => {
render(<App />);
const heading = screen.getByTestId('app-heading');
expect(heading).toBeInTheDocument();
expect(heading.className).toContain('heading');
});

it('renders the heading inside the container', () => {
render(<App />);
const container = screen.getByTestId('app-container');
const heading = screen.getByTestId('app-heading');
expect(container).toContainElement(heading);
});

it('renders exactly one h1 element', () => {
const { container } = render(<App />);
const headings = container.querySelectorAll('h1');
expect(headings).toHaveLength(1);
});

it('has the correct text content without extra whitespace', () => {
render(<App />);
const heading = screen.getByTestId('app-heading');
expect(heading.textContent?.trim()).toBe('yellow world');
});

it('container and heading have distinct CSS module classes', () => {
render(<App />);
const container = screen.getByTestId('app-container');
const heading = screen.getByTestId('app-heading');
expect(container.className).not.toBe(heading.className);
});

it('container element is a div', () => {
render(<App />);
const container = screen.getByTestId('app-container');
expect(container.tagName).toBe('DIV');
});

it('heading element is an h1', () => {
render(<App />);
const heading = screen.getByTestId('app-heading');
expect(heading.tagName).toBe('H1');
});
});
7 changes: 7 additions & 0 deletions src/__tests__/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Vitest setup file.
*
* Extends expect with jest-dom matchers for DOM assertion helpers
* such as toBeInTheDocument() and toHaveClass().
*/
import "@testing-library/jest-dom";
12 changes: 12 additions & 0 deletions src/main.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/* Global reset and base styles */

* {
box-sizing: border-box;
}

body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
'Helvetica Neue', Arial, sans-serif;
}
14 changes: 14 additions & 0 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import './main.css';

/**
* Application entry point.
* Mounts the root <App /> component into the #root DOM element.
*/
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
);
5 changes: 5 additions & 0 deletions src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Test setup file loaded before each test suite.
* Imports jest-dom matchers for enhanced DOM assertions.
*/
import '@testing-library/jest-dom';
12 changes: 12 additions & 0 deletions src/vite-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/// <reference types="vite/client" />

/**
* Type declarations for CSS module imports.
*
* Allows TypeScript to understand `import styles from '*.module.css'`
* and treat the default export as a record of class name strings.
*/
declare module "*.module.css" {
const classes: { readonly [key: string]: string };
export default classes;
}
Loading