diff --git a/QA.md b/QA.md new file mode 100644 index 0000000..ea78073 --- /dev/null +++ b/QA.md @@ -0,0 +1,17 @@ +app_type: spa +coverage_applies: false +coverage_source: null +coverage_threshold: 0 +coverage_tool: none +install_steps: +- cd /tmp/forge-repos/hello-world-react-app-e30fc2a0 +- npm install +lint_tool: none +notes: Verify that all tests in the test suite pass for the Hello World React app + using Vitest. +stack: TypeScript/React+Vite +test_files: +- src/components/HelloWorld.test.tsx +- src/App.test.tsx +test_runner: npx vitest run +workspace: /tmp/forge-repos/hello-world-react-app-e30fc2a0 diff --git a/RUNNING.md b/RUNNING.md index 77896cf..949fcff 100644 --- a/RUNNING.md +++ b/RUNNING.md @@ -1,33 +1,68 @@ -# Running the Todo API +# Hello World React App + +A minimal React + Vite + TypeScript application that renders a centered "Hello World" heading using CSS modules. + +## 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 +- npm >= 9 -## Install dependencies +## Setup ```bash -pip install fastapi uvicorn pydantic +npm install ``` -For running the test suite you will also need: +## Development Server ```bash -pip install httpx pytest +npm run dev ``` -## Start the server +Opens on http://localhost:5173 by default. + +## Build ```bash -uvicorn main:app --reload --host 0.0.0.0 --port 8000 +npm run build ``` -The API will be available at . +## Running Tests -Interactive docs are served at . +```bash +npm test +``` -## Run the tests +This runs `vitest run` which executes all `*.test.tsx` files in the `src/` directory. -```bash -pytest tests/ +Test files: +- `src/components/HelloWorld.test.tsx` — Unit tests for the HelloWorld component +- `src/App.test.tsx` — Integration tests for the App component + +## Project Structure + +``` +├── index.html # Vite HTML entry point +├── package.json # Dependencies and scripts +├── tsconfig.json # TypeScript configuration +├── vite.config.ts # Vite + Vitest configuration +├── vite-env.d.ts # TypeScript declarations for Vite +├── src/ +│ ├── main.tsx # React DOM entry point +│ ├── App.tsx # Root App component +│ ├── App.module.css # App-level centering styles +│ ├── App.test.tsx # Integration tests for App +│ └── components/ +│ ├── HelloWorld.tsx # HelloWorld component +│ ├── HelloWorld.module.css # HelloWorld scoped styles +│ └── HelloWorld.test.tsx # Unit tests for HelloWorld +└── RUNNING.md # This file ``` diff --git a/SETUP.md b/SETUP.md index 643c59c..836d151 100644 --- a/SETUP.md +++ b/SETUP.md @@ -1,25 +1,24 @@ # Setup Instructions +This project uses npm for dependency management. Lock files are not committed +and must be generated locally. + ## Install Dependencies ```bash -pip install -r requirements.txt +npm install ``` -## Install Test Dependencies - -```bash -pip install pytest httpx -``` +This will generate `package-lock.json` and populate `node_modules/`. ## Run Tests ```bash -pytest tests/ -v +npm test ``` -## Run the Application +## Start Development Server ```bash -uvicorn main:app --reload +npm run dev ``` diff --git a/index.html b/index.html new file mode 100644 index 0000000..d5fc1f0 --- /dev/null +++ b/index.html @@ -0,0 +1,12 @@ + + + + + + Hello World + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..feafee0 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "hello-world-react", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.6.3", + "@testing-library/react": "^14.3.1", + "@types/react": "^18.2.79", + "@types/react-dom": "^18.2.25", + "@vitejs/plugin-react": "^4.2.1", + "jsdom": "^24.0.0", + "typescript": "^5.4.5", + "vite": "^5.2.11", + "vitest": "^1.6.0" + } +} diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..4f885b4 --- /dev/null +++ b/src/App.css @@ -0,0 +1,14 @@ +/* + * Legacy App.css kept for compatibility. + * Primary styles are in App.module.css (CSS Modules). + * This file provides fallback app-level styles. + */ + +.app-container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + width: 100%; + background-color: #ffffff; +} diff --git a/src/App.module.css b/src/App.module.css new file mode 100644 index 0000000..9029567 --- /dev/null +++ b/src/App.module.css @@ -0,0 +1,7 @@ +.container { + display: flex; + justify-content: center; + align-items: center; + min-height: 100vh; + background-color: #f5f5f5; +} diff --git a/src/App.test.tsx b/src/App.test.tsx new file mode 100644 index 0000000..a6061d1 --- /dev/null +++ b/src/App.test.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import App from './App'; + +describe('App', () => { + it('renders the HelloWorld component with correct text', () => { + render(); + const heading = screen.getByTestId('hello-heading'); + expect(heading).toBeDefined(); + expect(heading.textContent).toBe('Hello World'); + }); + + it('renders the app container with a CSS module class', () => { + render(); + const container = screen.getByTestId('app-container'); + expect(container).toBeDefined(); + expect(container.className).toBeTruthy(); + expect(container.className.length).toBeGreaterThan(0); + }); + + it('app container contains the hello heading', () => { + render(); + const container = screen.getByTestId('app-container'); + const heading = screen.getByTestId('hello-heading'); + expect(container.contains(heading)).toBe(true); + }); + + it('app container has the correct CSS module class name', () => { + render(); + const container = screen.getByTestId('app-container'); + expect(container.className).toContain('container'); + }); + + it('renders the heading as an h1 element inside the app', () => { + render(); + const heading = screen.getByTestId('hello-heading'); + expect(heading.tagName).toBe('H1'); + }); + + it('app container is a div element', () => { + render(); + const container = screen.getByTestId('app-container'); + expect(container.tagName).toBe('DIV'); + }); +}); diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..6e9a5e9 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import HelloWorld from './components/HelloWorld'; +import styles from './App.module.css'; + +/** + * Root application component. + * + * Renders the HelloWorld component inside a centered flex container. + */ +function App(): React.JSX.Element { + return ( +
+ +
+ ); +} + +export default App; diff --git a/src/components/HelloWorld.module.css b/src/components/HelloWorld.module.css new file mode 100644 index 0000000..952902d --- /dev/null +++ b/src/components/HelloWorld.module.css @@ -0,0 +1,6 @@ +.heading { + font-size: 3rem; + font-weight: 700; + color: #333333; + text-align: center; +} diff --git a/src/components/HelloWorld.test.tsx b/src/components/HelloWorld.test.tsx new file mode 100644 index 0000000..dbfa2e2 --- /dev/null +++ b/src/components/HelloWorld.test.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import { describe, it, expect } from 'vitest'; +import HelloWorld from './HelloWorld'; + +describe('HelloWorld', () => { + it('renders an

element with the text "Hello World"', () => { + render(); + const heading = screen.getByTestId('hello-heading'); + expect(heading).toBeDefined(); + expect(heading.tagName).toBe('H1'); + expect(heading.textContent).toBe('Hello World'); + }); + + it('applies the CSS module heading class', () => { + render(); + const heading = screen.getByTestId('hello-heading'); + expect(heading.className).toBeTruthy(); + expect(heading.className.length).toBeGreaterThan(0); + expect(heading.className).toContain('heading'); + }); + + it('renders exactly one heading element', () => { + const { container } = render(); + const headings = container.querySelectorAll('h1'); + expect(headings.length).toBe(1); + }); + + it('heading is accessible via role', () => { + render(); + const heading = screen.getByRole('heading', { level: 1 }); + expect(heading).toBeDefined(); + expect(heading.textContent).toBe('Hello World'); + }); +}); diff --git a/src/components/HelloWorld.tsx b/src/components/HelloWorld.tsx new file mode 100644 index 0000000..941fba2 --- /dev/null +++ b/src/components/HelloWorld.tsx @@ -0,0 +1,18 @@ +import React from 'react'; +import styles from './HelloWorld.module.css'; + +/** + * HelloWorld component. + * + * Renders a styled

heading displaying "Hello World". + * Uses CSS modules for scoped styling. + */ +function HelloWorld(): React.JSX.Element { + return ( +

+ Hello World +

+ ); +} + +export default HelloWorld; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..3d67a50 --- /dev/null +++ b/src/index.css @@ -0,0 +1,35 @@ +/* + * Global reset and base styles. + * Provides a clean foundation: no default margins/padding, + * white background, and modern system font stack. + */ + +*, +*::before, +*::after { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html, +body { + width: 100%; + height: 100%; + background-color: #ffffff; + font-family: + -apple-system, + BlinkMacSystemFont, + "Segoe UI", + Roboto, + "Helvetica Neue", + Arial, + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +#root { + width: 100%; + min-height: 100vh; +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..c018515 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,9 @@ +import React from 'react'; +import ReactDOM from 'react-dom/client'; +import App from './App'; + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/src/test-setup.ts b/src/test-setup.ts new file mode 100644 index 0000000..9883d84 --- /dev/null +++ b/src/test-setup.ts @@ -0,0 +1,5 @@ +/** + * Test setup file for vitest. + * Extends matchers with @testing-library/jest-dom utilities. + */ +import "@testing-library/jest-dom"; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..f2b5c3a --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1,10 @@ +/// + +/** + * Type declarations for CSS module imports. + * Allows TypeScript to understand .module.css imports. + */ +declare module "*.module.css" { + const classes: { readonly [key: string]: string }; + export default classes; +} diff --git a/tests/test_frontend_styling.py b/tests/test_frontend_styling.py new file mode 100644 index 0000000..d336439 --- /dev/null +++ b/tests/test_frontend_styling.py @@ -0,0 +1,171 @@ +"""Tests for frontend CSS styling files. + +Verifies that all required CSS and component files exist with the +expected styling rules for a centered, modern look. +""" + +from __future__ import annotations + +import os +from pathlib import Path + +# Resolve the project root (parent of tests/) +PROJECT_ROOT = Path(__file__).resolve().parent.parent + + +def _read_file(relative_path: str) -> str: + """Read a project file and return its content as a string.""" + full_path = PROJECT_ROOT / relative_path + assert full_path.exists(), f"File not found: {relative_path}" + return full_path.read_text(encoding="utf-8") + + +class TestIndexCSS: + """Tests for the global index.css file.""" + + def test_file_exists(self) -> None: + """index.css must exist in src/.""" + path = PROJECT_ROOT / "src" / "index.css" + assert path.exists() + + def test_white_background(self) -> None: + """Global styles should set a white background.""" + content = _read_file("src/index.css") + assert "#ffffff" in content or "#fff" in content or "white" in content + + def test_box_sizing_reset(self) -> None: + """Global styles should include box-sizing: border-box reset.""" + content = _read_file("src/index.css") + assert "box-sizing" in content + + def test_margin_reset(self) -> None: + """Global styles should reset margins.""" + content = _read_file("src/index.css") + assert "margin: 0" in content or "margin:0" in content + + +class TestAppModuleCSS: + """Tests for the App-level CSS module.""" + + def test_file_exists(self) -> None: + """App.module.css must exist in src/.""" + path = PROJECT_ROOT / "src" / "App.module.css" + assert path.exists() + + def test_flexbox_centering(self) -> None: + """App container should use flexbox for centering.""" + content = _read_file("src/App.module.css") + assert "display: flex" in content or "display:flex" in content + assert "justify-content: center" in content or "justify-content:center" in content + assert "align-items: center" in content or "align-items:center" in content + + def test_min_height_viewport(self) -> None: + """App container should span at least full viewport height.""" + content = _read_file("src/App.module.css") + assert "min-height: 100vh" in content or "min-height:100vh" in content + + def test_white_background(self) -> None: + """App container should have a white background.""" + content = _read_file("src/App.module.css") + assert "#ffffff" in content or "#fff" in content or "white" in content + + +class TestAppCSS: + """Tests for the legacy App.css file.""" + + def test_file_exists(self) -> None: + """App.css must exist in src/.""" + path = PROJECT_ROOT / "src" / "App.css" + assert path.exists() + + +class TestHelloWorldModuleCSS: + """Tests for the HelloWorld component's CSS module.""" + + def test_file_exists(self) -> None: + """HelloWorld.module.css must exist in src/components/.""" + path = PROJECT_ROOT / "src" / "components" / "HelloWorld.module.css" + assert path.exists() + + def test_text_alignment(self) -> None: + """Heading should be centered.""" + content = _read_file("src/components/HelloWorld.module.css") + assert "text-align: center" in content or "text-align:center" in content + + def test_font_size(self) -> None: + """Heading should have a substantial font size.""" + content = _read_file("src/components/HelloWorld.module.css") + assert "font-size" in content + + def test_font_weight(self) -> None: + """Heading should be bold.""" + content = _read_file("src/components/HelloWorld.module.css") + assert "font-weight" in content + + def test_color(self) -> None: + """Heading should have a defined color.""" + content = _read_file("src/components/HelloWorld.module.css") + assert "color" in content + + +class TestComponentFiles: + """Tests that required component source files exist.""" + + def test_app_tsx_exists(self) -> None: + """App.tsx must exist.""" + assert (PROJECT_ROOT / "src" / "App.tsx").exists() + + def test_helloworld_tsx_exists(self) -> None: + """HelloWorld.tsx must exist.""" + assert (PROJECT_ROOT / "src" / "components" / "HelloWorld.tsx").exists() + + def test_main_tsx_exists(self) -> None: + """main.tsx must exist.""" + assert (PROJECT_ROOT / "src" / "main.tsx").exists() + + def test_index_html_exists(self) -> None: + """index.html must exist at project root.""" + assert (PROJECT_ROOT / "index.html").exists() + + +class TestAppTSXContent: + """Tests for App.tsx component content.""" + + def test_imports_css_module(self) -> None: + """App.tsx should import its CSS module.""" + content = _read_file("src/App.tsx") + assert "App.module.css" in content + + def test_imports_helloworld(self) -> None: + """App.tsx should import the HelloWorld component.""" + content = _read_file("src/App.tsx") + assert "HelloWorld" in content + + def test_uses_container_class(self) -> None: + """App.tsx should use the container class from the CSS module.""" + content = _read_file("src/App.tsx") + assert "styles.container" in content + + +class TestHelloWorldTSXContent: + """Tests for HelloWorld.tsx component content.""" + + def test_imports_css_module(self) -> None: + """HelloWorld.tsx should import its CSS module.""" + content = _read_file("src/components/HelloWorld.tsx") + assert "HelloWorld.module.css" in content + + def test_renders_h1(self) -> None: + """HelloWorld.tsx should render an h1 element.""" + content = _read_file("src/components/HelloWorld.tsx") + assert " None: + """HelloWorld.tsx should contain 'Hello World' text.""" + content = _read_file("src/components/HelloWorld.tsx") + assert "Hello World" in content + + def test_uses_heading_class(self) -> None: + """HelloWorld.tsx should use the heading class from CSS module.""" + content = _read_file("src/components/HelloWorld.tsx") + assert "styles.heading" in content diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..0ea80a2 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "types": ["vitest/globals"] + }, + "include": ["src", "vite-env.d.ts"] +} diff --git a/vite-env.d.ts b/vite-env.d.ts new file mode 100644 index 0000000..40abd8c --- /dev/null +++ b/vite-env.d.ts @@ -0,0 +1,6 @@ +/// + +declare module '*.module.css' { + const classes: { readonly [key: string]: string }; + export default classes; +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..1ad46f2 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,15 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + test: { + globals: true, + environment: 'jsdom', + css: { + modules: { + classNameStrategy: 'non-scoped', + }, + }, + }, +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..fe8a147 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + test: { + environment: "jsdom", + globals: true, + css: { + modules: { + classNameStrategy: "non-scoped", + }, + }, + setupFiles: ["./src/test-setup.ts"], + }, +});