diff --git a/server/default_exercises.csv b/server/default_exercises.csv index 0f25f53..8a9ed92 100644 --- a/server/default_exercises.csv +++ b/server/default_exercises.csv @@ -1,43 +1,43 @@ -name,type,bodyWeightPercentage,isUnilateral -Air Squats,BODYWEIGHT,1.0,false -Barbell Row,STRENGTH,0,false -Bench Press,STRENGTH,0,false -Bicep Curl,STRENGTH,0,true -Bulgarian Split-Squat Jumps,BODYWEIGHT,1.0,true -Bulgarian Split-Squats,BODYWEIGHT,1.0,true -Burpees,BODYWEIGHT,1.0,false -Calf Raise,STRENGTH,0,true -Chin-Ups,BODYWEIGHT,1.0,false -Cycling,CARDIO,0,false -Deadlift,STRENGTH,0,false -Dips,BODYWEIGHT,1.0,false -Dumbbell Curl,STRENGTH,0,true -Dumbbell Shoulder Press,STRENGTH,0,true -Face Pull,STRENGTH,0,false -Front Squat,STRENGTH,0,false -Hammer Curl,STRENGTH,0,true -Handstand,BODYWEIGHT,1.0,false -Hip Thrust,STRENGTH,0,false -Jump Rope,CARDIO,0,false -Lat Pulldown,STRENGTH,0,false -Leg Extension,STRENGTH,0,true -Leg Press,STRENGTH,0,false -Lunges,BODYWEIGHT,1.0,true -Mountain Climbers,CARDIO,0,false -Muscle-Up,BODYWEIGHT,1.0,false -Overhead Press,STRENGTH,0,false -Plank,STATIC,0,false -Pull-Ups,BODYWEIGHT,1.0,false -Push-Ups,BODYWEIGHT,0.65,false -Romanian Deadlift,STRENGTH,0,false -Rowing,CARDIO,0,false -Running,CARDIO,0,false -Russian Twist,BODYWEIGHT,0,false -Seated Cable Row,STRENGTH,0,false -Side Plank,STATIC,0,true -Sissy Squats,BODYWEIGHT,1.0,false -Sprint,CARDIO,0,false -Squat,STRENGTH,0,false -Treadmill,CARDIO,0,false -Tricep Extension,STRENGTH,0,false -Wall-Sit,STATIC,0,false +name,name_ru,type,bodyWeightPercentage,isUnilateral +Air Squats,Приседания,BODYWEIGHT,1.0,false +Barbell Row,Тяга штанги в наклоне,STRENGTH,0,false +Bench Press,Жим лежа,STRENGTH,0,false +Bicep Curl,Подъем на бицепс,STRENGTH,0,true +Bulgarian Split-Squat Jumps,Болгарские сплит-прыжки,BODYWEIGHT,1.0,true +Bulgarian Split-Squats,Болгарские сплит-приседания,BODYWEIGHT,1.0,true +Burpees,Берпи,BODYWEIGHT,1.0,false +Calf Raise,Подъем на носки,STRENGTH,0,true +Chin-Ups,Подтягивания обратным хватом,BODYWEIGHT,1.0,false +Cycling,Велосипед,CARDIO,0,false +Deadlift,Становая тяга,STRENGTH,0,false +Dips,Отжимания на брусьях,BODYWEIGHT,1.0,false +Dumbbell Curl,Сгибания рук с гантелями,STRENGTH,0,true +Dumbbell Shoulder Press,Жим гантелей сидя,STRENGTH,0,true +Face Pull,Тяга к лицу,STRENGTH,0,false +Front Squat,Фронтальный присед,STRENGTH,0,false +Hammer Curl,Сгибания "Молот",STRENGTH,0,true +Handstand,Стойка на руках,BODYWEIGHT,1.0,false +Hip Thrust,Ягодичный мостик,STRENGTH,0,false +Jump Rope,Скакалка,CARDIO,0,false +Lat Pulldown,Тяга верхнего блока,STRENGTH,0,false +Leg Extension,Разгибание ног в тренажере,STRENGTH,0,true +Leg Press,Жим ногами,STRENGTH,0,false +Lunges,Выпады,BODYWEIGHT,1.0,true +Mountain Climbers,Альпинист,CARDIO,0,false +Muscle-Up,Выход силой,BODYWEIGHT,1.0,false +Overhead Press,Армейский жим,STRENGTH,0,false +Plank,Планка,STATIC,0,false +Pull-Ups,Подтягивания,BODYWEIGHT,1.0,false +Push-Ups,Отжимания,BODYWEIGHT,0.65,false +Romanian Deadlift,Румынская тяга,STRENGTH,0,false +Rowing,Гребля,CARDIO,0,false +Running,Бег,CARDIO,0,false +Russian Twist,Русский твист,BODYWEIGHT,0,false +Seated Cable Row,Тяга блока к поясу,STRENGTH,0,false +Side Plank,Боковая планка,STATIC,0,true +Sissy Squats,Сисси-приседания,BODYWEIGHT,1.0,false +Sprint,Спринт,CARDIO,0,false +Squat,Приседания со штангой,STRENGTH,0,false +Treadmill,Беговая дорожка,CARDIO,0,false +Tricep Extension,Разгибание рук на трицепс,STRENGTH,0,false +Wall-Sit,Стульчик у стены,STATIC,0,false diff --git a/server/prisma/test.db b/server/prisma/test.db index b03caa9..df7a0ba 100644 Binary files a/server/prisma/test.db and b/server/prisma/test.db differ diff --git a/server/src/controllers/auth.controller.ts b/server/src/controllers/auth.controller.ts index ca474b4..0e7ab1b 100644 --- a/server/src/controllers/auth.controller.ts +++ b/server/src/controllers/auth.controller.ts @@ -83,6 +83,21 @@ export class AuthController { } } + static async initializeAccount(req: any, res: Response) { + try { + const userId = req.user.userId; + const { language } = req.body; + if (!language) { + return sendError(res, 'Language is required', 400); + } + const user = await AuthService.initializeUser(userId, language); + return sendSuccess(res, { user }); + } catch (error: any) { + logger.error('Error in initializeAccount', { error }); + return sendError(res, error.message || 'Server error', 500); + } + } + static async getAllUsers(req: any, res: Response) { try { if (req.user.role !== 'ADMIN') { diff --git a/server/src/index.ts b/server/src/index.ts index 62e1113..4cb76de 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -65,7 +65,7 @@ async function ensureAdminUser() { console.info(`ℹ️ Admin user exists but with different email: ${existingAdmin.email}. Expected: ${adminEmail}`); } // Even if admin exists, ensure exercises are seeded (will skip if already has them) - await AuthService.seedDefaultExercises(existingAdmin.id); + await AuthService.seedDefaultExercises(existingAdmin.id, 'en'); await prisma.$disconnect(); return; } @@ -77,12 +77,12 @@ async function ensureAdminUser() { email: adminEmail, password: hashed, role: 'ADMIN', - profile: { create: { weight: 70 } }, + profile: { create: { weight: 70, language: 'en' } }, }, }); // Seed exercises for new admin - await AuthService.seedDefaultExercises(admin.id); + await AuthService.seedDefaultExercises(admin.id, 'en'); console.info(`✅ Admin user created and exercises seeded (email: ${adminEmail})`); await prisma.$disconnect(); diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts index e538259..d6a98eb 100644 --- a/server/src/routes/auth.ts +++ b/server/src/routes/auth.ts @@ -16,6 +16,7 @@ router.use(authenticateToken); // Standard middleware now router.get('/me', AuthController.getCurrentUser); router.post('/change-password', validate(changePasswordSchema), AuthController.changePassword); router.patch('/profile', validate(updateProfileSchema), AuthController.updateProfile); +router.post('/initialize', AuthController.initializeAccount); // Admin routes router.get('/users', AuthController.getAllUsers); diff --git a/server/src/services/auth.service.ts b/server/src/services/auth.service.ts index a934cf7..bcd5fdb 100644 --- a/server/src/services/auth.service.ts +++ b/server/src/services/auth.service.ts @@ -67,17 +67,13 @@ export class AuthService { include: { profile: true } }); - // Seed default exercises - // Seed default exercises - await this.seedDefaultExercises(user.id); - const token = jwt.sign({ userId: user.id, role: user.role }, JWT_SECRET); const { password: _, ...userSafe } = user; return { user: userSafe, token }; } - static async seedDefaultExercises(userId: string) { + static async seedDefaultExercises(userId: string, language: string = 'en') { try { // Ensure env is loaded from root (in case server didn't restart) if (!process.env.DEFAULT_EXERCISES_CSV_PATH) { @@ -110,6 +106,8 @@ export class AuthService { const headers = lines[0].split(',').map(h => h.trim()); const exercisesToCreate = []; + const nameColumn = language === 'ru' ? 'name_ru' : 'name'; + for (let i = 1; i < lines.length; i++) { const cols = lines[i].split(',').map(c => c.trim()); if (cols.length < headers.length) continue; @@ -117,10 +115,12 @@ export class AuthService { const row: any = {}; headers.forEach((h, idx) => row[h] = cols[idx]); - if (row.name && row.type) { + const exerciseName = row[nameColumn] || row['name']; + + if (exerciseName && row.type) { exercisesToCreate.push({ userId, - name: row.name, + name: exerciseName, type: row.type, bodyWeightPercentage: row.bodyWeightPercentage ? parseFloat(row.bodyWeightPercentage) : 0, isUnilateral: row.isUnilateral === 'true', @@ -153,14 +153,34 @@ export class AuthService { } } + static async initializeUser(userId: string, language: string) { + // Update profile language + await prisma.userProfile.upsert({ + where: { userId }, + update: { language }, + create: { userId, language, weight: 70 } + }); + + // Seed exercises in that language + await this.seedDefaultExercises(userId, language); + + // Mark as first login done + await prisma.user.update({ + where: { id: userId }, + data: { isFirstLogin: false } + }); + + // Return updated user + return this.getUser(userId); + } + static async changePassword(userId: string, newPassword: string) { const hashed = await bcrypt.hash(newPassword, 10); await prisma.user.update({ where: { id: userId }, data: { - password: hashed, - isFirstLogin: false + password: hashed } }); } diff --git a/src/App.tsx b/src/App.tsx index 764acf8..455f32d 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,6 +8,7 @@ import AICoach from './components/AICoach'; import Plans from './components/Plans'; import Login from './components/Login'; import Profile from './components/Profile'; +import InitializeAccount from './components/InitializeAccount'; import { Language, User } from './types'; import { getSystemLanguage } from './services/i18n'; import { useAuth } from './context/AuthContext'; @@ -49,6 +50,10 @@ function App() { return ; } + if (currentUser?.isFirstLogin && location.pathname !== '/initialize') { + return ; + } + return (
{currentUser && ( @@ -66,6 +71,17 @@ function App() { ) } /> + + ) : ( + + ) + } /> } /> diff --git a/src/components/InitializeAccount.tsx b/src/components/InitializeAccount.tsx new file mode 100644 index 0000000..a5f107b --- /dev/null +++ b/src/components/InitializeAccount.tsx @@ -0,0 +1,99 @@ +import React, { useState } from 'react'; +import { initializeAccount } from '../services/auth'; +import { User, Language } from '../types'; +import { Globe, ArrowRight, Check } from 'lucide-react'; +import { t } from '../services/i18n'; + +interface InitializeAccountProps { + onInitialized: (user: User) => void; + language: Language; + onLanguageChange: (lang: Language) => void; +} + +const InitializeAccount: React.FC = ({ onInitialized, language, onLanguageChange }) => { + const [isSubmitting, setIsSubmitting] = useState(false); + const [error, setError] = useState(''); + + const handleInitialize = async () => { + setIsSubmitting(true); + setError(''); + const res = await initializeAccount(language); + if (res.success && res.user) { + onInitialized(res.user); + } else { + setError(res.error || 'Failed to initialize account'); + setIsSubmitting(false); + } + }; + + const languages: { code: Language; label: string; desc: string }[] = [ + { code: 'en', label: 'English', desc: t('init_lang_en_desc', language) }, + { code: 'ru', label: 'Русский', desc: t('init_lang_ru_desc', language) }, + ]; + + return ( +
+
+
+ +
+ +

+ {t('init_title', language)} +

+

+ {t('init_desc', language)} +

+ + {error && ( +
+ {error} +
+ )} + +
+ {languages.map((lang) => ( + + ))} +
+ + +
+
+ ); +}; + +export default InitializeAccount; diff --git a/src/components/Login.tsx b/src/components/Login.tsx index 1041335..cb76869 100644 --- a/src/components/Login.tsx +++ b/src/components/Login.tsx @@ -41,7 +41,7 @@ const Login: React.FC = ({ onLogin, language, onLanguageChange }) => if (tempUser && newPassword.length >= 4) { const res = await changePassword(tempUser.id, newPassword); if (res.success) { - const updatedUser = { ...tempUser, isFirstLogin: false }; + const updatedUser = { ...tempUser }; onLogin(updatedUser); } else { setError(res.error || t('change_pass_error', language)); diff --git a/src/services/auth.ts b/src/services/auth.ts index e5ccbe2..ed3c289 100644 --- a/src/services/auth.ts +++ b/src/services/auth.ts @@ -149,3 +149,19 @@ export const getMe = async (): Promise<{ success: boolean; user?: User; error?: return { success: false, error: 'Failed to fetch user' }; } }; +export const initializeAccount = async (language: string): Promise<{ success: boolean; user?: User; error?: string }> => { + try { + const res = await api.post>('/auth/initialize', { language }); + if (res.success && res.data) { + return { success: true, user: res.data.user }; + } + return { success: false, error: res.error }; + } catch (e: any) { + try { + const err = JSON.parse(e.message); + return { success: false, error: err.error || 'Failed to initialize account' }; + } catch { + return { success: false, error: 'Failed to initialize account' }; + } + } +}; diff --git a/src/services/i18n.ts b/src/services/i18n.ts index d0e5ba4..038c15f 100644 --- a/src/services/i18n.ts +++ b/src/services/i18n.ts @@ -36,6 +36,12 @@ const translations = { register_btn: 'Register', have_account: 'Already have an account? Login', need_account: 'Need an account? Register', + init_title: 'Setup Your Account', + init_desc: 'Welcome! Choose your preferred language.', + init_select_lang: 'Select Language', + init_start: 'Get Started', + init_lang_en_desc: 'GUI and default exercise names will be in English', + init_lang_ru_desc: 'GUI and default exercise names will be in Russian', // General date: 'Date', @@ -253,6 +259,12 @@ const translations = { register_btn: 'Зарегистрироваться', have_account: 'Уже есть аккаунт? Войти', need_account: 'Нет аккаунта? Регистрация', + init_title: 'Настройка аккаунта', + init_desc: 'Добро пожаловать! Выберите предпочтительный язык.', + init_select_lang: 'Выберите язык', + init_start: 'Начать работу', + init_lang_en_desc: 'Интерфейс и названия упражнений по умолчанию будут на английском', + init_lang_ru_desc: 'Интерфейс и названия упражнений по умолчанию будут на русском', // General date: 'Дата', diff --git a/tests/01_core_auth.spec.ts b/tests/01_core_auth.spec.ts index 9bcd3bf..ebb94b4 100644 --- a/tests/01_core_auth.spec.ts +++ b/tests/01_core_auth.spec.ts @@ -12,65 +12,61 @@ test.describe('I. Core & Authentication', () => { // Helper to handle first login if needed async function handleFirstLogin(page: any) { - // Wait for either Free Workout (already logged in/not first time) - // OR Change Password heading - // OR Error message - try { - const heading = page.getByRole('heading', { name: /Change Password/i }); - const dashboard = page.getByText('Free Workout'); - const loginButton = page.getByRole('button', { name: 'Login' }); + console.log('Starting handleFirstLogin helper...'); + const dashboard = page.getByText(/Free Workout|Свободная тренировка/i).first(); + const changePass = page.getByRole('heading', { name: /Change Password|Смена пароля/i }); + const initAcc = page.getByRole('heading', { name: /Setup Your Account|Настройка аккаунта/i }); - // Race condition: wait for one of these to appear - // We use a small polling or just wait logic. - // Playwright doesn't have "race" for locators easily without Promise.race - - // Simple approach: Check if Change Password appears within 5s - await expect(heading).toBeVisible({ timeout: 5000 }); - - // If we are here, Change Password is visible - console.log('Change Password screen detected. Handling...'); - await page.getByLabel('New Password').fill('StrongNewPass123!'); - await page.getByRole('button', { name: /Save|Change/i }).click(); - - // Now expect dashboard - await expect(dashboard).toBeVisible(); - console.log('Password changed. Dashboard visible.'); - } catch (e) { - // If Change Password didn't appear, maybe we are already at dashboard? - if (await page.getByText('Free Workout').isVisible()) { - console.log('Already at Dashboard.'); + for (let i = 0; i < 30; i++) { + if (await dashboard.isVisible()) { + console.log('Dashboard visible.'); return; } - // Check for login error - const error = page.locator('.text-error'); - if (await error.isVisible()) { - console.log('Login Error detected:', await error.textContent()); - throw new Error(`Login failed: ${await error.textContent()}`); + + if (await changePass.isVisible()) { + console.log('Change Password screen detected. Handling...'); + await page.getByLabel(/New Password|Новый пароль/i).fill('StrongNewPass123!'); + await page.getByRole('button', { name: /Save|Change|Сохранить/i }).click(); + // Wait a bit for transition + await page.waitForTimeout(1000); + continue; } - // Note: If none of the above, it might be a clean login that just worked fast or failed silently + + if (await initAcc.isVisible()) { + console.log('Initialization screen detected. Handling...'); + await page.getByRole('button', { name: /Get Started|Начать работу/i }).click(); + // Wait a bit for transition + await page.waitForTimeout(1000); + continue; + } + + await page.waitForTimeout(500); } + + // Final check with assertion to fail the test if not reached + await expect(dashboard).toBeVisible({ timeout: 5000 }); } // 1.1. A. Login - Successful Authentication test('1.1 Login - Successful Authentication', async ({ page, createUniqueUser }) => { const user = await createUniqueUser(); - await page.getByLabel('Email').fill(user.email); - await page.getByLabel('Password').fill(user.password); - await page.getByRole('button', { name: 'Login' }).click(); + await page.getByLabel(/Email/i).fill(user.email); + await page.getByLabel(/Password|Пароль/i).fill(user.password); + await page.getByRole('button', { name: /Login|Войти/i }).click(); await handleFirstLogin(page); // Expect redirection to dashboard await expect(page).not.toHaveURL(/\/login/); - await expect(page.getByText('Free Workout')).toBeVisible(); + await expect(page.getByText(/Free Workout|Свободная тренировка/i).first()).toBeVisible(); }); // 1.2. A. Login - Invalid Credentials test('1.2 Login - Invalid Credentials', async ({ page }) => { - await page.getByLabel('Email').fill('invalid@user.com'); - await page.getByLabel('Password').fill('wrongpassword'); - await page.getByRole('button', { name: 'Login' }).click(); + await page.getByLabel(/Email/i).fill('invalid@user.com'); + await page.getByLabel(/Password|Пароль/i).fill('wrongpassword'); + await page.getByRole('button', { name: /Login|Войти/i }).click(); await expect(page.getByText('Invalid credentials')).toBeVisible(); await expect(page.getByRole('button', { name: 'Login' })).toBeVisible(); @@ -79,31 +75,34 @@ test.describe('I. Core & Authentication', () => { test('1.3 & 1.4 Login - First-Time Password Change', async ({ page, createUniqueUser }) => { const user = await createUniqueUser(); - await page.getByLabel('Email').fill(user.email); - await page.getByLabel('Password').fill(user.password); - await page.getByRole('button', { name: 'Login' }).click(); + await page.getByLabel(/Email/i).fill(user.email); + await page.getByLabel(/Password|Пароль/i).fill(user.password); + await page.getByRole('button', { name: /Login|Войти/i }).click(); - await expect(page.getByRole('heading', { name: /Change Password/i }).first()).toBeVisible({ timeout: 10000 }); + await expect(page.getByRole('heading', { name: /Change Password|Смена пароля/i }).first()).toBeVisible({ timeout: 10000 }); // 1.4 Test short password - await page.getByLabel('New Password').fill('123'); - await page.getByRole('button', { name: /Save|Change/i }).click(); - await expect(page.getByText('Password too short')).toBeVisible(); + await page.getByLabel(/New Password|Новый пароль/i).fill('123'); + await page.getByRole('button', { name: /Save|Change|Сохранить/i }).click(); + await expect(page.getByText(/Password too short|Пароль слишком короткий/i)).toBeVisible(); // 1.3 Test successful change - await page.getByLabel('New Password').fill('StrongNewPass123!'); - await page.getByRole('button', { name: /Save|Change/i }).click(); + await page.getByLabel(/New Password|Новый пароль/i).fill('StrongNewPass123!'); + await page.getByRole('button', { name: /Save|Change|Сохранить/i }).click(); + + // Now we should be on Setup Account page + await expect(page.getByRole('heading', { name: /Setup Your Account|Настройка аккаунта/i })).toBeVisible(); + await page.getByRole('button', { name: /Get Started|Начать работу/i }).click(); // Now we should be logged in - await expect(page.getByText('Free Workout')).toBeVisible(); + await expect(page.getByText(/Free Workout|Свободная тренировка/i).first()).toBeVisible(); }); - // 1.5. A. Login - Language Selection (English) test('1.5 Login - Language Selection (English)', async ({ page }) => { await page.getByRole('combobox').selectOption('en'); - await expect(page.getByLabel('Email')).toBeVisible(); - await expect(page.getByRole('button', { name: 'Login' })).toBeVisible(); + await expect(page.getByLabel(/Email/i)).toBeVisible(); + await expect(page.getByRole('button', { name: /Login|Войти/i })).toBeVisible(); }); // 1.6. A. Login - Language Selection (Russian) @@ -116,26 +115,26 @@ test.describe('I. Core & Authentication', () => { test('1.7 Navigation - Desktop Navigation Rail', async ({ page, createUniqueUser }) => { const user = await createUniqueUser(); - await page.getByLabel('Email').fill(user.email); - await page.getByLabel('Password').fill(user.password); - await page.getByRole('button', { name: 'Login' }).click(); + await page.getByLabel(/Email/i).fill(user.email); + await page.getByLabel(/Password|Пароль/i).fill(user.password); + await page.getByRole('button', { name: /Login|Войти/i }).click(); await handleFirstLogin(page); // Set viewport to desktop await page.setViewportSize({ width: 1280, height: 720 }); - await expect(page.getByRole('button', { name: 'Tracker' }).first()).toBeVisible(); - await expect(page.getByRole('button', { name: 'Plans' }).first()).toBeVisible(); + await expect(page.getByRole('button', { name: /Tracker|Трекер/i }).first()).toBeVisible(); + await expect(page.getByRole('button', { name: /Plans|Планы/i }).first()).toBeVisible(); }); // 1.8. B. Navigation - Mobile Bottom Navigation Bar test('1.8 Navigation - Mobile Bottom Navigation Bar', async ({ page, createUniqueUser }) => { const user = await createUniqueUser(); - await page.getByLabel('Email').fill(user.email); - await page.getByLabel('Password').fill(user.password); - await page.getByRole('button', { name: 'Login' }).click(); + await page.getByLabel(/Email/i).fill(user.email); + await page.getByLabel(/Password|Пароль/i).fill(user.password); + await page.getByRole('button', { name: /Login|Войти/i }).click(); await handleFirstLogin(page); @@ -145,7 +144,44 @@ test.describe('I. Core & Authentication', () => { await page.waitForTimeout(500); // Allow layout transition // Verify visibility of mobile nav items - await expect(page.getByRole('button', { name: 'Tracker' }).last()).toBeVisible(); + await expect(page.getByRole('button', { name: /Tracker|Трекер/i }).last()).toBeVisible(); + }); + + // 1.9. C. Initialization - Russian Language Seeding + test('1.9 Initialization - Russian Language Seeding', async ({ page, createUniqueUser }) => { + const user = await createUniqueUser(); + + await page.getByLabel(/Email/i).fill(user.email); + await page.getByLabel(/Password|Пароль/i).fill(user.password); + await page.getByRole('button', { name: /Login|Войти/i }).click(); + + // Handle password change + await expect(page.getByRole('heading', { name: /Change Password|Смена пароля/i })).toBeVisible(); + await page.getByLabel(/New Password|Новый пароль/i).fill('StrongNewPass123!'); + await page.getByRole('button', { name: /Save|Change|Сохранить/i }).click(); + + // Handle initialization - Select Russian + await expect(page.getByRole('heading', { name: /Setup Your Account|Настройка аккаунта/i })).toBeVisible(); + await page.getByText('Русский').click(); + await page.getByRole('button', { name: /Начать работу|Get Started/i }).click(); + + // Expect dashboard + await expect(page.getByText('Свободная тренировка')).toBeVisible(); + + // Verify some exercise is in Russian + await page.getByText(/Свободная тренировка|Free Workout/i).first().click(); + await page.getByLabel(/Выберите упражнение|Select Exercise/i).click(); + + // "Air Squats" should be "Приседания" in suggestions + await expect(page.getByRole('button', { name: 'Приседания', exact: true })).toBeVisible(); + await page.getByRole('button', { name: 'Приседания', exact: true }).click(); + + // Verify it's selected in the input + const exerciseInput = page.getByLabel(/Выберите упражнение|Select Exercise/i); + await expect(exerciseInput).toHaveValue('Приседания'); + + // Verify "Log Set" button is now in Russian + await expect(page.getByRole('button', { name: /Записать подход|Log Set/i })).toBeVisible(); }); });