-
Notifications
You must be signed in to change notification settings - Fork 4
Design Documentation
| Version | Date | Description |
|---|---|---|
| V 1.0 | 2025-10-05 | Initial version of the design documentation. |
| V 1.1 | 2025-10-18 | Iteration 2: User Interface requirements update. |
| V 1.2 | 2025-11-02 | Iteration 3: User Interface requirements update. |
| V 1.3 | 2025-11-16 | Iteration 3: UI/UX and API revisions following Heuristic Evaluation. |
| V 1.4 | 2025-11-30 | Iteration 5: Unification of Diagram Design and Tone. |
| V 1.5 | 2025-12-07 | Buffer Iteration: Update Frontend Class Diagram. |
The project utilizes a Git flow-inspired branching model to ensure stable releases while allowing for parallel development of features.
%%{init: { 'logLevel': 'debug', 'theme': 'base', 'gitGraph': {'showBranches': true, 'showCommitLabel':false, 'mainBranchName': 'main'}} }%%
gitGraph
commit id: "Init"
branch dev
checkout dev
commit
branch dev-backend
branch dev-frontend
checkout dev-frontend
branch frontend/feat-login
commit
checkout dev-frontend
merge frontend/feat-login
checkout dev-backend
branch backend/feat-api
commit
checkout dev-backend
merge backend/feat-api
checkout dev
merge dev-frontend
merge dev-backend
checkout main
merge dev tag: "v1.0"
- Main: The production-ready branch. Merges here represent stable, versioned releases.
- Dev: The central integration branch. All feature branches are merged here for system-wide integration testing.
-
Integration Branches:
-
dev-frontend: Integration point for React Native/Expo features (Front directory). -
dev-backend: Integration point for FastAPI/Docker features (Backend directory).
-
-
Feature Branches: (Naming convention:
[domain]/feat-[name])- Temporary branches for individual tasks. Deleted after merging.
The system is deployed on an AWS EC2 instance hosting the API server and database. It leverages external AI services for content generation and Amazon S3 for media storage.
The testing and deployment process is automated using GitHub Actions.
For the CD pipeline, the workflow connects to the Amazon EC2 instance via SSH, pulls the latest code from GitHub, and builds a new Docker image. The updated image is then deployed as a FastAPI container, enabling seamless backend updates without manual intervention.
This CI/CD setup ensures continuous testing, integration, and automated deployment for the backend server hosted at 52.78.135.45:3000.
sequenceDiagram
autonumber
%% Participants
participant Dev as Developer
participant GH as GitHub Repo
participant GA as GitHub Actions
participant EC2 as AWS EC2
Note over Dev, GH: Trigger: Push to dev-backend
Dev->>GH: Push Code
GH->>GA: Trigger CI Workflow
%% CI Phase (Blue Tint - High Transparency for Dark Mode)
rect rgba(33, 150, 243, 0.15)
Note right of GA: CI Phase
GA->>GA: Run Unit Tests (pytest)
GA->>GA: Run Integration Tests
end
alt Tests Passed
%% CD Phase (Orange Tint)
rect rgba(255, 152, 0, 0.15)
GA->>EC2: SSH Connection
Note right of GA: CD Phase
EC2->>GH: Pull Latest Code
EC2->>EC2: Build New Docker Image
EC2->>EC2: Restart FastAPI Container
end
else Tests Failed
%% Failure (Red Tint)
rect rgba(244, 67, 54, 0.15)
GA-->>Dev: Notify Failure
end
end
The project adheres to the Model-View-Controller (MVC) architectural pattern.
The frontend is organized by feature-based screens which maintain local state and communicate with the backend via typed API clients.
classDiagram
%% Core app shell
class RootLayout {
+setupPlayerOnce()
+RootNavigation()
}
class RootNavigation {
+Stack.Protected authGuard
}
class QueryProvider {
+queryClient: QueryClient
}
class QueryClient
RootLayout --> QueryProvider
QueryProvider --> QueryClient
RootLayout --> RootNavigation
RootNavigation --> useUser
%% Networking & auth
class customFetch {
+getBaseUrl()
+refreshAccessToken()
+parseErrorResponse()
}
class tokenManager {
+setAccessToken()
+getAccessToken()
+saveRefreshToken()
+getRefreshToken()
+deleteRefreshToken()
}
customFetch --> tokenManager
customFetch --> QueryClient : clear cache on auth failure
%% API layer
class AuthAPI {
+signup()
+login()
+changePassword()
+deleteAccount()
}
class UserAPI {
+getMe()
+updateInterests()
}
class AudioAPI { +generateAudio() }
class AudioHistoryAPI { +getAudioHistory() }
class VocabAPI {
+getVocab()
+addVocab()
+getMyVocab()
+deleteMyVocab()
}
class StatsAPI { +getStats() }
class FeedbackAPI { +submitFeedback() }
class InitialSurveyAPI {
+submitLevelTest()
+submitManualLevel()
+mapLevelIdToCEFR()
}
AuthAPI --> customFetch
UserAPI --> customFetch
AudioAPI --> customFetch
AudioHistoryAPI --> customFetch
VocabAPI --> customFetch
StatsAPI --> customFetch
FeedbackAPI --> customFetch
InitialSurveyAPI --> customFetch
%% Hooks
class useSignup
class useLogin
class useLogout
class useChangePassword
class useDeleteAccount
useSignup --> AuthAPI
useSignup --> tokenManager
useSignup --> QueryClient
useLogin --> AuthAPI
useLogin --> UserAPI
useLogin --> tokenManager
useLogout --> tokenManager
useLogout --> QueryClient
useChangePassword --> AuthAPI
useDeleteAccount --> AuthAPI
class useUser
useUser --> UserAPI
class useGenerateAudio
class useAudioHistory
class useAddVocab
class useVocab
class useMyVocab
class useDeleteMyVocab
class useStats
class useSubmitLevelTest
class useSubmitManualLevel
class useUpdateInterests
useGenerateAudio --> AudioAPI
useAudioHistory --> AudioHistoryAPI
useAddVocab --> VocabAPI
useVocab --> VocabAPI
useMyVocab --> VocabAPI
useDeleteMyVocab --> VocabAPI
useStats --> StatsAPI
useSubmitLevelTest --> InitialSurveyAPI
useSubmitManualLevel --> InitialSurveyAPI
useUpdateInterests --> UserAPI
%% Screens
class HomeScreen
class HistoryScreen
class AudioPlayerScreen
class FeedbackScreen
class LevelResultScreen
class InitialSurveyScreen
class WalkthroughScreen
class AuthScreens
HomeScreen --> useGenerateAudio
HomeScreen --> useUser
HistoryScreen --> useAudioHistory
AudioPlayerScreen --> useAddVocab
AudioPlayerScreen --> useVocab
AudioPlayerScreen --> FeedbackScreen : route params
FeedbackScreen --> FeedbackAPI
LevelResultScreen --> FeedbackScreen : params -> results
InitialSurveyScreen --> useSubmitLevelTest
InitialSurveyScreen --> useSubmitManualLevel
AuthScreens --> useSignup
AuthScreens --> useLogin
The backend logic is structured around domain-specific services that handle business logic and external API orchestration.
classDiagram
direction TB
%% --- Style Definitions ---
classDef serviceNode fill:#ffffff,stroke:#333,stroke-width:2px,color:#000
classDef externalNode fill:#FFF3E0,stroke:#FB8C00,stroke-width:2px,stroke-dasharray: 5 5,color:#000
%% --- External APIs ---
class OpenAI:::externalNode {
+chat.completions.create()
}
class ElevenLabs:::externalNode {
+text_to_speech.convert()
}
class S3:::externalNode {
+upload_fileobj()
+generate_presigned_url()
}
%% --- Core Services ---
class AudioService:::serviceNode {
+generate_full_audio_with_timestamps()
+generate_full_audio_streaming()
+list_user_audio_history()
-select_voice_algorithmically()
}
class VocabService:::serviceNode {
+build_contextual_vocab()
+process_sentence_async()
-extract_keywords()
}
class LevelManagementService:::serviceNode {
+evaluate_initial_level()
+evaluate_session_feedback()
+set_manual_level()
}
class LevelSystemService:::serviceNode {
+initialize_level()
+calculate_level_deltas()
-apply_constraints()
}
class StatsService:::serviceNode {
+get_user_stats()
+update_streaks()
+calculate_weekly_minutes()
}
%% --- Relationships ---
AudioService ..> VocabService : uses
AudioService ..> OpenAI : Script Gen
AudioService ..> ElevenLabs : TTS Gen
AudioService ..> S3 : Storage
VocabService ..> OpenAI : Context Extraction
LevelManagementService ..> LevelSystemService : delegates calculation
LevelManagementService ..> OpenAI : Rationale Eval
The database schema centers around the users table, which serves as the primary foreign key reference for learning content, history, and achievements.
erDiagram
%% Entities
users {
int id PK
string username
float lexical_level
float syntactic_level
float speed_level
}
generated_contents {
int generated_content_id PK
int user_id FK
string audio_url
json script_data
}
user_interests {
int id PK
int user_id FK
string interest_code
}
user_level_history {
int id PK
int user_id FK
timestamp created_at
json level_snapshot
}
study_sessions {
int id PK
int user_id FK
int duration_seconds
timestamp created_at
}
vocab_entries {
int id PK
int user_id FK
string word
string context_sentence
}
achievements {
string code PK
string name
string description
}
user_achievements {
int id PK
int user_id FK
string achievement_code FK
}
level_test_scripts {
int id PK
string content
string target_level
}
%% Relationships
users ||--o{ generated_contents : "creates"
users ||--o{ study_sessions : "logs"
users ||--o{ user_level_history : "tracks"
users ||--o{ vocab_entries : "saves"
users ||--o{ user_interests : "selects"
users ||--o{ user_achievements : "earns"
achievements ||--o{ user_achievements : "defines"
| Entity | Description |
|---|---|
| User | The central entity storing account credentials and current CEFR proficiency levels (Lexical, Syntactic, Speed). |
| GeneratedContent | Stores metadata for AI-generated podcasts, including S3 audio URLs and the raw script JSON. |
| StudySession | specific learning session, tracking duration and completion status for statistics. |
| UserLevelHistory | A historical log of proficiency changes, used to visualize progress over time. |
| VocabEntry | User-specific vocabulary words saved during listening sessions, including the context sentence where the word appeared. |
| Achievement | Static definitions of available badges/milestones (e.g., "7 Day Streak"). |
| LevelTestScript | Pre-defined scripts used specifically for the initial placement test. |
The following sequence details the lifecycle of an audio generation request, from the client's initial prompt to the final MP3 synthesis.
sequenceDiagram
autonumber
%% Participants
participant Client
participant Server as Audio Service
participant Config as voices.json
participant OpenAI as OpenAI API
participant Eleven as ElevenLabs API
participant S3 as Storage
%% Flow
Client->>Server: POST /audio/generate (token, mood, theme)
activate Server
Note right of Server: 1. Voice Selection
Server->>Config: load_voices()
Config-->>Server: All Voices List
Server->>Server: select_voice(user.level)
Note right of Server: 2. Script Generation
Server->>OpenAI: generate_script(mood, theme, user_level)
activate OpenAI
OpenAI-->>Server: Generated Script (title, text)
deactivate OpenAI
Note right of Server: 3. Audio Synthesis
Server->>Eleven: convert_with_timestamps(script, voice_id)
activate Eleven
Eleven-->>Server: Audio Stream (Base64) + Alignment
deactivate Eleven
Note right of Server: 4. Processing
Server->>Server: parse_tts_by_newlines()
Server->>S3: Save .mp3 file
activate S3
S3-->>Server: File URL
deactivate S3
Server-->>Client: 200 OK (JSON with Sentences & URL)
deactivate Server
%% Styling
rect rgba(243, 229, 245, 0.4)
Note over Client, S3: Audio Generation Context
end
The backend exposes a RESTful API built with FastAPI. All endpoints are prefixed with /api/v1.
POST /api/v1/auth/signup - Register new user
-
Request Body:
{ "username": "user123", "password": "securePassword1!" } -
Response Body (200 OK):
{ "access_token": "eyJhbG...", "refresh_token": "eyJhbG...", "token_type": "bearer" }
POST /api/v1/auth/login - User login
-
Request Body:
{ "username": "user123", "password": "securePassword1!" } -
Response Body (200 OK):
{ "access_token": "eyJhbG...", "refresh_token": "eyJhbG...", "token_type": "bearer" }
POST /api/v1/auth/change-password - Change Password
-
Request Headers:
Authorization: Bearer {token} -
Request Body:
{ "current_password": "oldPassword", "new_password": "newPassword" } -
Response Body (200 OK):
{ "message": "Password updated successfully" }
POST /api/v1/auth/refresh/access - Refresh Access Token
-
Request Body:
{ "refresh_token": "eyJhbG..." } -
Response Body (200 OK):
{ "access_token": "eyJhbG...", "token_type": "bearer" }
DELETE /api/v1/auth/delete-account - Delete Account
-
Request Headers:
Authorization: Bearer {token} -
Response Body (200 OK):
{ "message": "Account deleted successfully." }
GET /api/v1/user/me - Get My Profile
-
Request Headers:
Authorization: Bearer {token} -
Response Body (200 OK):
{ "id": 1, "username": "user123", "lexical_level": 50.5, "syntactic_level": 40.0, "speed_level": 60.0 }
PUT /api/v1/user/me/interests - Update Interests
-
Request Headers:
Authorization: Bearer {token} -
Request Body:
{ "interests": ["Technology", "Travel"] } -
Response Body (200 OK):
{ "success": true }
POST /api/v1/audio/generate - Generate Audio Content
-
Request Headers:
Authorization: Bearer {token} -
Request Body:
{ "style": "podcast", "theme": "sports" } -
Response Body (200 OK):
{ "generated_content_id": 101, "title": "A Trip to Seoul", "audio_url": "[https://s3.aws.com/](https://s3.aws.com/)...", "sentences": [ { "id": 1, "start_time": 0.0, "text": "Hello there." } ] }
GET /api/v1/audio/content/{id} - Get Audio Content By ID
-
Request Headers:
Authorization: Bearer {token} -
Response Body (200 OK):
{ "generated_content_id": 101, "title": "A Trip to Seoul", "audio_url": "[https://s3.aws.com/](https://s3.aws.com/)...", "sentences": [ { "id": 1, "start_time": 0.0, "text": "Hello there." } ] }
GET /api/v1/audio/history - Get User History
-
Request Headers:
Authorization: Bearer {token} -
Response Body (200 OK):
[ { "generated_content_id": 101, "title": "A Trip to Seoul", "created_at": "2025-10-10T10:00:00" } ]
GET /api/v1/audio/files/{filename} - Serve Audio File
- Summary: Directly serves the static MP3 file from storage (used by the player).
GET /api/v1/vocabs/me - Get My Vocabulary
-
Request Headers:
Authorization: Bearer {token} -
Response Body (200 OK):
[ { "id": 5, "word": "Serendipity", "meaning": "Finding something good without looking for it.", "example_sentence": "It was pure serendipity..." } ]
GET /api/v1/vocabs/{id} - Get Vocabs from Script
-
Request Headers:
Authorization: Bearer {token} -
Response Body (200 OK):
["serendipity", "tranquility", "ephemeral"]
GET /api/v1/vocabs/{id}/sentences/{idx} - Get Contextual Word
-
Query Params:
?word=target -
Response Body (200 OK):
{ "word": "target", "part_of_speech": "noun", "meaning": "The definition in this specific context." }
POST /api/v1/vocabs/{id}/sentences/{idx} - Save Word
-
Request Body:
{ "word": "target" } -
Response Body (201 Created):
{ "success": true }
DELETE /api/v1/vocabs/me/{entry_id} - Delete Vocab Entry
-
Request Headers:
Authorization: Bearer {token} -
Response Body (200 OK):
{ "success": true }
POST /api/v1/level-system/level-test - Evaluate Level Test
-
Request Body:
{ "tests": [ { "script_id": "A1_01", "understanding": 80 } ] } -
Response Body (200 OK):
{ "lexical_level": 30.0, "syntactic_level": 25.0, "speed_level": 40.0 }
POST /api/v1/level-system/session-feedback - Submit Feedback
-
Request Body:
{ "generated_content_id": 101, "pause_cnt": 2, "rewind_cnt": 1, "vocab_lookup_cnt": 3, "vocab_save_cnt": 1, "understanding_difficulty": 3, "speed_difficulty": 2 } -
Response Body (200 OK):
{ "lexical_level_delta": +1.5, "syntactic_level_delta": +1.0, "speed_level_delta": -1.0 }
POST /api/v1/level-system/manual-level - Set Manual Level
-
Request Body:
{ "level": "B1" } -
Response Body (200 OK):
{ "success": true }
GET /api/v1/initial-survey/{level}/{number} - Get Survey Audio
- Summary: Retrieves the static audio file for the initial placement test questions.
GET /api/v1/stats - Get User Statistics
-
Request Headers:
Authorization: Bearer {token} -
Response Body (200 OK):
{ "streak": { "current": 5, "best": 12 }, "total_minutes": 120, "level_history": [...] }
The API returns errors in a consistent JSON format to ensure predictable client handling.
Standard Error Format:
{
"status_code": 400,
"custom_code": "USERNAME_EXISTS",
"detail": "The username you provided is already taken."
}Global Error Codes:
| Status | Custom Code | Detail |
|---|---|---|
| 400 | USERNAME_EXISTS |
Username already exists. |
| 401 | INVALID_CREDENTIALS |
Invalid username or password. |
| 401 | AUTH_TOKEN_EXPIRED |
Authentication token has expired. |
| 401 | INVALID_TOKEN |
Token is invalid. |
| 401 | INVALID_TOKEN_TYPE |
Invalid token type. |
| 401 | INVALID_AUTH_HEADER |
Invalid authorization header. |
| 404 | USER_NOT_FOUND |
The requested user does not exist. |
| 404 | GENERATED_CONTENT_NOT_FOUND |
The requested generated content does not exist. |
| 404 | LEVEL_TEST_SCRIPT_NOT_FOUND |
The requested level test script does not exist. |
| 404 | SENTENCE_INDEX_OUT_OF_RANGE |
The sentence index is out of range. |
| 404 | WORD_NOT_FOUND_IN_SENTENCE |
The word was not found in the sentence. |
| 404 | NO_VOCAB_ENTRIES_FOUND |
No vocabulary entries found for user. |
| 422 | INVALID_USERNAME_FORMAT |
Username must be 6~16 characters long and contain only letters and numbers. |
| 422 | INVALID_PASSWORD_FORMAT |
Password must be 8~32 characters long and include at least one letter and one number. |
| 422 | WORD_ALREADY_EXISTS |
Word already exists in vocabulary list. |
| 500 | ACCOUNT_DELETION_FAILED |
Server failed to delete account. |
| 500 | VOCAB_SAVE_FAILED |
Failed to save vocabulary entry. |
| 500 | DATABASE_CONNECTION_ERROR |
Database connection error occurred. |
| 503 | SCRIPT_GENERATION_FAILED |
Failed to generate script from external API. |
| 503 | TTS_GENERATION_FAILED |
Failed to generate audio from external API. |
| 503 | OPEN_AI_API_ERROR |
OpenAI API error occurred. |
The project employs a comprehensive testing strategy that separates concerns between isolated Unit Tests and system-wide Integration Tests. This separation ensures individual components function correctly while verifying that the entire stack (Frontend
flowchart LR
%% Definitions
subgraph Trigger [Trigger]
PR["Pull Request / Push"]
end
subgraph Test_Suite [Test Execution Suite]
direction TB
Unit["Unit Tests\n(Isolated Logic)"]
Integration["Integration Tests\n(API & DB Flows)"]
E2E["Acceptance Tests\n(User Stories)"]
end
subgraph Reporting [Analysis & Reporting]
Coverage{Codecov Analysis}
Pass[Merge Allowed]
Fail[Block Merge]
end
%% Flow
PR --> Unit
Unit --> Integration
Integration --> E2E
E2E --> Coverage
Coverage -- "Coverage > 80%" --> Pass
Coverage -- "Coverage < 80%" --> Fail
%% Styling
classDef process fill:#ffffff,stroke:#333,stroke-width:2px,color:#000
classDef success fill:#E8F5E9,stroke:#2E7D32,stroke-width:2px,color:#000
classDef fail fill:#FFEBEE,stroke:#C62828,stroke-width:2px,color:#000
class PR,Unit,Integration,E2E,Coverage process
class Pass success
class Fail fail
A split-layer testing strategy is adopted using pytest. The test suite is physically separated into two distinct directories to enforce separation of concerns:
-
Unit Tests (
tests/unit/):- Focus: Verification of individual functions and classes in isolation.
- Constraint: These tests do not interact with the database, external APIs (OpenAI/ElevenLabs), or the file system. All external dependencies are mocked.
- Goal: Immediate feedback on business logic correctness (e.g., token validation logic, text parsing regex).
-
Integration Tests (
tests/integration/):- Focus: Verification of complete workflows and component interaction.
- Scope: These tests instantiate a test database container, execute real HTTP requests against the FastAPI endpoints, and validate the resulting state changes in the database.
- Goal: Validation of the API contract and correct interoperability between services (Auth, DB, S3).
| Tool | Role | Usage |
|---|---|---|
| pytest | Test Runner | The primary framework for discovering and executing tests. |
| pytest-asyncio | Async Support | Essential for testing FastAPI's asynchronous async def endpoints. |
| httpx | HTTP Client | Used to simulate real user requests (GET, POST) against the API during integration tests. |
| Codecov | Reporting | Visualizes coverage data in PRs to prevent coverage regression. |
The project has achieved the 90% coverage target for the backend. Key core modules such as auth, audio, and stats maintain near-perfect coverage. Below is the latest report.
The frontend employs a layered testing strategy organized by test type and co-located with source files. Tests are structured to validate user-facing behavior while maintaining confidence in implementation details.
-
Unit Tests (co-located in tests/ directories):
- Focus: Verification of individual functions, utilities, and custom hooks in isolation.
- Constraint: All external dependencies (APIs, storage, native modules) are mocked.
- Goal: Fast feedback on business logic correctness (e.g., token management, validation logic, utility functions).
-
Component Tests (co-located in tests/ directories):
- Focus: UI rendering and User Interaction.
- Method: React Testing Library is used to query elements by accessibility roles (e.g., getByRole('button')) rather than implementation details (IDs or class names). This ensures resilience against refactoring.
- Scenarios: Verification of loading states, error message rendering, and button press handlers.
-
Integration Flows (tests/integration/):
- Focus: Complete user workflows and cross-component interaction.
- Scope: Tests render entire screens with necessary providers and validate end-to-end feature correctness.
- Goal: Validation of API contract and cross-component integration.
The frontend has 9 integration test suites located in frontend/__tests__/integration/.
| No. | Test Suite File | Description | Key Features Tested |
|---|---|---|---|
| 1 |
Audio History (audioHistory.integration.test.tsx) |
Tests the audio playback history functionality. | * Loading state display * Successful audio history list rendering * Empty state when no history exists |
| 2 |
Authentication Routing (auth-routing.integration.test.tsx) |
Tests navigation logic based on user authentication state. | * Redirect to initial survey when user hasn't completed initial level test * No redirect when initial level test is completed * No redirect for unauthenticated users * No redirect during loading state * Dynamic redirect after user data loads |
| 3 |
Feedback (feedback.integration.test.tsx) |
Tests the post-session feedback screen. | * Title and section display * Understanding difficulty level options (매우 낮음 to 매우 높음) * Submit button rendering * Speed options appearing after selecting understanding level |
| 4 |
Initial Survey (initialSurvey.integration.test.tsx) |
Tests the initial level assessment flow. | * Survey start and level selection (A1-C2) * Navigation between survey steps (back button) * Validation preventing navigation without level selection |
| 5 |
Login (login.integration.test.tsx) |
Tests user authentication. | * Successful login with token storage and navigation to home * Error display on invalid credentials * Input validation before submission * Form disabled state during mutation * Navigation to signup screen |
| 6 |
Profile (profile.integration.test.tsx) |
Tests the user profile screen. | * Loading state * Display of logout button, account deletion, privacy, help, and about options |
| 7 |
Signup (signup.integration.test.tsx) |
Tests user registration. | * Successful signup and navigation to login * Password mismatch validation * Required field validation * API error handling * Navigation back to login * Incomplete form button disabling |
| 8 |
Stats (stats.integration.test.tsx) |
Tests the statistics/progress screen. | * Loading state * Level information display (어휘력, 문법) * Streak information (연속 학습일, 누적 학습일) * Total study time * Weekly activity chart with days of week * Achievements/badges section * Error state handling |
| 9 |
Vocabulary (vocab.integration.test.tsx) |
Tests the vocabulary list feature. | * Loading state * Successful vocab list display * Empty state messaging * Error state with retry functionality * Vocab details display (word, meaning, example sentences) |
| Tool | Role | Usage |
|---|---|---|
| Jest | Test Runner | The standard JavaScript testing framework, configured with jest-expo. |
| React Testing Library | Component Testing | Provides helpers to render components and simulate user events (press, type). |
| @testing-library/jest-native | Native Matchers | Adds custom matchers for React Native (e.g., toHaveTextContent, toBeDisabled). |
| @tanstack/react-query | State Management Testing | QueryClient instances are created per test with retry: false for deterministic behavior. |
The project has achieved 96% overall coverage, exceeding the 80% target set in project requirements:

# Run all tests
npm test
# Generate coverage report
npm run test:coverage
# Run unit tests only (excludes integration)
npm run test:unit
# Run integration tests only
npm run test:integrationCI/CD Integration: Tests run automatically on pull requests via GitHub Actions with coverage validation.
Acceptance tests act as the final quality gate. These validate that the system satisfies the core business requirements defined in the Product Requirements Document (PRD).
| Feature ID | User Story | Acceptance Criteria |
|---|---|---|
| AT-01 | Personalized Recommendations | User receives audio content matching their exact Interest tags. |
| AT-02 | History Tracking | Completed sessions appear in the "History" tab with correct timestamps. |
| AT-03 | Vocabulary Extraction | Tapping a word in the player adds it to the Vocab list with context. |
| AT-04 | Daily Suggestions | App suggests new content every 24 hours based on user level. |
| AT-05 | Learning Dashboard | Statistics page updates "Minutes Listened" immediately after a session. |