Got basics of FE/BE running. BE passes tests
Browse files- .gitignore +67 -0
- Dockerfile +44 -0
- backend/app/__init__.py +1 -0
- backend/app/main.py +38 -0
- backend/tests/test_quiz.py +21 -0
- frontend/jest.config.js +8 -0
- frontend/package.json +48 -0
- frontend/public/index.html +14 -0
- frontend/public/manifest.json +8 -0
- frontend/public/robots.txt +2 -0
- frontend/src/App.tsx +35 -0
- frontend/src/components/DocumentInput.tsx +38 -0
- frontend/src/components/Header.tsx +13 -0
- frontend/src/components/ProblemList.tsx +26 -0
- frontend/src/components/QuizGenerator.tsx +44 -0
- frontend/src/index.js +13 -0
- frontend/src/tests/App.test.tsx +20 -0
- frontend/src/tests/components/DocumentInput.test.tsx +30 -0
- frontend/src/tests/components/QuizGenerator.test.tsx +35 -0
- frontend/tsconfig.json +22 -0
- pyproject.toml +22 -0
- pytest.ini +2 -0
.gitignore
ADDED
@@ -0,0 +1,67 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Python
|
2 |
+
__pycache__/
|
3 |
+
*.py[cod]
|
4 |
+
*$py.class
|
5 |
+
*.so
|
6 |
+
.Python
|
7 |
+
build/
|
8 |
+
develop-eggs/
|
9 |
+
dist/
|
10 |
+
downloads/
|
11 |
+
eggs/
|
12 |
+
.eggs/
|
13 |
+
lib/
|
14 |
+
lib64/
|
15 |
+
parts/
|
16 |
+
sdist/
|
17 |
+
var/
|
18 |
+
wheels/
|
19 |
+
*.egg-info/
|
20 |
+
.installed.cfg
|
21 |
+
*.egg
|
22 |
+
.pytest_cache/
|
23 |
+
.coverage
|
24 |
+
htmlcov/
|
25 |
+
.env
|
26 |
+
.venv
|
27 |
+
env/
|
28 |
+
venv/
|
29 |
+
ENV/
|
30 |
+
.uv/
|
31 |
+
|
32 |
+
# Node/React
|
33 |
+
node_modules/
|
34 |
+
coverage/
|
35 |
+
build/
|
36 |
+
.DS_Store
|
37 |
+
.env.local
|
38 |
+
.env.development.local
|
39 |
+
.env.test.local
|
40 |
+
.env.production.local
|
41 |
+
npm-debug.log*
|
42 |
+
yarn-debug.log*
|
43 |
+
yarn-error.log*
|
44 |
+
|
45 |
+
# IDEs and editors
|
46 |
+
.idea/
|
47 |
+
.vscode/
|
48 |
+
*.swp
|
49 |
+
*.swo
|
50 |
+
.project
|
51 |
+
.classpath
|
52 |
+
.settings/
|
53 |
+
*.sublime-workspace
|
54 |
+
*.sublime-project
|
55 |
+
|
56 |
+
# OS generated files
|
57 |
+
.DS_Store
|
58 |
+
.DS_Store?
|
59 |
+
._*
|
60 |
+
.Spotlight-V100
|
61 |
+
.Trashes
|
62 |
+
ehthumbs.db
|
63 |
+
Thumbs.db
|
64 |
+
|
65 |
+
# Docker
|
66 |
+
.docker/
|
67 |
+
*.log
|
Dockerfile
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Use Node.js image for building frontend
|
2 |
+
FROM node:20-slim AS frontend-builder
|
3 |
+
|
4 |
+
WORKDIR /app/frontend
|
5 |
+
|
6 |
+
# Copy package files first for better caching
|
7 |
+
COPY frontend/package*.json ./
|
8 |
+
RUN npm cache clean --force && \
|
9 |
+
npm install --legacy-peer-deps --force
|
10 |
+
|
11 |
+
# Copy only the necessary frontend files
|
12 |
+
COPY frontend/public ./public
|
13 |
+
COPY frontend/src ./src
|
14 |
+
COPY frontend/tsconfig.json .
|
15 |
+
COPY frontend/jest.config.js .
|
16 |
+
COPY frontend/.env .
|
17 |
+
|
18 |
+
# Show more verbose output during build
|
19 |
+
RUN npm run build --verbose
|
20 |
+
|
21 |
+
# Use Python image with uv pre-installed
|
22 |
+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
|
23 |
+
|
24 |
+
WORKDIR /app
|
25 |
+
|
26 |
+
# Copy backend code
|
27 |
+
COPY backend/ backend/
|
28 |
+
COPY pyproject.toml .
|
29 |
+
|
30 |
+
# Install backend dependencies and make pytest available
|
31 |
+
RUN uv sync && uv pip install .
|
32 |
+
ENV PATH="/root/.local/bin:/root/.uv/venv/bin:${PATH}"
|
33 |
+
|
34 |
+
# Copy frontend build
|
35 |
+
COPY --from=frontend-builder /app/frontend/build /app/frontend/build
|
36 |
+
|
37 |
+
# Add uv's bin directory to PATH
|
38 |
+
ENV PATH="/app/.venv/bin:/root/.local/bin:/root/.uv/venv/bin:${PATH}"
|
39 |
+
|
40 |
+
# Expose port
|
41 |
+
EXPOSE 8000
|
42 |
+
|
43 |
+
# Run the application
|
44 |
+
CMD ["uvicorn", "backend.app.main:app", "--host", "0.0.0.0", "--port", "8000"]
|
backend/app/__init__.py
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
# Empty file to make the directory a Python package
|
backend/app/main.py
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi import FastAPI
|
2 |
+
from fastapi.middleware.cors import CORSMiddleware
|
3 |
+
from pydantic import BaseModel
|
4 |
+
import random
|
5 |
+
|
6 |
+
app = FastAPI()
|
7 |
+
|
8 |
+
# Add CORS middleware
|
9 |
+
app.add_middleware(
|
10 |
+
CORSMiddleware,
|
11 |
+
allow_origins=["*"], # In production, replace with specific origins
|
12 |
+
allow_credentials=True,
|
13 |
+
allow_methods=["*"],
|
14 |
+
allow_headers=["*"],
|
15 |
+
)
|
16 |
+
|
17 |
+
class UrlInput(BaseModel):
|
18 |
+
url: str
|
19 |
+
|
20 |
+
class UserQuery(BaseModel):
|
21 |
+
user_query: str
|
22 |
+
|
23 |
+
@app.post("/crawl/")
|
24 |
+
async def crawl_documentation(input_data: UrlInput):
|
25 |
+
print(f"Received url {input_data.url}")
|
26 |
+
return {"status": "received"}
|
27 |
+
|
28 |
+
@app.post("/problems/")
|
29 |
+
async def generate_problems(query: UserQuery):
|
30 |
+
# For MVP, returning random sample questions
|
31 |
+
sample_questions = [
|
32 |
+
"What is the main purpose of this framework?",
|
33 |
+
"How do you install this tool?",
|
34 |
+
"What are the key components?",
|
35 |
+
"Explain the basic workflow",
|
36 |
+
"What are the best practices?"
|
37 |
+
]
|
38 |
+
return {"Problems": sample_questions}
|
backend/tests/test_quiz.py
ADDED
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from fastapi.testclient import TestClient
|
2 |
+
from backend.app.main import app
|
3 |
+
|
4 |
+
client = TestClient(app)
|
5 |
+
|
6 |
+
def test_crawl_endpoint():
|
7 |
+
response = client.post(
|
8 |
+
"/crawl/",
|
9 |
+
json={"url": "https://example.com"}
|
10 |
+
)
|
11 |
+
assert response.status_code == 200
|
12 |
+
assert response.json() == {"status": "received"}
|
13 |
+
|
14 |
+
def test_problems_endpoint():
|
15 |
+
response = client.post(
|
16 |
+
"/problems/",
|
17 |
+
json={"user_query": "test query"}
|
18 |
+
)
|
19 |
+
assert response.status_code == 200
|
20 |
+
assert "Problems" in response.json()
|
21 |
+
assert len(response.json()["Problems"]) == 5
|
frontend/jest.config.js
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
module.exports = {
|
2 |
+
preset: 'ts-jest',
|
3 |
+
testEnvironment: 'jsdom',
|
4 |
+
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
|
5 |
+
moduleNameMapper: {
|
6 |
+
'\\.(css|less|scss|sass)$': 'identity-obj-proxy',
|
7 |
+
},
|
8 |
+
};
|
frontend/package.json
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"name": "simplify-frontend",
|
3 |
+
"version": "0.1.0",
|
4 |
+
"private": true,
|
5 |
+
"dependencies": {
|
6 |
+
"@emotion/react": "^11.11.3",
|
7 |
+
"@emotion/styled": "^11.11.0",
|
8 |
+
"@mui/material": "^5.15.10",
|
9 |
+
"@testing-library/jest-dom": "^6.4.2",
|
10 |
+
"@testing-library/react": "^14.2.1",
|
11 |
+
"@testing-library/user-event": "^14.5.2",
|
12 |
+
"@types/jest": "^29.5.12",
|
13 |
+
"@types/node": "^20.11.19",
|
14 |
+
"@types/react": "^18.2.56",
|
15 |
+
"@types/react-dom": "^18.2.19",
|
16 |
+
"ajv": "^8.12.0",
|
17 |
+
"ajv-keywords": "^5.1.0",
|
18 |
+
"react": "^18.2.0",
|
19 |
+
"react-dom": "^18.2.0",
|
20 |
+
"react-scripts": "5.0.1",
|
21 |
+
"typescript": "^4.9.5",
|
22 |
+
"web-vitals": "^3.5.2"
|
23 |
+
},
|
24 |
+
"scripts": {
|
25 |
+
"start": "react-scripts start",
|
26 |
+
"build": "CI=true react-scripts build",
|
27 |
+
"test": "react-scripts test",
|
28 |
+
"eject": "react-scripts eject"
|
29 |
+
},
|
30 |
+
"eslintConfig": {
|
31 |
+
"extends": [
|
32 |
+
"react-app",
|
33 |
+
"react-app/jest"
|
34 |
+
]
|
35 |
+
},
|
36 |
+
"browserslist": {
|
37 |
+
"production": [
|
38 |
+
">0.2%",
|
39 |
+
"not dead",
|
40 |
+
"not op_mini all"
|
41 |
+
],
|
42 |
+
"development": [
|
43 |
+
"last 1 chrome version",
|
44 |
+
"last 1 firefox version",
|
45 |
+
"last 1 safari version"
|
46 |
+
]
|
47 |
+
}
|
48 |
+
}
|
frontend/public/index.html
ADDED
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="utf-8" />
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
6 |
+
<meta name="theme-color" content="#000000" />
|
7 |
+
<meta name="description" content="Simplify - Quiz Generator" />
|
8 |
+
<title>Simplify</title>
|
9 |
+
</head>
|
10 |
+
<body>
|
11 |
+
<noscript>You need to enable JavaScript to run this app.</noscript>
|
12 |
+
<div id="root"></div>
|
13 |
+
</body>
|
14 |
+
</html>
|
frontend/public/manifest.json
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"short_name": "Simplify",
|
3 |
+
"name": "Simplify Quiz Generator",
|
4 |
+
"start_url": ".",
|
5 |
+
"display": "standalone",
|
6 |
+
"theme_color": "#000000",
|
7 |
+
"background_color": "#ffffff"
|
8 |
+
}
|
frontend/public/robots.txt
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
User-agent: *
|
2 |
+
Disallow:
|
frontend/src/App.tsx
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Container, CssBaseline, ThemeProvider, createTheme } from '@mui/material';
|
2 |
+
import Header from './components/Header';
|
3 |
+
import DocumentInput from './components/DocumentInput';
|
4 |
+
import QuizGenerator from './components/QuizGenerator';
|
5 |
+
import ProblemList from './components/ProblemList';
|
6 |
+
import { useState } from 'react';
|
7 |
+
|
8 |
+
const theme = createTheme({
|
9 |
+
palette: {
|
10 |
+
primary: {
|
11 |
+
main: '#1976d2',
|
12 |
+
},
|
13 |
+
background: {
|
14 |
+
default: '#f5f5f5',
|
15 |
+
},
|
16 |
+
},
|
17 |
+
});
|
18 |
+
|
19 |
+
function App() {
|
20 |
+
const [problems, setProblems] = useState<string[]>([]);
|
21 |
+
|
22 |
+
return (
|
23 |
+
<ThemeProvider theme={theme}>
|
24 |
+
<CssBaseline />
|
25 |
+
<Container maxWidth="md" sx={{ py: 4 }}>
|
26 |
+
<Header />
|
27 |
+
<DocumentInput />
|
28 |
+
<QuizGenerator onProblemsGenerated={setProblems} />
|
29 |
+
<ProblemList problems={problems} />
|
30 |
+
</Container>
|
31 |
+
</ThemeProvider>
|
32 |
+
);
|
33 |
+
}
|
34 |
+
|
35 |
+
export default App;
|
frontend/src/components/DocumentInput.tsx
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { TextField, Button, Box } from '@mui/material';
|
2 |
+
import { useState } from 'react';
|
3 |
+
|
4 |
+
function DocumentInput() {
|
5 |
+
const [url, setUrl] = useState('');
|
6 |
+
|
7 |
+
const handleSubmit = async () => {
|
8 |
+
try {
|
9 |
+
const response = await fetch('http://localhost:8000/crawl/', {
|
10 |
+
method: 'POST',
|
11 |
+
headers: {
|
12 |
+
'Content-Type': 'application/json',
|
13 |
+
},
|
14 |
+
body: JSON.stringify({ url }),
|
15 |
+
});
|
16 |
+
if (!response.ok) throw new Error('Network response was not ok');
|
17 |
+
setUrl('');
|
18 |
+
} catch (error) {
|
19 |
+
console.error('Error:', error);
|
20 |
+
}
|
21 |
+
};
|
22 |
+
|
23 |
+
return (
|
24 |
+
<Box sx={{ mb: 4, display: 'flex', gap: 2 }}>
|
25 |
+
<TextField
|
26 |
+
fullWidth
|
27 |
+
label="Source Documentation"
|
28 |
+
value={url}
|
29 |
+
onChange={(e) => setUrl(e.target.value)}
|
30 |
+
/>
|
31 |
+
<Button variant="contained" onClick={handleSubmit}>
|
32 |
+
Pull Source Docs
|
33 |
+
</Button>
|
34 |
+
</Box>
|
35 |
+
);
|
36 |
+
}
|
37 |
+
|
38 |
+
export default DocumentInput;
|
frontend/src/components/Header.tsx
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Typography, Box } from '@mui/material';
|
2 |
+
|
3 |
+
function Header() {
|
4 |
+
return (
|
5 |
+
<Box sx={{ mb: 4 }}>
|
6 |
+
<Typography variant="h2" component="h1" align="center">
|
7 |
+
Simplify
|
8 |
+
</Typography>
|
9 |
+
</Box>
|
10 |
+
);
|
11 |
+
}
|
12 |
+
|
13 |
+
export default Header;
|
frontend/src/components/ProblemList.tsx
ADDED
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { List, ListItem, Paper, Typography } from '@mui/material';
|
2 |
+
|
3 |
+
interface ProblemListProps {
|
4 |
+
problems: string[];
|
5 |
+
}
|
6 |
+
|
7 |
+
function ProblemList({ problems }: ProblemListProps) {
|
8 |
+
if (problems.length === 0) return null;
|
9 |
+
|
10 |
+
return (
|
11 |
+
<Paper sx={{ p: 2 }}>
|
12 |
+
<Typography variant="h6" gutterBottom>
|
13 |
+
Generated Problems
|
14 |
+
</Typography>
|
15 |
+
<List>
|
16 |
+
{problems.map((problem, index) => (
|
17 |
+
<ListItem key={index}>
|
18 |
+
{index + 1}. {problem}
|
19 |
+
</ListItem>
|
20 |
+
))}
|
21 |
+
</List>
|
22 |
+
</Paper>
|
23 |
+
);
|
24 |
+
}
|
25 |
+
|
26 |
+
export default ProblemList;
|
frontend/src/components/QuizGenerator.tsx
ADDED
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { TextField, Button, Box } from '@mui/material';
|
2 |
+
import { useState } from 'react';
|
3 |
+
|
4 |
+
interface QuizGeneratorProps {
|
5 |
+
onProblemsGenerated: (problems: string[]) => void;
|
6 |
+
}
|
7 |
+
|
8 |
+
function QuizGenerator({ onProblemsGenerated }: QuizGeneratorProps) {
|
9 |
+
const [query, setQuery] = useState('');
|
10 |
+
|
11 |
+
const handleGenerate = async () => {
|
12 |
+
try {
|
13 |
+
const response = await fetch('http://localhost:8000/problems/', {
|
14 |
+
method: 'POST',
|
15 |
+
headers: {
|
16 |
+
'Content-Type': 'application/json',
|
17 |
+
},
|
18 |
+
body: JSON.stringify({ user_query: query }),
|
19 |
+
});
|
20 |
+
if (!response.ok) throw new Error('Network response was not ok');
|
21 |
+
const data = await response.json();
|
22 |
+
onProblemsGenerated(data.Problems);
|
23 |
+
setQuery('');
|
24 |
+
} catch (error) {
|
25 |
+
console.error('Error:', error);
|
26 |
+
}
|
27 |
+
};
|
28 |
+
|
29 |
+
return (
|
30 |
+
<Box sx={{ mb: 4, display: 'flex', gap: 2 }}>
|
31 |
+
<TextField
|
32 |
+
fullWidth
|
33 |
+
label="Quiz topic?"
|
34 |
+
value={query}
|
35 |
+
onChange={(e) => setQuery(e.target.value)}
|
36 |
+
/>
|
37 |
+
<Button variant="contained" onClick={handleGenerate}>
|
38 |
+
Generate
|
39 |
+
</Button>
|
40 |
+
</Box>
|
41 |
+
);
|
42 |
+
}
|
43 |
+
|
44 |
+
export default QuizGenerator;
|
frontend/src/index.js
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React from 'react';
|
2 |
+
import ReactDOM from 'react-dom/client';
|
3 |
+
import App from './App';
|
4 |
+
|
5 |
+
const root = ReactDOM.createRoot(
|
6 |
+
document.getElementById('root')
|
7 |
+
);
|
8 |
+
|
9 |
+
root.render(
|
10 |
+
<React.StrictMode>
|
11 |
+
<App />
|
12 |
+
</React.StrictMode>
|
13 |
+
);
|
frontend/src/tests/App.test.tsx
ADDED
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { render, screen } from '@testing-library/react';
|
2 |
+
import userEvent from '@testing-library/user-event';
|
3 |
+
import App from '../App';
|
4 |
+
|
5 |
+
describe('App', () => {
|
6 |
+
test('renders main components', () => {
|
7 |
+
render(<App />);
|
8 |
+
|
9 |
+
// Check for title
|
10 |
+
expect(screen.getByText('Simplify')).toBeInTheDocument();
|
11 |
+
|
12 |
+
// Check for input fields
|
13 |
+
expect(screen.getByLabelText('Source Documentation')).toBeInTheDocument();
|
14 |
+
expect(screen.getByLabelText('Quiz topic?')).toBeInTheDocument();
|
15 |
+
|
16 |
+
// Check for buttons
|
17 |
+
expect(screen.getByText('Pull Source Docs')).toBeInTheDocument();
|
18 |
+
expect(screen.getByText('Generate')).toBeInTheDocument();
|
19 |
+
});
|
20 |
+
});
|
frontend/src/tests/components/DocumentInput.test.tsx
ADDED
@@ -0,0 +1,30 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { render, screen, fireEvent } from '@testing-library/react';
|
2 |
+
import DocumentInput from '../../components/DocumentInput';
|
3 |
+
|
4 |
+
describe('DocumentInput', () => {
|
5 |
+
beforeEach(() => {
|
6 |
+
global.fetch = jest.fn();
|
7 |
+
});
|
8 |
+
|
9 |
+
test('submits URL when button is clicked', async () => {
|
10 |
+
const mockFetch = global.fetch as jest.Mock;
|
11 |
+
mockFetch.mockResolvedValueOnce({
|
12 |
+
ok: true,
|
13 |
+
json: async () => ({ status: 'received' }),
|
14 |
+
});
|
15 |
+
|
16 |
+
render(<DocumentInput />);
|
17 |
+
|
18 |
+
const input = screen.getByLabelText('Source Documentation');
|
19 |
+
const button = screen.getByText('Pull Source Docs');
|
20 |
+
|
21 |
+
await fireEvent.change(input, { target: { value: 'https://example.com' } });
|
22 |
+
await fireEvent.click(button);
|
23 |
+
|
24 |
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:8000/crawl/', {
|
25 |
+
method: 'POST',
|
26 |
+
headers: { 'Content-Type': 'application/json' },
|
27 |
+
body: JSON.stringify({ url: 'https://example.com' }),
|
28 |
+
});
|
29 |
+
});
|
30 |
+
});
|
frontend/src/tests/components/QuizGenerator.test.tsx
ADDED
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { render, screen, fireEvent } from '@testing-library/react';
|
2 |
+
import QuizGenerator from '../../components/QuizGenerator';
|
3 |
+
|
4 |
+
describe('QuizGenerator', () => {
|
5 |
+
const mockOnProblemsGenerated = jest.fn();
|
6 |
+
|
7 |
+
beforeEach(() => {
|
8 |
+
global.fetch = jest.fn();
|
9 |
+
mockOnProblemsGenerated.mockClear();
|
10 |
+
});
|
11 |
+
|
12 |
+
test('generates problems when button is clicked', async () => {
|
13 |
+
const mockProblems = ['Problem 1', 'Problem 2'];
|
14 |
+
const mockFetch = global.fetch as jest.Mock;
|
15 |
+
mockFetch.mockResolvedValueOnce({
|
16 |
+
ok: true,
|
17 |
+
json: async () => ({ Problems: mockProblems }),
|
18 |
+
});
|
19 |
+
|
20 |
+
render(<QuizGenerator onProblemsGenerated={mockOnProblemsGenerated} />);
|
21 |
+
|
22 |
+
const input = screen.getByLabelText('Quiz topic?');
|
23 |
+
const button = screen.getByText('Generate');
|
24 |
+
|
25 |
+
await fireEvent.change(input, { target: { value: 'React' } });
|
26 |
+
await fireEvent.click(button);
|
27 |
+
|
28 |
+
expect(mockFetch).toHaveBeenCalledWith('http://localhost:8000/problems/', {
|
29 |
+
method: 'POST',
|
30 |
+
headers: { 'Content-Type': 'application/json' },
|
31 |
+
body: JSON.stringify({ user_query: 'React' }),
|
32 |
+
});
|
33 |
+
expect(mockOnProblemsGenerated).toHaveBeenCalledWith(mockProblems);
|
34 |
+
});
|
35 |
+
});
|
frontend/tsconfig.json
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"compilerOptions": {
|
3 |
+
"target": "es5",
|
4 |
+
"lib": ["dom", "dom.iterable", "esnext"],
|
5 |
+
"allowJs": true,
|
6 |
+
"skipLibCheck": true,
|
7 |
+
"esModuleInterop": true,
|
8 |
+
"allowSyntheticDefaultImports": true,
|
9 |
+
"strict": true,
|
10 |
+
"forceConsistentCasingInFileNames": true,
|
11 |
+
"noFallthroughCasesInSwitch": true,
|
12 |
+
"module": "esnext",
|
13 |
+
"moduleResolution": "node",
|
14 |
+
"resolveJsonModule": true,
|
15 |
+
"isolatedModules": true,
|
16 |
+
"noEmit": true,
|
17 |
+
"jsx": "react-jsx",
|
18 |
+
"types": ["jest", "node"]
|
19 |
+
},
|
20 |
+
"include": ["src/**/*"],
|
21 |
+
"exclude": ["node_modules"]
|
22 |
+
}
|
pyproject.toml
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[project]
|
2 |
+
name = "simplify"
|
3 |
+
version = "0.1.0"
|
4 |
+
description = "LLM System to generate quizzes that simplify the learning process of tools and frameworks"
|
5 |
+
readme = "README.md"
|
6 |
+
requires-python = ">=3.12"
|
7 |
+
dependencies = [
|
8 |
+
"chainlit>=2.0.4",
|
9 |
+
"numpy>=2.2.2",
|
10 |
+
"openai>=1.59.9",
|
11 |
+
"pydantic==2.10.1",
|
12 |
+
"pypdf2>=3.0.1",
|
13 |
+
"websockets>=14.2",
|
14 |
+
"fastapi>=0.110.0",
|
15 |
+
"uvicorn>=0.27.1",
|
16 |
+
"pytest>=8.0.0",
|
17 |
+
"httpx>=0.26.0"
|
18 |
+
]
|
19 |
+
|
20 |
+
[tool.pytest.ini_options]
|
21 |
+
testpaths = ["backend/tests"]
|
22 |
+
python_files = ["test_*.py"]
|
pytest.ini
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
[pytest]
|
2 |
+
pythonpath = .
|