diff --git a/server/prisma/test.db b/server/prisma/test.db index d4e8809..9121a76 100644 Binary files a/server/prisma/test.db and b/server/prisma/test.db differ diff --git a/tests/data-progress.spec.ts b/tests/data-progress.spec.ts new file mode 100644 index 0000000..a9962fd --- /dev/null +++ b/tests/data-progress.spec.ts @@ -0,0 +1,397 @@ +import { test, expect } from './fixtures'; +import { randomUUID } from 'crypto'; + +// Helper for setup +async function loginAndSetup(page: any, createUniqueUser: any) { + const user = await createUniqueUser(); + await page.goto('/'); + await page.getByLabel('Email').fill(user.email); + await page.getByLabel('Password').fill(user.password); + await page.getByRole('button', { name: 'Login' }).click(); + + try { + const heading = page.getByRole('heading', { name: /Change Password/i }); + const dashboard = page.getByText('Free Workout'); + await expect(heading.or(dashboard)).toBeVisible({ timeout: 5000 }); + if (await heading.isVisible()) { + await page.getByLabel('New Password').fill('StrongNewPass123!'); + await page.getByRole('button', { name: /Save|Change/i }).click(); + await expect(dashboard).toBeVisible(); + } + } catch (e) { + // Login might already be done + } + return user; +} + +test.describe('IV. Data & Progress', () => { + + test('4.1. A. Session History - View Past Sessions', async ({ page, createUniqueUser, request }) => { + const user = await loginAndSetup(page, createUniqueUser); + + // Subtask 2.1: Complete a workout session + const exNameSession = 'Hist View Session ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { + data: { name: exNameSession, type: 'STRENGTH' }, + headers: { 'Authorization': `Bearer ${user.token}` } + }); + + await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); + + await page.getByRole('textbox', { name: /Select Exercise/i }).click(); + await page.getByText(exNameSession).click(); + + await page.getByLabel('Weight (kg)').first().fill('50'); + await page.getByLabel('Reps').first().fill('10'); + await page.getByRole('button', { name: /Log Set/i }).click(); + + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + // Subtask 2.2: Log a sporadic set + const exNameSporadic = 'Hist View Sporadic ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { + data: { name: exNameSporadic, type: 'STRENGTH' }, + headers: { 'Authorization': `Bearer ${user.token}` } + }); + + await page.getByRole('button', { name: 'Quick Log' }).click(); + await page.getByRole('textbox', { name: /Select Exercise/i }).click(); + await page.getByText(exNameSporadic).click(); + + await page.getByLabel(/Reps/i).first().fill('12'); + await page.getByRole('button', { name: /Log Set/i }).click(); + + await page.getByRole('button', { name: 'Quit' }).click(); + + // 3. Navigate to History + await page.getByRole('button', { name: 'History' }).click(); + + // Verification + await expect(page.getByRole('heading', { name: 'History' })).toBeVisible(); + + // Check for Quick Log entry details + await expect(page.getByText(/50\s*kg\s*x\s*12\s*reps/).or(page.getByText(/x 12 reps/))).toBeVisible(); + + // Check for Workout Session entry (shows summary) + await expect(page.getByText('No plan').first()).toBeVisible(); + await expect(page.getByText('Sets:').first()).toBeVisible(); + + // Check for Quick Log heading + await expect(page.getByRole('heading', { name: 'Quick Log' })).toBeVisible(); + }); + + test('4.2. A. Session History - View Detailed Session', async ({ page, createUniqueUser, request }) => { + const user = await loginAndSetup(page, createUniqueUser); + + const exName = 'Detail View ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { + data: { name: exName, type: 'STRENGTH' }, + headers: { 'Authorization': `Bearer ${user.token}` } + }); + + // Complete session + await page.getByRole('button', { name: /Free Workout|Start Empty/i }).click(); + await page.getByRole('textbox', { name: /Select Exercise/i }).click(); + await page.getByText(exName).click(); + + await page.getByLabel('Weight (kg)').first().fill('50'); + await page.getByLabel('Reps').first().fill('10'); + await page.getByRole('button', { name: /Log Set/i }).click(); + + await expect(page.getByText('50 kg x 10 reps')).toBeVisible(); + + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + // Navigate to History + await page.getByRole('button', { name: 'History' }).click(); + + // Click on a workout session entry + await page.getByText('No plan').first().click(); + + // Verification + await expect(page.getByRole('heading', { name: /Edit|Session Details/ })).toBeVisible(); + + // Check details + await expect(page.getByText('Start')).toBeVisible(); + await expect(page.getByText('End')).toBeVisible(); + await expect(page.getByText('Weight (kg)').first()).toBeVisible(); + + // Verify set details + await expect(page.getByRole('heading', { name: /Sets/ })).toBeVisible(); + }); + + test('4.3. A. Session History - Edit Past Session Details', async ({ page, createUniqueUser, request }) => { + const user = await loginAndSetup(page, createUniqueUser); + + const exName = 'Edit Sess ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); + + await page.getByRole('button', { name: /Free Workout/i }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + + await page.getByLabel(/Weight/i).first().fill('50'); + await page.getByLabel(/Reps/i).first().fill('10'); + await page.getByRole('button', { name: /Log/i }).click(); + + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + await page.getByRole('button', { name: 'History' }).click(); + + // Open details + await page.getByText('No plan').first().click(); + + // Modify Body Weight (first spinbutton usually) + await page.getByRole('spinbutton').first().fill('75.5'); + + // Save + await page.getByRole('button', { name: 'Save' }).click(); + + // Verify + await expect(page.getByText('75.5kg')).toBeVisible(); + }); + + test('4.4. A. Session History - Edit Individual Set in Past Session', async ({ page, createUniqueUser, request }) => { + const user = await loginAndSetup(page, createUniqueUser); + + const exName = 'Edit Set ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); + + await page.getByRole('button', { name: /Free Workout/i }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + + await page.getByLabel(/Weight/i).first().fill('50'); + await page.getByLabel(/Reps/i).first().fill('10'); + await page.getByRole('button', { name: /Log/i }).click(); + + await expect(page.getByText('50 kg x 10 reps')).toBeVisible(); + + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + await page.getByRole('button', { name: 'History' }).click(); + + // Open details + await page.getByText('No plan').first().click(); + + // Modify weight from 50 to 55 + // Be specific with locator if possible, or use first matching input + await page.locator('input[value="50"]').fill('55'); + + // Save + await page.getByRole('button', { name: 'Save' }).click(); + + // Verify + await page.getByText('No plan').first().click(); + await expect(page.locator('input[value="55"]')).toBeVisible(); + }); + + test('4.5. A. Session History - Delete Past Session', async ({ page, createUniqueUser, request }) => { + const user = await loginAndSetup(page, createUniqueUser); + + const exName = 'Del Sess ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); + + await page.getByRole('button', { name: /Free Workout/i }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + + await page.getByLabel(/Weight/i).first().fill('50'); + await page.getByLabel(/Reps/i).first().fill('10'); + await page.getByRole('button', { name: /Log/i }).click(); + + await expect(page.getByText('50 kg x 10 reps')).toBeVisible(); + + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + await page.getByRole('button', { name: 'History' }).click(); + await expect(page.getByText('No plan').first()).toBeVisible(); + + // Delete (2nd button usually) + await page.getByRole('main').getByRole('button').nth(1).click(); + + // Confirm + await expect(page.getByRole('heading', { name: 'Delete workout?' })).toBeVisible(); + await page.getByRole('button', { name: 'Delete' }).click(); + + // Verify empty + await expect(page.getByText('History is empty')).toBeVisible(); + }); + + test('4.6. A. Session History - Edit Sporadic Set', async ({ page, createUniqueUser, request }) => { + const user = await loginAndSetup(page, createUniqueUser); + + const exName = 'Spor Edit ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); + + await page.getByRole('button', { name: 'Quick Log' }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + + await page.getByLabel(/Weight/i).first().fill('50'); + await page.getByLabel(/Reps/i).first().fill('12'); + await page.getByRole('button', { name: /Log/i }).click(); + await page.getByRole('button', { name: 'Quit' }).click(); + + await page.getByRole('button', { name: 'History' }).click(); + await expect(page.getByRole('heading', { name: 'Quick Log' })).toBeVisible(); + + // Edit (1st button for sporadic row) + await page.getByRole('main').getByRole('button').nth(0).click(); + + await expect(page.getByRole('heading', { name: 'Edit' })).toBeVisible(); + await page.locator('input[value="12"]').fill('15'); + await page.getByRole('button', { name: 'Save' }).click(); + + await expect(page.getByText(/50\s*kg\s*x\s*15\s*reps/)).toBeVisible(); + }); + + test('4.7. A. Session History - Delete Sporadic Set', async ({ page, createUniqueUser, request }) => { + const user = await loginAndSetup(page, createUniqueUser); + + const exName = 'Spor Del ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); + + await page.getByRole('button', { name: 'Quick Log' }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + await page.getByLabel(/Weight/i).first().fill('50'); + await page.getByLabel(/Reps/i).first().fill('12'); + await page.getByRole('button', { name: /Log/i }).click(); + await page.getByRole('button', { name: 'Quit' }).click(); + + await page.getByRole('button', { name: 'History' }).click(); + + // Delete (2nd button for sporadic row, or last button in main if only one row) + // With only one row, buttons are Edit, Delete. Delete is 2nd. + await page.getByRole('main').getByRole('button').last().click(); + + await expect(page.getByRole('dialog')).toBeVisible(); + await page.getByRole('button', { name: 'Delete' }).click(); + + await expect(page.getByText('50 kg x 12 reps')).not.toBeVisible(); + }); + + test('4.8. B. Performance Statistics - View Volume Chart', async ({ page, createUniqueUser, request }) => { + test.setTimeout(120000); + const user = await loginAndSetup(page, createUniqueUser); + const exName = 'Vol Chart ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); + + // Session 1 + await page.getByRole('button', { name: /Free Workout/i }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + await page.getByLabel(/Weight/i).first().fill('50'); + await page.getByLabel(/Reps/i).first().fill('10'); + await page.getByRole('button', { name: /Log/i }).click(); + + await expect(page.getByText('50 kg x 10 reps')).toBeVisible(); + + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + // Session 2 + await page.getByRole('button', { name: /Free Workout/i }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + await page.getByLabel(/Weight/i).first().fill('60'); + await page.getByLabel(/Reps/i).first().fill('10'); + await page.getByRole('button', { name: /Log/i }).click(); + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + await page.getByRole('button', { name: 'Stats' }).click(); + await expect(page.getByText('Work Volume')).toBeVisible(); + }); + + test('4.9. B. Performance Statistics - View Set Count Chart', async ({ page, createUniqueUser, request }) => { + test.setTimeout(120000); + const user = await loginAndSetup(page, createUniqueUser); + const exName = 'Set Chart ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); + + // Session 1 + await page.getByRole('button', { name: /Free Workout/i }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + await page.getByLabel(/Weight/i).first().fill('50'); + await page.getByLabel(/Reps/i).first().fill('10'); + await page.getByRole('button', { name: /Log/i }).click(); + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + // Session 2 + await page.getByRole('button', { name: /Free Workout/i }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + await page.getByLabel(/Weight/i).first().fill('60'); + await page.getByLabel(/Reps/i).first().fill('10'); + await page.getByRole('button', { name: /Log/i }).click(); + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + await page.getByRole('button', { name: 'Stats' }).click(); + await expect(page.getByText('Number of Sets')).toBeVisible(); + }); + + test('4.10. B. Performance Statistics - View Body Weight Chart', async ({ page, createUniqueUser, request }) => { + test.setTimeout(120000); + const user = await loginAndSetup(page, createUniqueUser); + const exName = 'BW Chart ' + randomUUID().slice(0, 4); + await request.post('/api/exercises', { data: { name: exName, type: 'STRENGTH' }, headers: { 'Authorization': `Bearer ${user.token}` } }); + + // Complete 2 sessions (to unlock stats page - assuming constraint) + // Session 1 + await page.getByRole('button', { name: /Free Workout/i }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + await page.getByLabel(/Weight/i).first().fill('50'); + await page.getByLabel(/Reps/i).first().fill('10'); + await page.getByRole('button', { name: /Log/i }).click(); + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + // Session 2 + await page.getByRole('button', { name: /Free Workout/i }).click(); + await page.getByRole('textbox', { name: /Select/i }).click(); + await page.getByText(exName).click(); + await page.getByLabel(/Weight/i).first().fill('60'); + await page.getByLabel(/Reps/i).first().fill('10'); + await page.getByRole('button', { name: /Log/i }).click(); + await page.getByRole('button', { name: 'Finish' }).click(); + await page.getByRole('button', { name: 'Confirm' }).click(); + + // Log body weight history via API + const yesterday = new Date(); + yesterday.setDate(yesterday.getDate() - 1); + const dateStr = yesterday.toISOString().split('T')[0]; + + await page.evaluate(async ({ token, dateStr }) => { + await fetch('/api/weight', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + }, + body: JSON.stringify({ weight: 70, dateStr }) + }); + }, { token: user.token, dateStr }); + + // Log today's weight via UI + await page.getByRole('button', { name: 'Profile' }).click(); + await page.getByRole('button', { name: 'Weight Tracker' }).click(); + await page.getByPlaceholder('Enter weight...').fill('72'); + await page.getByRole('button', { name: 'Log', exact: true }).click(); + await expect(page.getByText('Weight logged successfully')).toBeVisible(); + + await page.getByRole('button', { name: 'Stats' }).click(); + await expect(page.getByText('Body Weight History')).toBeVisible(); + }); + +}); diff --git a/tests/test-1.spec.ts b/tests/test-1.spec.ts deleted file mode 100644 index 5b3d48c..0000000 --- a/tests/test-1.spec.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('test', async ({ page }) => { - // Recording... -}); \ No newline at end of file diff --git a/tests/test-2.spec.ts b/tests/test-2.spec.ts deleted file mode 100644 index d3f129d..0000000 --- a/tests/test-2.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test('test', async ({ page }) => { - await page.goto('http://localhost:3000/login'); - await page.getByRole('textbox', { name: 'Email' }).click(); - await page.getByRole('textbox', { name: 'Email' }).fill('admin@gymflow.ai'); - await page.getByRole('textbox', { name: 'Email' }).press('Tab'); - await page.getByRole('textbox', { name: 'Password' }).fill('admin123'); - await page.getByRole('button', { name: 'Login' }).click(); - await page.getByRole('button', { name: 'Plans' }).click(); - await page.getByRole('button', { name: 'Create Plan' }).click(); - await page.getByRole('textbox', { name: 'Name' }).click(); - await page.getByRole('textbox', { name: 'Name' }).fill('Smart Plan'); - await page.getByRole('button', { name: 'Add Exercise' }).click(); - await page.getByRole('button').filter({ hasText: /^$/ }).nth(2).click(); - await page.locator('[id="_r_3_"]').fill('Exercise A'); - await page.getByRole('button', { name: 'Create' }).click(); - await page.getByRole('button', { name: 'Add Exercise' }).click(); - await page.getByRole('button').filter({ hasText: /^$/ }).nth(3).click(); - await page.locator('[id="_r_4_"]').fill('Exercise B'); - await page.getByRole('button', { name: 'Create' }).click(); - await page.getByRole('button', { name: 'Save' }).click(); - await page.getByRole('button', { name: 'Start' }).nth(1).click(); - await page.getByRole('spinbutton', { name: 'Weight (kg)' }).click(); - await page.getByRole('spinbutton', { name: 'Weight (kg)' }).fill('12'); - await page.getByRole('spinbutton', { name: 'Reps' }).click(); - await page.getByRole('spinbutton', { name: 'Reps' }).fill('13'); - await page.getByRole('button', { name: 'Log Set' }).click(); - await page.getByRole('spinbutton', { name: 'Weight (kg)' }).click(); - await page.getByRole('spinbutton', { name: 'Weight (kg)' }).fill('13'); - await page.getByRole('spinbutton', { name: 'Reps' }).click(); - await page.getByRole('spinbutton', { name: 'Reps' }).fill('14'); - await page.getByRole('button', { name: 'Log Set' }).click(); - await page.getByRole('button', { name: 'Finish' }).click(); - await page.getByRole('button', { name: 'Confirm' }).click(); -}); \ No newline at end of file