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
19 changes: 19 additions & 0 deletions QA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
app_type: spa
coverage_applies: false
coverage_source: null
coverage_threshold: 0
coverage_tool: none
install_steps:
- npm install
- npm install --save-dev @testing-library/jest-dom @testing-library/react @testing-library/user-event
jsdom vitest @vitest/coverage-v8
lint_tool: none
notes: Verify that all main components render and user interactions work as expected
in the real estate SPA.
stack: TypeScript/React+Vite
test_files:
- src/__tests__/App.test.tsx
- src/__tests__/RecentSales.test.tsx
- src/__tests__/ContactInfo.test.tsx
test_runner: npx vitest run
workspace: /tmp/forge-repos/website-for-realestate-single-page-with--c91683d9
57 changes: 36 additions & 21 deletions RUNNING.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,48 @@
# Running the Todo API
# Madhuri Real Estate - Single Page Application

## Prerequisites
## TEAM_BRIEF
stack: TypeScript/React
test_runner: npx jest --verbose
lint_tool: none
coverage_tool: none
coverage_threshold: 0
coverage_applies: false

- Python 3.10 or later
## Overview
A React-based single-page application for Madhuri Real Estate, featuring a logo, company name, agent profile, recent sales showcase, and contact form.

## Install dependencies
## Prerequisites
- Node.js 18+
- npm

## Setup
```bash
pip install fastapi uvicorn pydantic
npm install
```

For running the test suite you will also need:

## Running Tests
```bash
pip install httpx pytest
npm test
```

## Start the server

```bash
uvicorn main:app --reload --host 0.0.0.0 --port 8000
## Project Structure
```

The API will be available at <http://localhost:8000>.

Interactive docs are served at <http://localhost:8000/docs>.

## Run the tests

```bash
pytest tests/
src/
├── App.tsx # Main application component
├── components/
│ ├── Logo.tsx # Logo display component
│ ├── CompanyName.tsx # Company name heading component
│ ├── Profile.tsx # Agent profile component
│ ├── RecentSales.tsx # Recent sales grid component
│ └── ContactInfo.tsx # Contact info and form component
├── styles/
│ └── global.css # Global styles
├── __tests__/
│ ├── App.test.tsx # App composition tests
│ ├── RecentSales.test.tsx # Recent sales rendering tests
│ └── ContactInfo.test.tsx # Contact form validation tests
└── __mocks__/
└── fileMock.ts # Static file mock for Jest
public/
└── logo.svg # Company logo asset
```
27 changes: 11 additions & 16 deletions SETUP.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
# Setup Instructions

## Install Dependencies
The following files are generated by tooling and should NOT be hand-written:

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

## Install Test Dependencies

```bash
pip install pytest httpx
```
- `package-lock.json` — generated by `npm install`
- `node_modules/` — generated by `npm install`
- `build/` — generated by `npm run build`

## Run Tests
## Initial Setup

```bash
pytest tests/ -v
```
# Install all dependencies (generates package-lock.json and node_modules/)
npm install

## Run the Application
# Verify the setup by running tests
npm test

```bash
uvicorn main:app --reload
# Start development server
npm run dev
```
14 changes: 14 additions & 0 deletions frontend/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Madhuri Real Estate - Your trusted real estate partner" />
<link rel="icon" type="image/svg+xml" href="/logo.svg" />
<title>Madhuri Real Estate</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
30 changes: 30 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "madhuri-real-estate",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc && vite build",
"preview": "vite preview",
"test": "vitest run",
"test:watch": "vitest",
"lint": "eslint src/"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.1.2",
"@testing-library/user-event": "^14.5.1",
"@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"
}
}
21 changes: 21 additions & 0 deletions frontend/public/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
33 changes: 33 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import React from 'react';
import { Logo } from './components/Logo';
import { CompanyName } from './components/CompanyName';
import { Profile } from './components/Profile';
import { RecentSales } from './components/RecentSales';
import { ContactInfo } from './components/ContactInfo';

/**
* Main application component.
* Composes all page sections in order: Logo, CompanyName, Profile, RecentSales, ContactInfo.
*/
const App: React.FC = () => {
return (
<div className="app-container">
<header className="app-header">
<Logo />
<CompanyName />
</header>

<main className="app-main">
<Profile />
<RecentSales />
<ContactInfo />
</main>

<footer className="app-footer">
<p>&copy; {new Date().getFullYear()} Madhuri Real Estate. All rights reserved.</p>
</footer>
</div>
);
};

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

describe('App', () => {
it('renders the Logo component', () => {
render(<App />);
expect(screen.getByTestId('logo')).toBeInTheDocument();
});

it('renders the CompanyName component', () => {
render(<App />);
expect(screen.getByTestId('company-name')).toBeInTheDocument();
});

it('renders the Profile component', () => {
render(<App />);
expect(screen.getByTestId('profile')).toBeInTheDocument();
});

it('renders the RecentSales component', () => {
render(<App />);
expect(screen.getByTestId('recent-sales')).toBeInTheDocument();
});

it('renders the ContactInfo component', () => {
render(<App />);
expect(screen.getByTestId('contact-info')).toBeInTheDocument();
});

it('renders all sections in correct order', () => {
const { container } = render(<App />);

const header = container.querySelector('.app-header');
const main = container.querySelector('.app-main');

expect(header).toBeInTheDocument();
expect(main).toBeInTheDocument();

// Logo and CompanyName should be in the header
expect(header!.querySelector('[data-testid="logo"]')).toBeInTheDocument();
expect(header!.querySelector('[data-testid="company-name"]')).toBeInTheDocument();

// Profile, RecentSales, ContactInfo should be in main
const mainChildren = main!.children;
expect(mainChildren[0]).toHaveAttribute('data-testid', 'profile');
expect(mainChildren[1]).toHaveAttribute('data-testid', 'recent-sales');
expect(mainChildren[2]).toHaveAttribute('data-testid', 'contact-info');
});

it('renders the footer with copyright', () => {
render(<App />);
const year = new Date().getFullYear().toString();
expect(screen.getByText(new RegExp(year))).toBeInTheDocument();
});
});
109 changes: 109 additions & 0 deletions frontend/src/__tests__/ContactInfo.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect } from 'vitest';
import { ContactInfo } from '../components/ContactInfo';

describe('ContactInfo', () => {
it('renders the section heading', () => {
render(<ContactInfo />);
expect(screen.getByText('Contact Us')).toBeInTheDocument();
});

it('displays phone number', () => {
render(<ContactInfo />);
expect(screen.getByTestId('contact-phone')).toHaveTextContent('(555) 123-4567');
});

it('displays email address', () => {
render(<ContactInfo />);
expect(screen.getByTestId('contact-email')).toHaveTextContent(
'info@madhurirealestate.com',
);
});

it('displays physical address', () => {
render(<ContactInfo />);
expect(screen.getByTestId('contact-address')).toHaveTextContent(
'100 Main Street, Suite 200, Springfield, IL 62701',
);
});

it('renders the contact form', () => {
render(<ContactInfo />);
expect(screen.getByTestId('contact-form')).toBeInTheDocument();
});

it('renders name, email, and message fields', () => {
render(<ContactInfo />);
expect(screen.getByLabelText(/name/i)).toBeInTheDocument();
expect(screen.getByLabelText(/email/i)).toBeInTheDocument();
expect(screen.getByLabelText(/message/i)).toBeInTheDocument();
});

it('shows validation errors when submitting empty form', async () => {
const user = userEvent.setup();
render(<ContactInfo />);

const submitBtn = screen.getByRole('button', { name: /send message/i });
await user.click(submitBtn);

expect(screen.getByTestId('error-name')).toHaveTextContent('Name is required');
expect(screen.getByTestId('error-email')).toHaveTextContent('Email is required');
expect(screen.getByTestId('error-message')).toHaveTextContent(
'Message is required',
);
});

it('shows email validation error for invalid email', async () => {
const user = userEvent.setup();
render(<ContactInfo />);

await user.type(screen.getByLabelText(/name/i), 'John Doe');
await user.type(screen.getByLabelText(/email/i), 'invalid-email');
await user.type(screen.getByLabelText(/message/i), 'Hello there');

const submitBtn = screen.getByRole('button', { name: /send message/i });
await user.click(submitBtn);

expect(screen.getByTestId('error-email')).toHaveTextContent(
'Please enter a valid email address',
);
});

it('shows success message on valid form submission', async () => {
const user = userEvent.setup();
render(<ContactInfo />);

await user.type(screen.getByLabelText(/name/i), 'John Doe');
await user.type(screen.getByLabelText(/email/i), 'john@example.com');
await user.type(screen.getByLabelText(/message/i), 'I am interested in a property');

const submitBtn = screen.getByRole('button', { name: /send message/i });
await user.click(submitBtn);

expect(screen.getByTestId('form-success')).toBeInTheDocument();
expect(
screen.getByText(/thank you for your message/i),
).toBeInTheDocument();
});

it('clears form fields after successful submission', async () => {
const user = userEvent.setup();
render(<ContactInfo />);

const nameInput = screen.getByLabelText(/name/i) as HTMLInputElement;
const emailInput = screen.getByLabelText(/email/i) as HTMLInputElement;
const messageInput = screen.getByLabelText(/message/i) as HTMLTextAreaElement;

await user.type(nameInput, 'John Doe');
await user.type(emailInput, 'john@example.com');
await user.type(messageInput, 'Hello');

const submitBtn = screen.getByRole('button', { name: /send message/i });
await user.click(submitBtn);

expect(nameInput.value).toBe('');
expect(emailInput.value).toBe('');
expect(messageInput.value).toBe('');
});
});
Loading