Initial commit: Complete NodeJS-native setup
- Migrated from Python pre-commit to NodeJS-native solution - Reorganized documentation structure - Set up Husky + lint-staged for efficient pre-commit hooks - Fixed Dockerfile healthcheck issue - Added comprehensive documentation index
This commit is contained in:
+171
@@ -0,0 +1,171 @@
|
||||
# 🧪 Testing Documentation
|
||||
|
||||
## Test Structure
|
||||
|
||||
```
|
||||
tests/
|
||||
├── setup.ts # Jest configuration and global test setup
|
||||
├── integration/ # Integration tests for production validation
|
||||
│ └── production.test.js # CouchDB, deployment, and service testing
|
||||
├── manual/ # Manual testing scripts and debugging tools
|
||||
│ ├── admin-login-debug.js # Browser console debugging for admin login
|
||||
│ ├── auth-db-debug.js # Authentication database debugging
|
||||
│ └── debug-email-validation.js # Email validation debugging
|
||||
└── e2e/ # End-to-end tests with Playwright
|
||||
├── README.md # E2E testing documentation
|
||||
├── fixtures.ts # Custom test fixtures
|
||||
├── helpers.ts # Test utilities and data
|
||||
├── auth.spec.ts # Authentication flow tests
|
||||
├── medication.spec.ts # Medication management tests
|
||||
├── admin.spec.ts # Admin interface tests
|
||||
├── ui-navigation.spec.ts # UI and navigation tests
|
||||
└── reminders.spec.ts # Reminder system tests
|
||||
|
||||
services/
|
||||
└── auth/
|
||||
└── __tests__/ # Unit tests for authentication services
|
||||
├── auth.integration.test.ts
|
||||
└── emailVerification.test.ts
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Unit Tests (Jest)
|
||||
|
||||
```bash
|
||||
# Run all unit tests
|
||||
bun run test
|
||||
|
||||
# Run tests in watch mode
|
||||
bun run test:watch
|
||||
|
||||
# Run with coverage
|
||||
bun run test:coverage
|
||||
|
||||
# Run specific test file
|
||||
bun run test auth.integration.test.ts
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```bash
|
||||
# Run production integration tests
|
||||
bun run test:integration
|
||||
|
||||
# Run all tests (unit + integration + e2e)
|
||||
bun run test:all
|
||||
|
||||
# Run E2E tests with Playwright
|
||||
bun run test:e2e
|
||||
|
||||
# Run E2E tests in UI mode
|
||||
bun run test:e2e:ui
|
||||
|
||||
# Debug E2E tests
|
||||
bun run test:e2e:debug
|
||||
|
||||
# View E2E test reports
|
||||
bun run test:e2e:report
|
||||
```
|
||||
|
||||
### Manual Testing Scripts
|
||||
|
||||
#### Admin Login Debug
|
||||
|
||||
```bash
|
||||
# Open browser to http://localhost:8080
|
||||
# Open developer console
|
||||
# Run:
|
||||
bun tests/manual/admin-login-debug.js
|
||||
```
|
||||
|
||||
#### Auth Database Debug
|
||||
|
||||
```bash
|
||||
# Open browser to http://localhost:8080
|
||||
# Open developer console
|
||||
# Run:
|
||||
bun tests/manual/auth-db-debug.js
|
||||
```
|
||||
|
||||
## Test Categories
|
||||
|
||||
### ✅ Unit Tests (`services/auth/__tests__/`)
|
||||
|
||||
- **Purpose**: Test individual functions and services in isolation
|
||||
- **Framework**: Jest + TypeScript
|
||||
- **Coverage**: Authentication, email verification
|
||||
- **Status**: ✅ Well-structured and maintained
|
||||
|
||||
### 🔧 Integration Tests (`tests/integration/`)
|
||||
|
||||
- **Purpose**: Test entire system interactions and deployment validation
|
||||
- **Framework**: Bun native testing
|
||||
- **Coverage**: CouchDB connectivity, database setup, production readiness
|
||||
- **Status**: ✅ Useful for deployment validation
|
||||
|
||||
### 🛠️ Manual Tests (`tests/manual/`)
|
||||
|
||||
- **Purpose**: Browser-based debugging and manual verification
|
||||
- **Framework**: Vanilla JavaScript for browser console
|
||||
- **Coverage**: Admin authentication, database debugging
|
||||
- **Status**: ⚠️ Useful for debugging but should be automated
|
||||
|
||||
### 🎯 E2E Tests (`tests/e2e/`)
|
||||
|
||||
- **Purpose**: Full user journey testing across browsers
|
||||
- **Framework**: Playwright with TypeScript
|
||||
- **Coverage**: Complete user workflows, cross-browser compatibility
|
||||
- **Status**: ✅ Comprehensive test suite with 5 spec files
|
||||
|
||||
## Test Configuration
|
||||
|
||||
### Jest Configuration (`jest.config.json`)
|
||||
|
||||
- TypeScript support with ts-jest
|
||||
- jsdom environment for DOM testing
|
||||
- Coverage reporting
|
||||
- Module path mapping
|
||||
|
||||
### Test Setup (`tests/setup.ts`)
|
||||
|
||||
- localStorage mocking
|
||||
- fetch mocking
|
||||
- Console noise reduction
|
||||
- Global test utilities
|
||||
|
||||
## Recommendations
|
||||
|
||||
### ✅ Keep These Tests
|
||||
|
||||
1. **Authentication unit tests** - Critical for security
|
||||
2. **Production integration tests** - Essential for deployment validation
|
||||
3. **Manual debugging scripts** - Useful for development
|
||||
|
||||
### 🔄 Future Improvements
|
||||
|
||||
1. **Remove temporary type declarations** once Playwright types are fully recognized
|
||||
2. **Increase unit test coverage** for components and utilities
|
||||
3. **Add visual regression tests** using Playwright screenshots
|
||||
4. **Implement accessibility testing** in E2E suite
|
||||
5. **Add performance tests** for large datasets
|
||||
6. **Set up test data management** for E2E tests
|
||||
|
||||
### 🛡️ Testing Best Practices
|
||||
|
||||
- Run tests before every deployment
|
||||
- Maintain >80% code coverage for critical paths
|
||||
- Use integration tests to validate environment setup
|
||||
- Keep manual tests for complex debugging scenarios
|
||||
|
||||
## CI/CD Integration
|
||||
|
||||
Add to your deployment pipeline:
|
||||
|
||||
```bash
|
||||
# Validate tests before deployment
|
||||
bun run test:all
|
||||
|
||||
# Run in production validation
|
||||
bun run test:integration
|
||||
```
|
||||
@@ -0,0 +1,319 @@
|
||||
# 🎭 End-to-End Testing with Playwright
|
||||
|
||||
## Overview
|
||||
|
||||
This directory contains comprehensive end-to-end tests for the Medication Reminder App using Playwright. These tests simulate real user interactions across different browsers and devices.
|
||||
|
||||
## Test Structure
|
||||
|
||||
```
|
||||
tests/e2e/
|
||||
├── README.md # This documentation
|
||||
├── fixtures.ts # Custom test fixtures and utilities
|
||||
├── helpers.ts # Test helper functions and data
|
||||
├── auth.spec.ts # Authentication flow tests
|
||||
├── medication.spec.ts # Medication management tests
|
||||
├── admin.spec.ts # Admin interface tests
|
||||
├── ui-navigation.spec.ts # UI and navigation tests
|
||||
└── reminders.spec.ts # Reminder system tests
|
||||
```
|
||||
|
||||
## Test Categories
|
||||
|
||||
### 🔐 **Authentication Tests** (`auth.spec.ts`)
|
||||
|
||||
- User registration and login
|
||||
- Admin authentication
|
||||
- OAuth button visibility
|
||||
- Invalid credential handling
|
||||
- Session management
|
||||
|
||||
### 💊 **Medication Management** (`medication.spec.ts`)
|
||||
|
||||
- Adding new medications
|
||||
- Editing existing medications
|
||||
- Deleting medications
|
||||
- Marking doses as taken
|
||||
- Viewing medication history
|
||||
|
||||
### 👑 **Admin Interface** (`admin.spec.ts`)
|
||||
|
||||
- User management operations
|
||||
- Password changes
|
||||
- User status toggles
|
||||
- Admin permissions
|
||||
|
||||
### 🎨 **UI & Navigation** (`ui-navigation.spec.ts`)
|
||||
|
||||
- Theme switching
|
||||
- Modal interactions
|
||||
- Responsive design
|
||||
- Search functionality
|
||||
- Account management
|
||||
|
||||
### ⏰ **Reminder System** (`reminders.spec.ts`)
|
||||
|
||||
- Custom reminder creation
|
||||
- Reminder editing and deletion
|
||||
- Scheduled dose display
|
||||
- Missed dose handling
|
||||
|
||||
## Setup and Installation
|
||||
|
||||
### 1. Install Playwright
|
||||
|
||||
```bash
|
||||
# Install Playwright and browsers
|
||||
npm install -D @playwright/test
|
||||
npx playwright install
|
||||
```
|
||||
|
||||
### 2. Update Package.json
|
||||
|
||||
```json
|
||||
{
|
||||
"scripts": {
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:debug": "playwright test --debug",
|
||||
"test:e2e:report": "playwright show-report"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Running Tests
|
||||
|
||||
### Basic Test Execution
|
||||
|
||||
```bash
|
||||
# Run all E2E tests
|
||||
npm run test:e2e
|
||||
|
||||
# Run tests in UI mode (interactive)
|
||||
npm run test:e2e:ui
|
||||
|
||||
# Run specific test file
|
||||
npx playwright test auth.spec.ts
|
||||
|
||||
# Run tests in debug mode
|
||||
npm run test:e2e:debug
|
||||
```
|
||||
|
||||
### Browser-Specific Testing
|
||||
|
||||
```bash
|
||||
# Run on specific browser
|
||||
npx playwright test --project=chromium
|
||||
npx playwright test --project=firefox
|
||||
npx playwright test --project=webkit
|
||||
|
||||
# Run on mobile browsers
|
||||
npx playwright test --project="Mobile Chrome"
|
||||
npx playwright test --project="Mobile Safari"
|
||||
```
|
||||
|
||||
### Test Reporting
|
||||
|
||||
```bash
|
||||
# Generate and view HTML report
|
||||
npm run test:e2e:report
|
||||
|
||||
# Run with specific reporter
|
||||
npx playwright test --reporter=line
|
||||
npx playwright test --reporter=json
|
||||
```
|
||||
|
||||
## Test Configuration
|
||||
|
||||
The tests are configured via `playwright.config.ts`:
|
||||
|
||||
- **Base URL**: `http://localhost:8080`
|
||||
- **Auto-start**: Docker Compose before tests
|
||||
- **Browsers**: Chrome, Firefox, Safari, Mobile Chrome, Mobile Safari
|
||||
- **Retries**: 2 on CI, 0 locally
|
||||
- **Screenshots**: On failure
|
||||
- **Videos**: On failure
|
||||
- **Traces**: On retry
|
||||
|
||||
## Test Data and Fixtures
|
||||
|
||||
### Custom Fixtures (`fixtures.ts`)
|
||||
|
||||
- `adminPage`: Auto-login as admin user
|
||||
- `userPage`: Auto-login as regular user
|
||||
|
||||
### Helper Functions (`helpers.ts`)
|
||||
|
||||
- `MedicationHelpers`: Medication CRUD operations
|
||||
- `AuthHelpers`: Authentication actions
|
||||
- `ModalHelpers`: Modal interactions
|
||||
- `WaitHelpers`: Wait utilities
|
||||
- `TestData`: Pre-defined test data
|
||||
|
||||
### Example Usage
|
||||
|
||||
```typescript
|
||||
import { test } from './fixtures';
|
||||
import { MedicationHelpers, TestData } from './helpers';
|
||||
|
||||
test('should add medication', async ({ adminPage }) => {
|
||||
const medicationHelper = new MedicationHelpers(adminPage);
|
||||
const testMed = TestData.medications[0];
|
||||
|
||||
await medicationHelper.addMedication(testMed.name, testMed.dosage, testMed.frequency);
|
||||
});
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
### ✅ Test Organization
|
||||
|
||||
- Group related tests in describe blocks
|
||||
- Use descriptive test names
|
||||
- Keep tests independent and isolated
|
||||
|
||||
### ✅ Selectors
|
||||
|
||||
- Use data-testid attributes for reliable targeting
|
||||
- Prefer semantic selectors (role, text content)
|
||||
- Avoid CSS selectors that may change
|
||||
|
||||
### ✅ Waiting Strategies
|
||||
|
||||
- Use `waitForSelector()` for dynamic content
|
||||
- Leverage auto-waiting for most actions
|
||||
- Add explicit waits for complex interactions
|
||||
|
||||
### ✅ Test Data
|
||||
|
||||
- Use helper functions for common operations
|
||||
- Keep test data in centralized location
|
||||
- Clean up test data after tests
|
||||
|
||||
## Debugging Tests
|
||||
|
||||
### Local Debugging
|
||||
|
||||
```bash
|
||||
# Debug specific test
|
||||
npx playwright test auth.spec.ts --debug
|
||||
|
||||
# Run with headed browser
|
||||
npx playwright test --headed
|
||||
|
||||
# Slow down execution
|
||||
npx playwright test --slow-mo=1000
|
||||
```
|
||||
|
||||
### CI/CD Integration
|
||||
|
||||
```bash
|
||||
# Run in CI mode
|
||||
CI=true npx playwright test
|
||||
|
||||
# Generate artifacts for CI
|
||||
npx playwright test --reporter=github
|
||||
```
|
||||
|
||||
## Adding New Tests
|
||||
|
||||
### 1. Create Test File
|
||||
|
||||
```typescript
|
||||
import { test, expect } from './fixtures';
|
||||
|
||||
test.describe('New Feature', () => {
|
||||
test('should do something', async ({ adminPage }) => {
|
||||
// Test implementation
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
### 2. Add Helper Functions
|
||||
|
||||
```typescript
|
||||
// In helpers.ts
|
||||
export class NewFeatureHelpers {
|
||||
constructor(private page: any) {}
|
||||
|
||||
async performAction() {
|
||||
// Helper implementation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Update Documentation
|
||||
|
||||
- Add test description to this README
|
||||
- Update test count in project documentation
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
**Tests timeout:**
|
||||
|
||||
- Increase timeout in config
|
||||
- Add explicit waits
|
||||
- Check application startup time
|
||||
|
||||
**Flaky tests:**
|
||||
|
||||
- Add proper wait conditions
|
||||
- Use retry logic
|
||||
- Check for race conditions
|
||||
|
||||
**Browser compatibility:**
|
||||
|
||||
- Test across all configured browsers
|
||||
- Check for browser-specific issues
|
||||
- Use cross-browser compatible selectors
|
||||
|
||||
### Debug Commands
|
||||
|
||||
```bash
|
||||
# Show browser developer tools
|
||||
npx playwright test --debug
|
||||
|
||||
# Record test execution
|
||||
npx playwright codegen localhost:8080
|
||||
|
||||
# Trace viewer
|
||||
npx playwright show-trace trace.zip
|
||||
```
|
||||
|
||||
## Continuous Integration
|
||||
|
||||
Example GitHub Actions workflow:
|
||||
|
||||
```yaml
|
||||
name: E2E Tests
|
||||
on: [push, pull_request]
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-node@v3
|
||||
- run: npm ci
|
||||
- run: npx playwright install --with-deps
|
||||
- run: npm run test:e2e
|
||||
- uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
```
|
||||
|
||||
## Coverage and Metrics
|
||||
|
||||
The E2E tests provide coverage for:
|
||||
|
||||
- ✅ User authentication flows
|
||||
- ✅ Core medication management
|
||||
- ✅ Admin functionality
|
||||
- ✅ UI interactions and navigation
|
||||
- ✅ Responsive design
|
||||
- ✅ Cross-browser compatibility
|
||||
|
||||
For optimal coverage, run tests regularly and add new tests for new features.
|
||||
@@ -0,0 +1,63 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Admin Interface', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login as admin
|
||||
await page.goto('/');
|
||||
await page.fill('input[type="email"]', 'admin@localhost');
|
||||
await page.fill('input[type="password"]', 'admin123!');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for main app and open admin interface
|
||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
||||
await page.click('button:has-text("Admin")');
|
||||
});
|
||||
|
||||
test('should display admin interface', async ({ page }) => {
|
||||
await expect(page.locator('text=Admin Interface')).toBeVisible();
|
||||
await expect(page.locator('text=User Management')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show list of users', async ({ page }) => {
|
||||
// Should show admin user at minimum
|
||||
await expect(page.locator('text=admin@localhost')).toBeVisible();
|
||||
await expect(page.locator('text=Admin')).toBeVisible(); // Role
|
||||
});
|
||||
|
||||
test('should allow changing user password', async ({ page }) => {
|
||||
// Click on a user's change password button
|
||||
await page.click('[data-testid="change-password"]');
|
||||
|
||||
// Fill new password
|
||||
await page.fill('input[type="password"]', 'NewPassword123!');
|
||||
|
||||
// Submit password change
|
||||
await page.click('button:has-text("Change Password")');
|
||||
|
||||
// Should show success message
|
||||
await expect(page.locator('text=Password changed')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow suspending/activating users', async ({ page }) => {
|
||||
// Look for user status controls
|
||||
const statusButton = page
|
||||
.locator('[data-testid="toggle-user-status"]')
|
||||
.first();
|
||||
await expect(statusButton).toBeVisible();
|
||||
});
|
||||
|
||||
test('should refresh user list', async ({ page }) => {
|
||||
await page.click('button:has-text("Refresh")');
|
||||
|
||||
// Should still show users after refresh
|
||||
await expect(page.locator('text=admin@localhost')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should close admin interface', async ({ page }) => {
|
||||
await page.click('button[aria-label="Close"]');
|
||||
|
||||
// Should return to main app
|
||||
await expect(page.locator('text=Admin Interface')).not.toBeVisible();
|
||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Authentication Flow', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test('should display login page for unauthenticated users', async ({
|
||||
page,
|
||||
}) => {
|
||||
await expect(page.locator('h2')).toContainText(['Sign In', 'Login']);
|
||||
await expect(page.locator('input[type="email"]')).toBeVisible();
|
||||
await expect(page.locator('input[type="password"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow user registration', async ({ page }) => {
|
||||
// Click register tab/link
|
||||
await page.click('text=Register');
|
||||
|
||||
// Fill registration form
|
||||
await page.fill('input[type="email"]', 'test@example.com');
|
||||
await page.fill('input[name="username"]', 'testuser');
|
||||
await page.fill('input[type="password"]', 'TestPassword123!');
|
||||
|
||||
// Submit registration
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Should show verification message or redirect
|
||||
await expect(page.locator('text=verification')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should login with admin credentials', async ({ page }) => {
|
||||
// Fill login form with admin credentials
|
||||
await page.fill('input[type="email"]', 'admin@localhost');
|
||||
await page.fill('input[type="password"]', 'admin123!');
|
||||
|
||||
// Submit login
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Should redirect to main app
|
||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
||||
await expect(page.locator('text=Admin')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show error for invalid credentials', async ({ page }) => {
|
||||
await page.fill('input[type="email"]', 'invalid@example.com');
|
||||
await page.fill('input[type="password"]', 'wrongpassword');
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await expect(page.locator('text=Invalid')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should handle OAuth login buttons', async ({ page }) => {
|
||||
await expect(page.locator('button:has-text("Google")')).toBeVisible();
|
||||
await expect(page.locator('button:has-text("GitHub")')).toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { test as base } from '@playwright/test';
|
||||
|
||||
// Extend basic test with custom fixtures
|
||||
export const test = base.extend({
|
||||
// Auto-login fixture for admin user
|
||||
adminPage: async ({ page }, use) => {
|
||||
await page.goto('/');
|
||||
await page.fill('input[type="email"]', 'admin@localhost');
|
||||
await page.fill('input[type="password"]', 'admin123!');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for app to load
|
||||
await page.waitForSelector('h1:has-text("Medication Reminder")');
|
||||
|
||||
await use(page);
|
||||
},
|
||||
|
||||
// Regular user login fixture
|
||||
userPage: async ({ page }, use) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Register a test user first if needed
|
||||
await page.click('text=Register');
|
||||
await page.fill('input[type="email"]', 'testuser@example.com');
|
||||
await page.fill('input[name="username"]', 'testuser');
|
||||
await page.fill('input[type="password"]', 'TestPassword123!');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// For mock database, user might be auto-verified
|
||||
// Wait for either verification message or app load
|
||||
try {
|
||||
await page.waitForSelector('h1:has-text("Medication Reminder")', {
|
||||
timeout: 5000,
|
||||
});
|
||||
} catch {
|
||||
// If not auto-logged in, login manually
|
||||
await page.goto('/');
|
||||
await page.fill('input[type="email"]', 'testuser@example.com');
|
||||
await page.fill('input[type="password"]', 'TestPassword123!');
|
||||
await page.click('button[type="submit"]');
|
||||
await page.waitForSelector('h1:has-text("Medication Reminder")');
|
||||
}
|
||||
|
||||
await use(page);
|
||||
},
|
||||
});
|
||||
|
||||
export { expect } from '@playwright/test';
|
||||
@@ -0,0 +1,131 @@
|
||||
// E2E Test Utilities and Helpers
|
||||
|
||||
export class MedicationHelpers {
|
||||
constructor(private page: any) {}
|
||||
|
||||
async addMedication(
|
||||
name: string,
|
||||
dosage: string,
|
||||
frequency: string = 'daily',
|
||||
times: string = '1'
|
||||
) {
|
||||
await this.page.click('button:has-text("Add Medication")');
|
||||
await this.page.fill('input[name="name"]', name);
|
||||
await this.page.fill('input[name="dosage"]', dosage);
|
||||
await this.page.selectOption('select[name="frequency"]', frequency);
|
||||
if (times !== '1') {
|
||||
await this.page.fill('input[name="times"]', times);
|
||||
}
|
||||
await this.page.click('button[type="submit"]');
|
||||
|
||||
// Wait for medication to appear
|
||||
await this.page.waitForSelector(`text=${name}`);
|
||||
}
|
||||
|
||||
async deleteMedication(name: string) {
|
||||
await this.page.click('button:has-text("Manage")');
|
||||
|
||||
// Find the medication row and click delete
|
||||
const medicationRow = this.page.locator(`tr:has-text("${name}")`);
|
||||
await medicationRow.locator('[data-testid="delete-medication"]').click();
|
||||
await this.page.click('button:has-text("Delete")');
|
||||
|
||||
// Close manage modal
|
||||
await this.page.click('button:has-text("Close")');
|
||||
}
|
||||
|
||||
async takeDose(medicationName: string) {
|
||||
const doseCard = this.page.locator(
|
||||
`.dose-card:has-text("${medicationName}")`
|
||||
);
|
||||
await doseCard.locator('button:has-text("Take")').click();
|
||||
}
|
||||
}
|
||||
|
||||
export class AuthHelpers {
|
||||
constructor(private page: any) {}
|
||||
|
||||
async loginAsAdmin() {
|
||||
await this.page.goto('/');
|
||||
await this.page.fill('input[type="email"]', 'admin@localhost');
|
||||
await this.page.fill('input[type="password"]', 'admin123!');
|
||||
await this.page.click('button[type="submit"]');
|
||||
await this.page.waitForSelector('h1:has-text("Medication Reminder")');
|
||||
}
|
||||
|
||||
async registerUser(email: string, username: string, password: string) {
|
||||
await this.page.goto('/');
|
||||
await this.page.click('text=Register');
|
||||
await this.page.fill('input[type="email"]', email);
|
||||
await this.page.fill('input[name="username"]', username);
|
||||
await this.page.fill('input[type="password"]', password);
|
||||
await this.page.click('button[type="submit"]');
|
||||
}
|
||||
|
||||
async logout() {
|
||||
await this.page.click('[data-testid="avatar-dropdown"]');
|
||||
await this.page.click('button:has-text("Logout")');
|
||||
await this.page.waitForSelector('h2:has-text("Sign In")');
|
||||
}
|
||||
}
|
||||
|
||||
export class ModalHelpers {
|
||||
constructor(private page: any) {}
|
||||
|
||||
async openModal(buttonText: string) {
|
||||
await this.page.click(`button:has-text("${buttonText}")`);
|
||||
}
|
||||
|
||||
async closeModal() {
|
||||
await this.page.click('button:has-text("Close")');
|
||||
}
|
||||
|
||||
async confirmAction() {
|
||||
await this.page.click('button:has-text("Confirm")');
|
||||
}
|
||||
}
|
||||
|
||||
export class WaitHelpers {
|
||||
constructor(private page: any) {}
|
||||
|
||||
async waitForAppLoad() {
|
||||
await this.page.waitForSelector('h1:has-text("Medication Reminder")');
|
||||
}
|
||||
|
||||
async waitForModal(title: string) {
|
||||
await this.page.waitForSelector(`text=${title}`);
|
||||
}
|
||||
|
||||
async waitForNotification(message: string) {
|
||||
await this.page.waitForSelector(`text=${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Data generators for testing
|
||||
export const TestData = {
|
||||
medications: [
|
||||
{ name: 'Aspirin', dosage: '100mg', frequency: 'daily', times: '1' },
|
||||
{ name: 'Vitamin D', dosage: '1000 IU', frequency: 'daily', times: '1' },
|
||||
{ name: 'Omega-3', dosage: '500mg', frequency: 'daily', times: '2' },
|
||||
{ name: 'Calcium', dosage: '600mg', frequency: 'twice_daily', times: '1' },
|
||||
],
|
||||
|
||||
users: [
|
||||
{
|
||||
email: 'test1@example.com',
|
||||
username: 'testuser1',
|
||||
password: 'TestPass123!',
|
||||
},
|
||||
{
|
||||
email: 'test2@example.com',
|
||||
username: 'testuser2',
|
||||
password: 'TestPass456!',
|
||||
},
|
||||
],
|
||||
|
||||
reminders: [
|
||||
{ title: 'Drink Water', icon: 'bell', frequency: 60 },
|
||||
{ title: 'Exercise', icon: 'heart', frequency: 1440 }, // Daily
|
||||
{ title: 'Check Blood Pressure', icon: 'chart', frequency: 10080 }, // Weekly
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Medication Management', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login as admin first
|
||||
await page.goto('/');
|
||||
await page.fill('input[type="email"]', 'admin@localhost');
|
||||
await page.fill('input[type="password"]', 'admin123!');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Wait for main app to load
|
||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
||||
});
|
||||
|
||||
test('should add a new medication', async ({ page }) => {
|
||||
// Click add medication button
|
||||
await page.click('button:has-text("Add Medication")');
|
||||
|
||||
// Fill medication form
|
||||
await page.fill('input[name="name"]', 'Aspirin');
|
||||
await page.fill('input[name="dosage"]', '100mg');
|
||||
await page.selectOption('select[name="frequency"]', 'daily');
|
||||
await page.fill('input[name="times"]', '2');
|
||||
|
||||
// Submit form
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Should see medication in list
|
||||
await expect(page.locator('text=Aspirin')).toBeVisible();
|
||||
await expect(page.locator('text=100mg')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should edit existing medication', async ({ page }) => {
|
||||
// First add a medication
|
||||
await page.click('button:has-text("Add Medication")');
|
||||
await page.fill('input[name="name"]', 'Vitamin D');
|
||||
await page.fill('input[name="dosage"]', '1000 IU');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Click edit button for the medication
|
||||
await page.click('[data-testid="edit-medication"]');
|
||||
|
||||
// Update dosage
|
||||
await page.fill('input[name="dosage"]', '2000 IU');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Should see updated dosage
|
||||
await expect(page.locator('text=2000 IU')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should delete medication', async ({ page }) => {
|
||||
// Add a medication first
|
||||
await page.click('button:has-text("Add Medication")');
|
||||
await page.fill('input[name="name"]', 'Test Medicine');
|
||||
await page.fill('input[name="dosage"]', '50mg');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Open manage medications modal
|
||||
await page.click('button:has-text("Manage")');
|
||||
|
||||
// Delete the medication
|
||||
await page.click('[data-testid="delete-medication"]');
|
||||
await page.click('button:has-text("Delete")'); // Confirm deletion
|
||||
|
||||
// Should not see medication anymore
|
||||
await expect(page.locator('text=Test Medicine')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should mark dose as taken', async ({ page }) => {
|
||||
// Add a medication first
|
||||
await page.click('button:has-text("Add Medication")');
|
||||
await page.fill('input[name="name"]', 'Daily Vitamin');
|
||||
await page.fill('input[name="dosage"]', '1 tablet');
|
||||
await page.selectOption('select[name="frequency"]', 'daily');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Find and click the "Take" button for upcoming dose
|
||||
await page.click('button:has-text("Take")');
|
||||
|
||||
// Should show as taken
|
||||
await expect(page.locator('text=Taken')).toBeVisible();
|
||||
await expect(page.locator('.bg-green-50')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should show medication history', async ({ page }) => {
|
||||
// Open history modal
|
||||
await page.click('button:has-text("History")');
|
||||
|
||||
// Should show history modal
|
||||
await expect(page.locator('text=Medication History')).toBeVisible();
|
||||
|
||||
// Close modal
|
||||
await page.click('button:has-text("Close")');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Reminder System', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login as admin
|
||||
await page.goto('/');
|
||||
await page.fill('input[type="email"]', 'admin@localhost');
|
||||
await page.fill('input[type="password"]', 'admin123!');
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
||||
});
|
||||
|
||||
test('should create custom reminder', async ({ page }) => {
|
||||
// Click manage reminders
|
||||
await page.click('button:has-text("Reminders")');
|
||||
|
||||
// Add new reminder
|
||||
await page.click('button:has-text("Add Reminder")');
|
||||
|
||||
// Fill reminder form
|
||||
await page.fill('input[name="title"]', 'Drink Water');
|
||||
await page.selectOption('select[name="icon"]', 'bell');
|
||||
await page.fill('input[name="frequency"]', '60'); // Every hour
|
||||
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Should see reminder in list
|
||||
await expect(page.locator('text=Drink Water')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should edit custom reminder', async ({ page }) => {
|
||||
// First create a reminder
|
||||
await page.click('button:has-text("Reminders")');
|
||||
await page.click('button:has-text("Add Reminder")');
|
||||
await page.fill('input[name="title"]', 'Exercise');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Edit the reminder
|
||||
await page.click('[data-testid="edit-reminder"]');
|
||||
await page.fill('input[name="title"]', 'Morning Exercise');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
await expect(page.locator('text=Morning Exercise')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should delete custom reminder', async ({ page }) => {
|
||||
// Create and then delete reminder
|
||||
await page.click('button:has-text("Reminders")');
|
||||
await page.click('button:has-text("Add Reminder")');
|
||||
await page.fill('input[name="title"]', 'Temporary Reminder');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Delete it
|
||||
await page.click('[data-testid="delete-reminder"]');
|
||||
await page.click('button:has-text("Delete")'); // Confirm
|
||||
|
||||
await expect(page.locator('text=Temporary Reminder')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should show scheduled medication doses', async ({ page }) => {
|
||||
// Add a medication first
|
||||
await page.click('button:has-text("Add Medication")');
|
||||
await page.fill('input[name="name"]', 'Scheduled Med');
|
||||
await page.fill('input[name="dosage"]', '5mg');
|
||||
await page.selectOption('select[name="frequency"]', 'daily');
|
||||
await page.fill('input[name="times"]', '3'); // 3 times daily
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Should see scheduled doses for today
|
||||
await expect(page.locator('text=Scheduled Med')).toBeVisible();
|
||||
await expect(page.locator('button:has-text("Take")')).toHaveCount(3);
|
||||
});
|
||||
|
||||
test('should handle missed doses', async ({ page }) => {
|
||||
// This would test the logic for marking doses as missed
|
||||
// when they pass their scheduled time
|
||||
|
||||
// Add medication with past schedule
|
||||
await page.click('button:has-text("Add Medication")');
|
||||
await page.fill('input[name="name"]', 'Past Due Med');
|
||||
await page.fill('input[name="dosage"]', '10mg');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Simulate time passing or manually mark as missed
|
||||
// This would depend on your app's specific implementation
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('User Interface and Navigation', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
// Login as admin
|
||||
await page.goto('/');
|
||||
await page.fill('input[type="email"]', 'admin@localhost');
|
||||
await page.fill('input[type="password"]', 'admin123!');
|
||||
await page.click('button[type="submit"]');
|
||||
await expect(page.locator('h1')).toContainText('Medication Reminder');
|
||||
});
|
||||
|
||||
test('should display main navigation elements', async ({ page }) => {
|
||||
await expect(
|
||||
page.locator('button:has-text("Add Medication")')
|
||||
).toBeVisible();
|
||||
await expect(page.locator('button:has-text("Manage")')).toBeVisible();
|
||||
await expect(page.locator('button:has-text("History")')).toBeVisible();
|
||||
await expect(page.locator('button:has-text("Stats")')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should toggle theme', async ({ page }) => {
|
||||
// Click theme switcher
|
||||
await page.click('[data-testid="theme-switcher"]');
|
||||
|
||||
// Check if dark mode is applied
|
||||
await expect(page.locator('html')).toHaveClass(/dark/);
|
||||
|
||||
// Toggle back to light mode
|
||||
await page.click('[data-testid="theme-switcher"]');
|
||||
await expect(page.locator('html')).not.toHaveClass(/dark/);
|
||||
});
|
||||
|
||||
test('should open and close account modal', async ({ page }) => {
|
||||
// Click account button
|
||||
await page.click('button:has-text("Account")');
|
||||
|
||||
// Should show account modal
|
||||
await expect(page.locator('text=Account Settings')).toBeVisible();
|
||||
|
||||
// Close modal
|
||||
await page.click('button:has-text("Close")');
|
||||
await expect(page.locator('text=Account Settings')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should open stats modal', async ({ page }) => {
|
||||
await page.click('button:has-text("Stats")');
|
||||
|
||||
await expect(page.locator('text=Medication Statistics')).toBeVisible();
|
||||
await expect(page.locator('text=Weekly Adherence')).toBeVisible();
|
||||
|
||||
await page.click('button:has-text("Close")');
|
||||
});
|
||||
|
||||
test('should show current time and date', async ({ page }) => {
|
||||
// Should display current time somewhere on the page
|
||||
const timeElement = page.locator('[data-testid="current-time"]');
|
||||
await expect(timeElement).toBeVisible();
|
||||
});
|
||||
|
||||
test('should handle responsive design', async ({ page }) => {
|
||||
// Test mobile viewport
|
||||
await page.setViewportSize({ width: 375, height: 667 });
|
||||
|
||||
// Should still show main elements
|
||||
await expect(page.locator('h1')).toBeVisible();
|
||||
await expect(
|
||||
page.locator('button:has-text("Add Medication")')
|
||||
).toBeVisible();
|
||||
|
||||
// Reset to desktop
|
||||
await page.setViewportSize({ width: 1280, height: 720 });
|
||||
});
|
||||
|
||||
test('should search medications', async ({ page }) => {
|
||||
// Add a test medication first
|
||||
await page.click('button:has-text("Add Medication")');
|
||||
await page.fill('input[name="name"]', 'Searchable Medicine');
|
||||
await page.fill('input[name="dosage"]', '10mg');
|
||||
await page.click('button[type="submit"]');
|
||||
|
||||
// Use search functionality
|
||||
await page.fill('input[placeholder*="search"]', 'Searchable');
|
||||
|
||||
// Should show filtered results
|
||||
await expect(page.locator('text=Searchable Medicine')).toBeVisible();
|
||||
|
||||
// Clear search
|
||||
await page.fill('input[placeholder*="search"]', '');
|
||||
});
|
||||
|
||||
test('should logout user', async ({ page }) => {
|
||||
// Click logout (usually in avatar dropdown)
|
||||
await page.click('[data-testid="avatar-dropdown"]');
|
||||
await page.click('button:has-text("Logout")');
|
||||
|
||||
// Should return to login page
|
||||
await expect(page.locator('h2')).toContainText(['Sign In', 'Login']);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bun
|
||||
|
||||
// Production environment test script
|
||||
console.log('🧪 Testing Production Environment...\n');
|
||||
|
||||
// Test 1: Check if CouchDB is accessible
|
||||
console.log('1️⃣ Testing CouchDB connection...');
|
||||
try {
|
||||
const response = await fetch('http://localhost:5984/', {
|
||||
headers: {
|
||||
Authorization: 'Basic ' + btoa('admin:password'),
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('✅ CouchDB is accessible');
|
||||
console.log(` Version: ${data.version}`);
|
||||
} else {
|
||||
console.log('❌ CouchDB connection failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ CouchDB connection error:', error.message);
|
||||
}
|
||||
|
||||
// Test 2: Check if databases exist
|
||||
console.log('\n2️⃣ Checking databases...');
|
||||
try {
|
||||
const response = await fetch('http://localhost:5984/_all_dbs', {
|
||||
headers: {
|
||||
Authorization: 'Basic ' + btoa('admin:password'),
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const databases = await response.json();
|
||||
console.log('✅ Available databases:', databases);
|
||||
|
||||
const requiredDbs = [
|
||||
'users',
|
||||
'medications',
|
||||
'settings',
|
||||
'taken_doses',
|
||||
'reminders',
|
||||
];
|
||||
const missing = requiredDbs.filter(db => !databases.includes(db));
|
||||
|
||||
if (missing.length === 0) {
|
||||
console.log('✅ All required databases exist');
|
||||
} else {
|
||||
console.log('⚠️ Missing databases:', missing);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ Database check error:', error.message);
|
||||
}
|
||||
|
||||
// Test 3: Check if frontend is accessible
|
||||
console.log('\n3️⃣ Testing frontend accessibility...');
|
||||
try {
|
||||
const response = await fetch('http://localhost:8080/');
|
||||
|
||||
if (response.ok) {
|
||||
console.log('✅ Frontend is accessible at http://localhost:8080');
|
||||
} else {
|
||||
console.log('❌ Frontend connection failed');
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('❌ Frontend connection error:', error.message);
|
||||
}
|
||||
|
||||
console.log('\n🎯 Production Environment Test Summary:');
|
||||
console.log(' • CouchDB: http://localhost:5984');
|
||||
console.log(' • Frontend: http://localhost:8080');
|
||||
console.log(' • Admin Login: admin@localhost / admin123!');
|
||||
console.log(
|
||||
'\n🚀 Your medication reminder app is ready for production testing!'
|
||||
);
|
||||
@@ -0,0 +1,34 @@
|
||||
// Simple test script to verify admin login functionality
|
||||
// Run this in the browser console to test admin credentials
|
||||
|
||||
async function testAdminLogin() {
|
||||
console.log('🧪 Testing admin login...');
|
||||
|
||||
// Import the services (this won't work directly, but helps us understand the flow)
|
||||
console.log('Admin credentials:');
|
||||
console.log('Email: admin@localhost');
|
||||
console.log('Password: admin123!');
|
||||
|
||||
// Check if admin user exists in localStorage
|
||||
const users = JSON.parse(localStorage.getItem('users') || '[]');
|
||||
console.log('All users in localStorage:', users);
|
||||
|
||||
const adminUser = users.find(u => u.email === 'admin@localhost');
|
||||
console.log('Admin user found:', adminUser);
|
||||
|
||||
if (adminUser) {
|
||||
console.log('Admin user details:');
|
||||
console.log('- Email:', adminUser.email);
|
||||
console.log('- Password:', adminUser.password);
|
||||
console.log('- Role:', adminUser.role);
|
||||
console.log('- Status:', adminUser.status);
|
||||
console.log('- Email Verified:', adminUser.emailVerified);
|
||||
} else {
|
||||
console.log('❌ Admin user not found in localStorage');
|
||||
}
|
||||
}
|
||||
|
||||
// Instructions
|
||||
console.log('Copy and paste this function in browser console:');
|
||||
console.log(testAdminLogin.toString());
|
||||
console.log('Then run: testAdminLogin()');
|
||||
@@ -0,0 +1,83 @@
|
||||
// Simple test to verify auth database functionality
|
||||
// Run this in browser console at http://localhost:5174
|
||||
|
||||
console.log('Testing Authentication Database...');
|
||||
|
||||
// Test the mock database service
|
||||
async function testDatabase() {
|
||||
try {
|
||||
// Import the services (this would work in browser context)
|
||||
const { dbService } = await import('./services/couchdb.ts');
|
||||
const { authService } = await import('./services/auth/auth.service.ts');
|
||||
|
||||
console.log('1. Testing user creation with password...');
|
||||
|
||||
// Test creating a user with password
|
||||
const testEmail = 'test@example.com';
|
||||
const testPassword = 'password123';
|
||||
|
||||
try {
|
||||
const newUser = await dbService.createUserWithPassword(
|
||||
testEmail,
|
||||
testPassword
|
||||
);
|
||||
console.log('✅ User created successfully:', newUser);
|
||||
} catch (error) {
|
||||
if (error.message.includes('already exists')) {
|
||||
console.log('ℹ️ User already exists, testing login...');
|
||||
} else {
|
||||
console.error('❌ User creation failed:', error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('2. Testing password login...');
|
||||
|
||||
// Test login with password
|
||||
try {
|
||||
const loginResult = await authService.login({
|
||||
email: testEmail,
|
||||
password: testPassword,
|
||||
});
|
||||
console.log('✅ Password login successful:', loginResult);
|
||||
} catch (error) {
|
||||
console.error('❌ Password login failed:', error);
|
||||
}
|
||||
|
||||
console.log('3. Testing OAuth user creation...');
|
||||
|
||||
// Test OAuth user creation
|
||||
const oauthData = {
|
||||
email: 'oauth@example.com',
|
||||
username: 'oauth_user',
|
||||
avatar: 'https://example.com/avatar.jpg',
|
||||
};
|
||||
|
||||
try {
|
||||
const oauthUser = await dbService.createUserFromOAuth(oauthData);
|
||||
console.log('✅ OAuth user created successfully:', oauthUser);
|
||||
} catch (error) {
|
||||
if (error.message.includes('already exists')) {
|
||||
console.log('ℹ️ OAuth user already exists');
|
||||
} else {
|
||||
console.error('❌ OAuth user creation failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('4. Testing user lookup by email...');
|
||||
|
||||
// Test finding users
|
||||
const foundUser = await dbService.findUserByEmail(testEmail);
|
||||
console.log('✅ User found by email:', foundUser);
|
||||
|
||||
console.log('🎉 All database tests completed!');
|
||||
} catch (error) {
|
||||
console.error('💥 Test setup failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Export for manual testing
|
||||
if (typeof window !== 'undefined') {
|
||||
window.testAuthDB = testDatabase;
|
||||
console.log('Run window.testAuthDB() to test the authentication database');
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// Test the email validation in browser console
|
||||
console.log('Testing email validation for admin@localhost');
|
||||
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+(\.[^\s@]+|localhost)$/;
|
||||
const testEmail = 'admin@localhost';
|
||||
|
||||
console.log('Email:', testEmail);
|
||||
console.log('Regex:', emailRegex.toString());
|
||||
console.log('Test result:', emailRegex.test(testEmail));
|
||||
|
||||
// Let's also test step by step
|
||||
console.log('Parts breakdown:');
|
||||
console.log('- Has @ symbol:', testEmail.includes('@'));
|
||||
console.log('- Before @:', testEmail.split('@')[0]);
|
||||
console.log('- After @:', testEmail.split('@')[1]);
|
||||
console.log('- No spaces:', !/\s/.test(testEmail));
|
||||
|
||||
// Let's test a simpler regex that should definitely work
|
||||
const simpleRegex = /^[^@\s]+@[^@\s]+$/;
|
||||
console.log('Simple regex test:', simpleRegex.test(testEmail));
|
||||
|
||||
// Copy this code and paste it in the browser console when you get the validation error
|
||||
@@ -0,0 +1,39 @@
|
||||
// Test setup file
|
||||
// Configure jsdom and global test utilities
|
||||
|
||||
import 'jest-environment-jsdom';
|
||||
|
||||
// Mock localStorage
|
||||
const localStorageMock = {
|
||||
getItem: jest.fn(),
|
||||
setItem: jest.fn(),
|
||||
removeItem: jest.fn(),
|
||||
clear: jest.fn(),
|
||||
length: 0,
|
||||
key: jest.fn(),
|
||||
} as Storage;
|
||||
|
||||
Object.defineProperty(window, 'localStorage', {
|
||||
value: localStorageMock,
|
||||
});
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = jest.fn();
|
||||
|
||||
// Setup console to avoid noise in tests
|
||||
const originalError = console.error;
|
||||
beforeAll(() => {
|
||||
console.error = (...args: any[]) => {
|
||||
if (
|
||||
typeof args[0] === 'string' &&
|
||||
args[0].includes('Warning: ReactDOM.render is deprecated')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
originalError.call(console, ...args);
|
||||
};
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
console.error = originalError;
|
||||
});
|
||||
Reference in New Issue
Block a user