From 1c4b40b720bdb2e62a3ecd0259ff0da88fa8b154 Mon Sep 17 00:00:00 2001 From: Patryk Makuch Date: Sat, 18 Apr 2026 01:19:13 +0200 Subject: [PATCH 1/3] feat: add typecheck script to package.json Added two-step type checking: - nuxt typecheck: type-checks .vue and .ts files via vue-tsc - tsc --noEmit: ensures all project .ts files are covered Also added ignoreDeprecations to suppress TypeScript 7.0 deprecation warning. Co-Authored-By: Claude Haiku 4.5 --- package.json | 5 ++++- tsconfig.json | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 1671168..ebfca92 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "test:e2e": "playwright test", "test:e2e:ui": "playwright test --ui", "test:e2e:debug": "playwright test --debug", + "typecheck": "nuxt typecheck && tsc --noEmit --project tsconfig.json", "lint-staged": "lint-staged", "prepare": "husky" }, @@ -35,6 +36,7 @@ "@vue/test-utils": "^2.2.6", "@vueuse/components": "^11.1.0", "@vueuse/nuxt": "^11.1.0", + "chai": "^6.2.2", "eslint": "^9.14.0", "eslint-config-prettier": "^8.10.0", "eslint-plugin-prettier": "^4.2.1", @@ -46,8 +48,9 @@ "nuxt": "3.12.4", "nuxt-icon": "^0.6.10", "prettier": "^2.8.8", + "tinyspy": "^4.0.4", "typescript": "^5.9.3", - "vitest": "^0.26.3" + "vitest": "^3.2.4" }, "dependencies": { "@nuxtjs/i18n": "^8.5.5", diff --git a/tsconfig.json b/tsconfig.json index 8bf3a89..1779205 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,7 @@ "extends": "./.nuxt/tsconfig.json", "includes": ["types/*.d.ts"], "compilerOptions": { + "ignoreDeprecations": "6.0", "lib": ["es5", "es6", "dom", "dom.iterable"], "esModuleInterop": true, "moduleResolution": "node", From 076ea3b04437083f6bc36f7643662c50f300d799 Mon Sep 17 00:00:00 2001 From: Patryk Makuch Date: Mon, 25 May 2026 23:45:36 +0200 Subject: [PATCH 2/3] fix: add test fix types replace to supabase --- .github/actions/install/action.yml | 9 +- .github/copilot-instructions.md | 70 +- .github/workflows/e2e.yml | 5 +- .github/workflows/main.yml | 2 +- CLAUDE.md | 238 + E2E_TESTS_SETUP.md | 51 +- README.md | 86 +- layouts/default.vue | 1 - layouts/game.vue | 6 +- modules/core/composables/useImage.ts | 2 +- modules/core/composables/useStopwatch.ts | 6 +- modules/core/index.ts | 2 + modules/core/store/images.ts | 20 +- modules/core/types/index.ts | 10 +- modules/game/components/Piece.vue | 22 +- modules/game/components/SelectImageModal.vue | 9 +- modules/game/composables/useEventGame.ts | 26 +- modules/game/composables/useShuffle.ts | 5 +- modules/game/index.ts | 2 + modules/game/pages/game/[...id].vue | 12 +- modules/game/types/index.ts | 4 +- modules/game/views/GameView.vue | 31 +- modules/game/views/MainView.vue | 22 +- modules/ui/index.ts | 2 + nuxt.config.ts | 7 +- package.json | 10 +- pnpm-lock.yaml | 10103 ++++++++++------- pnpm-workspace.yaml | 6 + scripts/uploadImages/.gitignore | 2 + scripts/uploadImages/README.md | 57 + scripts/uploadImages/upload.ts | 156 + server/api/get-image.ts | 33 +- server/api/get-images.ts | 52 +- server/utils/adapters/image-adapter.ts | 6 + server/utils/adapters/supabase-adapter.ts | 159 + specs/unit-tests.md | 319 + tests/e2e/README.md | 56 +- tests/e2e/advanced.spec.ts | 12 +- tests/e2e/fixtures.ts | 5 +- tests/e2e/main.spec.ts | 24 +- tests/e2e/multi-select.spec.ts | 202 +- tests/units/mocks/fixtures.ts | 49 + tests/units/setup.ts | 6 + tests/units/useEventGame.test.ts | 287 + tests/units/useImage.test.ts | 118 + tests/units/useShuffle.test.ts | 214 + tests/units/useStopwatch.test.ts | 95 + tsconfig.json | 12 +- types/nuxt-icon.d.ts | 9 + vitest.config.js | 13 +- 50 files changed, 8259 insertions(+), 4396 deletions(-) create mode 100644 CLAUDE.md create mode 100644 pnpm-workspace.yaml create mode 100644 scripts/uploadImages/.gitignore create mode 100644 scripts/uploadImages/README.md create mode 100644 scripts/uploadImages/upload.ts create mode 100644 server/utils/adapters/image-adapter.ts create mode 100644 server/utils/adapters/supabase-adapter.ts create mode 100644 specs/unit-tests.md create mode 100644 tests/units/mocks/fixtures.ts create mode 100644 tests/units/setup.ts create mode 100644 tests/units/useEventGame.test.ts create mode 100644 tests/units/useImage.test.ts create mode 100644 tests/units/useShuffle.test.ts create mode 100644 tests/units/useStopwatch.test.ts create mode 100644 types/nuxt-icon.d.ts diff --git a/.github/actions/install/action.yml b/.github/actions/install/action.yml index 599d4f1..3992fc6 100644 --- a/.github/actions/install/action.yml +++ b/.github/actions/install/action.yml @@ -1,13 +1,12 @@ -name: '🛠️ Install' -description: 'Setup Node, pnpm & install dependencies' +name: "🛠️ Install" +description: "Setup Node, pnpm & install dependencies" runs: - using: 'composite' + using: "composite" steps: - name: Install pnpm uses: pnpm/action-setup@v4 with: - version: 9 run_install: false - name: Install Node.js @@ -18,4 +17,4 @@ runs: - name: Install dependencies shell: bash - run: pnpm install \ No newline at end of file + run: pnpm install diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index e627245..fdaadab 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -14,19 +14,23 @@ Plik zawiera kompletny przegląd projektu z następującymi sekcjami: ### 1. **Informacje Ogólne** (10 linii) + - Nazwa, typ, framework, język - Data i status projektu ### 2. **Opis Projektu** (20 linii) + - Koncepcja gry - 7 głównych cech ### 3. **Architektura** (40 linii) + - Modułowa struktura (core, game, ui) - Przepływ danych - Organizacja kodu ### 4. **Techniczny Stack** (50 linii) + - Framework & Build tools - State management - Styling & CSS @@ -34,11 +38,13 @@ Plik zawiera kompletny przegląd projektu z następującymi sekcjami: - Package manager ### 5. **Struktura Projektu** (60 linii) + - Pełna lista katalogów - Opis plików - Organizacja testów i dokumentacji ### 6. **Główne Funkcjonalności** (40 linii) + - Strona główna - Rozgrywka - Modals @@ -46,36 +52,43 @@ Plik zawiera kompletny przegląd projektu z następującymi sekcjami: - Internacjonalizacja ### 7. **API Integration** (30 linii) + - Pexels API endpoints - Formaty danych - Struktura odpowiedzi ### 8. **State Management** (20 linii) + - Pinia store - State, getters, actions ### 9. **Composables** (40 linii) + - useImage.ts - useStopwatch.ts - useEventGame.ts - useShuffle.ts ### 10. **Komendy Dostępne** (35 linii) + - Development commands - Testing commands - Code quality commands ### 11. **E2E Testy** (40 linii) + - Statystyka testów - Pokrytość funkcjonalności - Test suite description ### 12. **Metryki Projektu** (25 linii) + - Wielkość kodu - Liczba komponentów - Liczba plików ### 13. **Best Practices** (25 linii) + - Code quality - Development practices - Testing approach @@ -83,40 +96,49 @@ Plik zawiera kompletny przegląd projektu z następującymi sekcjami: - Maintainability ### 14. **Jak Zacząć** (20 linii) + - Setup instructions - Development server - How to play - Testing instructions ### 15. **Dokumentacja** (20 linii) + - Lista dokumentów w projekcie - Linki do zasobów online ### 16. **Workflow Dewelopera** (30 linii) + - Dodawanie funkcji - Bugfixing process - Code review checklist ### 17. **Znane Ograniczenia** (15 linii) + - Bieżące ograniczenia - Przyszłe ulepszenia ### 18. **Deployment** (20 linii) + - Production build - Netlify/Vercel deployment - Docker option ### 19. **Support & Issues** (15 linii) + - Troubleshooting - Git workflow ### 20. **Podsumowanie** (15 linii) + - Highlights projektu ### 21. **Historia Projektu** (10 linii) + - Timeline событий ### 22. **Informacje Finalne** (15 linii) + - Autor - Licencja - Status @@ -145,16 +167,16 @@ Plik zawiera kompletny przegląd projektu z następującymi sekcjami: ## 📊 Statystyka Podsumowania -| Metryka | Wartość | -|---------|---------| -| Całkowite linii | 656 | -| Rozmiar | 15.7 KB | -| Sekcji | 22 | -| Tabel | 5 | -| Code blocks | 15 | -| Listy | 30+ | -| Linki | 20+ | -| Emojis | 100+ | +| Metryka | Wartość | +| --------------- | ------- | +| Całkowite linii | 656 | +| Rozmiar | 15.7 KB | +| Sekcji | 22 | +| Tabel | 5 | +| Code blocks | 15 | +| Listy | 30+ | +| Linki | 20+ | +| Emojis | 100+ | --- @@ -174,22 +196,26 @@ PerplexImage/ ## 🎓 Jak Używać Podsumowania ### Dla Nowych Developerów + 1. Przeczytaj sekcję "Opis Projektu" 2. Sprawdź "Jak Zacząć" 3. Uruchom dev server 4. Przeczytaj "Architektura" ### Dla Code Review + 1. Sprawdź "Best Practices" 2. Przeczytaj "Workflow Dewelopera" 3. Weryfikuj E2E testy ### Dla Maintenance + 1. Sprawdź "Techniczny Stack" 2. Czytaj "Znane Ograniczenia" 3. Patrz "Przyszłe Ulepszenia" ### Dla Deployment + 1. Przeczytaj "Deployment" sekcję 2. Sprawdź "Production Ready" status 3. Weryfikuj testy @@ -199,30 +225,35 @@ PerplexImage/ ## ✨ Highlights Projektu ### 🏆 Technologia + - Najnowszy Nuxt 3 - TypeScript type-safety - Vue 3 Composition API - Tailwind CSS responsive ### 🎮 Gra + - 3 poziomy trudności - Drag & drop - Timer & move counter - Responsywny design ### ✅ Testowanie + - 51 E2E testów Playwright - Cross-browser testing - Responsive viewport tests - 100% coverage głównych funkcji ### 📚 Dokumentacja + - Kompletna architektura - API documentation - Setup guides - Troubleshooting ### 🚀 Production Ready + - Optimized build - Deployment ready - Docker support @@ -233,21 +264,25 @@ PerplexImage/ ## 📝 Sekcje do Czytania ### Dla Zrozumienia Projektu + 1. **Opis Projektu** - Czym to jest 2. **Architektura** - Jak to jest zbudowane 3. **Główne Funkcjonalności** - Co to robi ### Dla Setup & Development + 1. **Jak Zacząć** - Pierwsza konfiguracja 2. **Komendy Dostępne** - Útilne skrypty 3. **Workflow Dewelopera** - Procesy ### Dla Testowania + 1. **E2E Testy** - Jakie testy są dostępne 2. **Jak Zacząć** > Testing sekcja 3. **E2E_TESTS_SETUP.md** - Szczegółowy guide ### Dla Deployment + 1. **Deployment** - Jak wdrażać 2. **Status Projektu** - Production readiness 3. **Support & Issues** - Troubleshooting @@ -269,13 +304,13 @@ Plik `PROJECT_SUMMARY.md` służy jako: ## 🔗 Powiązane Dokumenty -| Dokument | Cel | Linki | -|----------|-----|-------| -| PROJECT_SUMMARY.md | Pełne podsumowanie | ← TEN PLIK | -| E2E_TESTS_SETUP.md | Quick start testy | Setup guide | -| README.md | Original starter | Setup basics | -| tests/e2e/README.md | Test dokumentacja | Test details | -| playwright.config.ts | Test config | Konfiguracja | +| Dokument | Cel | Linki | +| -------------------- | ------------------ | ------------ | +| PROJECT_SUMMARY.md | Pełne podsumowanie | ← TEN PLIK | +| E2E_TESTS_SETUP.md | Quick start testy | Setup guide | +| README.md | Original starter | Setup basics | +| tests/e2e/README.md | Test dokumentacja | Test details | +| playwright.config.ts | Test config | Konfiguracja | --- @@ -316,6 +351,7 @@ git commit -m "docs: Add comprehensive project summary" **Status:** ✅ Kompletny Projekt PerplexImage ma teraz: + - ✅ Kompletny kod (2000+ linii) - ✅ Kompletne testy E2E (51 testów) - ✅ Kompletną dokumentację (656 linii) diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 975d1d2..76ec5ee 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -12,9 +12,9 @@ on: workflow_dispatch: inputs: branch: - description: 'Branch do przetestowania' + description: "Branch do przetestowania" required: false - default: 'main' + default: "main" jobs: e2e: @@ -76,4 +76,3 @@ jobs: name: test-results-${{ github.run_id }} path: test-results/ retention-days: 7 - diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 835c02a..8bd0b2c 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,4 +29,4 @@ jobs: uses: ./.github/actions/install - name: Build - run: pnpm build \ No newline at end of file + run: pnpm build diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..9fe9d0a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,238 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +**PerplexImage** is a puzzle game built with Nuxt 3 where players complete image puzzles fetched from the Pexels API. The game supports three difficulty levels (9x13, 15x23, 20x30 tiles) with drag-and-drop gameplay, timers, move counters, and responsive design. + +**Tech Stack:** + +- **Framework:** Nuxt 3 with Vue 3 Composition API +- **Language:** TypeScript +- **State Management:** Pinia +- **Styling:** Tailwind CSS +- **UI Icons:** nuxt-icon +- **Internationalization:** @nuxtjs/i18n +- **Testing:** Playwright (E2E), Vitest (Unit) +- **API:** Pexels API for puzzle images +- **Code Quality:** ESLint, Prettier +- **Pre-commit Hooks:** Husky + lint-staged + +## Common Development Commands + +### Setup + +```bash +pnpm install # Install dependencies +pnpm postinstall # Nuxt prepare (auto-run after install) +``` + +### Development + +```bash +pnpm dev # Start dev server (http://localhost:3000) +pnpm build # Build for production +pnpm preview # Preview production build locally +``` + +### Code Quality + +```bash +pnpm lint # Run ESLint check +pnpm lintfix # Auto-fix ESLint + Prettier issues +pnpm lint:eslint # ESLint only +pnpm lint:prettier # Prettier check only +``` + +### Testing + +```bash +pnpm test # Run unit tests (Vitest) +pnpm test:e2e # Run E2E tests (Playwright) +pnpm test:e2e:ui # Run E2E tests with interactive UI +pnpm test:e2e:debug # Run E2E tests in debug mode +``` + +### Troubleshooting + +```bash +# If tests fail due to missing browsers +npx playwright install + +# View test report after running tests +npx playwright show-report +``` + +## Architecture & Module Structure + +The project is organized into three core modules under `/modules`: + +### `/modules/core` — Shared utilities and state + +- **store/** — Pinia store for image management and game state (`useImagesStore`) +- **composables/** — Reusable logic (`useImage`, `useStopwatch`) +- **types/** — TypeScript interfaces and types +- **components/** — Shared UI components +- **constants/** — Application constants + +### `/modules/game` — Game-specific features + +- **pages/** — Nuxt pages (`index.vue` for home, `game/[...id].vue` for game) +- **views/** — Page-level components (`MainView`, `GameView`) +- **components/** — Game UI components (`Piece`, `GameSidebar`, `SelectImageModal`, `PauseModal`, `FinishModal`) +- **composables/** — Game logic (`useEventGame`, `useShuffle`) +- **types/** — Game-specific types + +### `/modules/ui` — Reusable UI components + +- **components/** — Generic UI components (`UiButton`, `UiModal`, `UiAdvancedSelect`) + +### Other directories + +- `/assets` — Images, fonts, and static assets +- `/layouts` — Nuxt layout templates +- `/server/api` — Backend API routes (proxies Pexels API) +- `/tests/e2e` — Playwright E2E tests +- `/public` — Static files served directly + +## Key Patterns & Conventions + +### State Management (Pinia) + +State is centralized in `modules/core/store/images.ts`. Actions handle: + +- Fetching images from the API +- Managing selected puzzle image +- Shuffling and storing puzzle pieces + +Access the store in components using: + +```typescript +const store = useImagesStore(); +``` + +### Composables + +- `useImage()` — Image loading and processing +- `useStopwatch()` — Timer functionality +- `useEventGame()` — Game event handling +- `useShuffle()` — Puzzle piece shuffling + +### Types + +Core types are defined in: + +- `modules/core/types/index.ts` — Shared types (`PexelPhoto`, `ImagePieces`, `Difficulty`) +- `modules/game/types/index.ts` — Game-specific types + +### API Integration + +- API routes are defined in `/server/api` and proxy the Pexels API +- Configured via `PEXELS_API_KEY` environment variable in `nuxt.config.ts` +- Security headers (CSP, X-Frame-Options, etc.) are enforced in `routeRules` + +### Styling + +- Tailwind CSS is used for all styling +- Components use class-based styling (no scoped CSS) +- Responsive design is tested across mobile (375x667), tablet (768x1024), and desktop (1920x1080) viewports + +### Internationalization (i18n) + +- Configured in `i18n.config.ts` +- Used in components for multi-language support +- Default language can be set in config + +## Testing + +### E2E Tests (Playwright) + +Located in `/tests/e2e/`: + +- `main.spec.ts` — Core functionality tests (31+ tests) +- `advanced.spec.ts` — Advanced tests with fixtures (14 test groups) +- `fixtures.ts` — Reusable test fixtures for common flows + +**Test Coverage:** + +- Home page interaction and image selection +- Game gameplay (drag/drop, pause, timer) +- Responsive design across viewports +- Error handling and network timeouts +- Performance (page load times) + +**Run tests:** + +```bash +pnpm test:e2e # Headless across all browsers +pnpm test:e2e:ui # Interactive test runner +pnpm test:e2e:debug # Debug mode with inspector +npx playwright test --headed # Visible browser +npx playwright test --project=chromium # Single browser +``` + +### Unit Tests (Vitest) + +- Configured in `vitest.config.js` +- Run with `pnpm test` +- Uses jsdom environment + +### Pre-commit Hooks + +- Husky runs lint-staged before commits +- Only lints files in `/modules/**/*.{ts,vue}` +- Failing lints block commits + +## Development Workflow + +### Adding Features + +1. Create components or logic in the appropriate module +2. Use Pinia store for shared state +3. Extract reusable logic into composables +4. Write E2E tests in `/tests/e2e` to verify user flows +5. Run linting: `pnpm lintfix` +6. Run tests: `pnpm test:e2e:ui` + +### Debugging + +- Use `pnpm test:e2e:debug` to step through E2E tests +- Use browser DevTools in dev mode (`pnpm dev`) +- Check Playwright test reports: `npx playwright show-report` + +### Code Review Checklist + +- [ ] TypeScript types are proper (no `any`) +- [ ] Component props are typed +- [ ] Composable return types are explicit +- [ ] No unused imports or variables +- [ ] Linting passes: `pnpm lint` +- [ ] E2E tests pass: `pnpm test:e2e` +- [ ] Responsive design tested on mobile/tablet/desktop + +## Environment Variables + +Required for development: + +``` +PEXELS_API_KEY=your_pexels_api_key +``` + +This is set in `nuxt.config.ts` as `runtimeConfig.pexelsApiKey` and accessed via `$fetch` in API routes. + +## Important Notes + +- **Module Auto-registration:** Modules under `/modules` are auto-imported via Nuxt conventions. Module `index.ts` files define what's exported. +- **Path Aliases:** Use `@core`, `@ui`, `@game` in imports (configured in `vitest.config.js` and Nuxt defaults) +- **CSS Nesting:** Tailwind is configured with autoprefixer; no need for vendor prefixes +- **Game Logic:** Puzzle piece shuffling and drag-drop interaction is handled in composables, separate from UI components for testability +- **API Proxy:** Backend API routes prevent direct frontend-to-Pexels communication and enforce security headers + +## TypeScript Standards + +- Every implementation task must pass `pnpm typecheck` before considered done. +- No `any` types allowed. Always use explicit types. If the correct type is unclear, ask — never guess. +- When fixing or implementing features, look for opportunities to improve TypeScript correctness (e.g. + tighten loose types, replace type assertions with proper narrowing, add missing return types). Apply + these if the improvement is clear and safe. diff --git a/E2E_TESTS_SETUP.md b/E2E_TESTS_SETUP.md index e136927..d1a3339 100644 --- a/E2E_TESTS_SETUP.md +++ b/E2E_TESTS_SETUP.md @@ -7,6 +7,7 @@ Playwright E2E tests have been successfully added to the PerplexImage project! ## 📦 What Was Added ### Files Created: + 1. **playwright.config.ts** - Playwright configuration 2. **tests/e2e/main.spec.ts** - Core functionality tests (31+ tests) 3. **tests/e2e/advanced.spec.ts** - Advanced tests with fixtures (14 test groups) @@ -14,9 +15,11 @@ Playwright E2E tests have been successfully added to the PerplexImage project! 5. **tests/e2e/README.md** - Detailed test documentation ### Package Updated: + - **@playwright/test** v1.58.2 added to devDependencies ### Scripts Added to package.json: + - `pnpm test:e2e` - Run all tests - `pnpm test:e2e:ui` - Run tests with interactive UI - `pnpm test:e2e:debug` - Run tests in debug mode @@ -30,6 +33,7 @@ pnpm test:e2e ``` This will: + - Start the dev server automatically - Run tests on Chromium, Firefox, and WebKit browsers - Generate an HTML report @@ -41,6 +45,7 @@ pnpm test:e2e:ui ``` This opens an interactive test runner where you can: + - See tests executing in real-time - Inspect elements on the page - Debug individual tests @@ -55,6 +60,7 @@ npx playwright show-report ``` Opens an HTML report with: + - Test results and timing - Screenshots of failures - Network traces @@ -87,6 +93,7 @@ npx playwright test --headed ### Test Categories: **Home Page & Image Selection (6 tests)** + - Load home page - Display difficulty options - Select difficulty level @@ -95,6 +102,7 @@ npx playwright test --headed - Start game with selections **Game Page & Puzzle Gameplay (5 tests)** + - Load game page with puzzle pieces - Display game sidebar with timer - Allow dragging puzzle pieces @@ -102,40 +110,49 @@ npx playwright test --headed - Update move counter **Game Controls & Navigation (2 tests)** + - Navigate back to home page - Display game completion modal **Responsive Design (3 tests)** + - Mobile viewport (375x667) - Tablet viewport (768x1024) - Desktop viewport (1920x1080) **Error Handling (2 tests)** + - Handle loading errors gracefully - Handle network timeout gracefully **Performance (2 tests)** + - Load home page within acceptable time - Load game page within acceptable time **Advanced Game Flow (2 tests with fixtures)** + - Complete game flow from home to game - Test all difficulty levels **Advanced Game Interaction (4 tests with fixtures)** + - Test piece interaction and movement - Test pause and resume functionality - Test timer functionality - Test move counter **UI & UX Tests (2 tests with fixtures)** + - Test responsive game layout - Test image selection flow **State Management (1 test with fixtures)** + - Test image selection persistence **Navigation Tests (2 tests with fixtures)** + - Test back navigation from game to home - Test direct navigation with ID @@ -154,22 +171,26 @@ npx playwright test --headed ## 📚 Test Structure Example ```typescript -test('should start game with selected image and difficulty', async ({ page }) => { - await page.goto('/'); - await page.waitForLoadState('networkidle'); - +test("should start game with selected image and difficulty", async ({ + page, +}) => { + await page.goto("/"); + await page.waitForLoadState("networkidle"); + // Select difficulty level const option15x23 = page.locator('input[value="15x23"]'); await option15x23.click(); - + // Start game - const startBtn = page.locator('button').filter({ hasText: /Start|Play|Begin/i }); + const startBtn = page + .locator("button") + .filter({ hasText: /Start|Play|Begin/i }); if (await startBtn.isVisible()) { await startBtn.click(); await page.waitForURL(/\/game\//); - + // Verify game started - expect(page.url()).toContain('/game/'); + expect(page.url()).toContain("/game/"); } }); ``` @@ -179,7 +200,7 @@ test('should start game with selected image and difficulty', async ({ page }) => ### Using Custom Fixtures ```typescript -test('complete game flow', async ({ +test("complete game flow", async ({ page, navigateToHome, selectDifficulty9x13, @@ -188,15 +209,15 @@ test('complete game flow', async ({ await navigateToHome(); await selectDifficulty9x13(); await startGame(); - - expect(page.url()).toContain('/game/'); + + expect(page.url()).toContain("/game/"); }); ``` ### Taking Screenshots ```typescript -await page.screenshot({ path: 'screenshot.png' }); +await page.screenshot({ path: "screenshot.png" }); ``` ### Generating Locators @@ -220,24 +241,29 @@ This opens a tool to interactively generate test code. ## 🐛 Troubleshooting ### Tests timeout + ```bash # Increase timeout npx playwright test --timeout=60000 ``` ### Browser not starting + ```bash # Install browsers npx playwright install ``` ### API fails + Check that: + - Dev server is running on http://localhost:3000 - Pexels API key is valid - Internet connection is active ### Tests are too slow + ```bash # Run on specific browser only npx playwright test --project=chromium @@ -272,4 +298,3 @@ npx playwright test --project=chromium Created: February 23, 2026 Playwright Version: 1.58.2 Project: PerplexImage (Nuxt 3) - diff --git a/README.md b/README.md index d69a115..bc801a7 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,80 @@ -# Nuxt 3 Minimal Starter +# PerplexImage -Look at the [Nuxt 3 documentation](https://nuxt.com/docs/getting-started/introduction) to learn more. +Puzzle game built with Nuxt 3. Players complete image puzzles fetched from Pexels API with drag-and-drop gameplay, timers, and move counters. -## Setup - -Make sure to install the dependencies: +## Quick Start ```bash -# yarn -yarn install +pnpm install +pnpm dev +``` -# npm -npm install +Open [http://localhost:3000](http://localhost:3000) -# pnpm -pnpm install --shamefully-hoist -``` +## Features + +- **Three difficulty levels:** 9×13, 15×23, 20×30 tiles +- **Drag-and-drop gameplay:** Move puzzle pieces to complete the image +- **Real-time stats:** Timer and move counter +- **Responsive design:** Works on mobile, tablet, and desktop +- **Image selection:** Browse and select from Pexels API +- **Game controls:** Pause, reset, and view progress -## Development Server +## Tech Stack -Start the development server on http://localhost:3000 +- **Nuxt 3** + Vue 3 Composition API +- **TypeScript** for type safety +- **Pinia** for state management +- **Tailwind CSS** for styling +- **Playwright** for E2E testing +- **Vitest** for unit testing +- **Pexels API** for puzzle images + +## Commands ```bash -npm run dev +pnpm dev # Start dev server +pnpm build # Build for production +pnpm test # Run unit tests +pnpm test:e2e # Run E2E tests +pnpm test:e2e:ui # Interactive test UI +pnpm lint # Check code quality +pnpm lintfix # Auto-fix linting issues +pnpm typecheck # TypeScript validation ``` -## Production - -Build the application for production: +## Project Structure -```bash -npm run build +``` +/modules + /core - Shared utilities, store, types + /game - Game pages, components, logic + /ui - Generic UI components +/tests + /e2e - Playwright E2E tests + /units - Vitest unit tests +/server/api - Backend API routes (Pexels proxy) ``` -Locally preview production build: +## Environment Setup + +Requires `PEXELS_API_KEY` environment variable. Add to `.env`: -```bash -npm run preview ``` +PEXELS_API_KEY=your_pexels_api_key +``` + +## Development + +See [CLAUDE.md](./CLAUDE.md) for detailed architecture, testing patterns, and development workflow. + +## Testing + +All changes must pass: + +- TypeScript: `pnpm typecheck` +- Unit tests: `pnpm test` +- E2E tests: `pnpm test:e2e` +- Linting: `pnpm lint` -Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information. +Pre-commit hooks (Husky) enforce linting automatically. diff --git a/layouts/default.vue b/layouts/default.vue index 0aa8dcb..d68f214 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -17,7 +17,6 @@ - \ No newline at end of file + diff --git a/modules/core/composables/useImage.ts b/modules/core/composables/useImage.ts index 09ef204..d7d80ac 100644 --- a/modules/core/composables/useImage.ts +++ b/modules/core/composables/useImage.ts @@ -1,4 +1,4 @@ -export async function useImage() { +export function useImage() { const imageToBase64 = ( url: string, callback: (a: string | ArrayBuffer | null) => void diff --git a/modules/core/composables/useStopwatch.ts b/modules/core/composables/useStopwatch.ts index 437268f..73b41ff 100644 --- a/modules/core/composables/useStopwatch.ts +++ b/modules/core/composables/useStopwatch.ts @@ -1,4 +1,6 @@ -export async function useStopwatch() { +import { ref } from "vue"; + +export function useStopwatch() { const startTime = ref(0); let stopwatchInterval: string | number | NodeJS.Timeout | null | undefined = null; @@ -47,4 +49,4 @@ export async function useStopwatch() { updateStopwatch, displayTime, }; -} \ No newline at end of file +} diff --git a/modules/core/index.ts b/modules/core/index.ts index 7cb97dc..da42e73 100644 --- a/modules/core/index.ts +++ b/modules/core/index.ts @@ -1,5 +1,7 @@ +// @ts-expect-error Nuxt types not required for auto-discovery import { defineNuxtModule } from "@nuxt/kit"; import { resolve, join } from "path"; +// @ts-expect-error Nuxt schema types import type { Nuxt } from "@nuxt/schema"; export default defineNuxtModule({ diff --git a/modules/core/store/images.ts b/modules/core/store/images.ts index f3da769..9b5b7a0 100644 --- a/modules/core/store/images.ts +++ b/modules/core/store/images.ts @@ -1,14 +1,14 @@ import { defineStore } from "pinia"; import type { ImagePieces, - PexelPhoto, - ResponsePexel, + ImagePhoto, + ResponseImage, } from "~/modules/core/types"; interface State { shuffledPieces: ImagePieces[]; - photos: PexelPhoto[]; - selectedImage?: PexelPhoto; + photos: ImagePhoto[]; + selectedImage?: ImagePhoto; } export const useImagesStore = defineStore({ @@ -27,26 +27,22 @@ export const useImagesStore = defineStore({ this.selectedImage = this.photos[Math.round(Math.random() * this.photos.length)]; }, - async setSelectedImage(image: PexelPhoto) { + async setSelectedImage(image: ImagePhoto) { return Promise.resolve().then(() => { this.selectedImage = image; }); }, async getImages() { - const { media } = await $fetch( - `/api/get-images/?per_page=${100}` - ); + const { media } = await $fetch(`/api/get-images`); this.photos = media; await this.setSelectedImage( - media[Math.floor(Math.random() * media.length)] + media[Math.floor(Math.random() * media.length)] ); }, async getImage(options: { id: string | string[] }) { const id = Array.isArray(options.id) ? options.id[0] : options.id; - const image = await $fetch( - `/api/get-image/?id=${id}` - ); + const image = await $fetch(`/api/get-image/?id=${id}`); await this.setSelectedImage(image); }, diff --git a/modules/core/types/index.ts b/modules/core/types/index.ts index aa2b80a..a1e4e36 100644 --- a/modules/core/types/index.ts +++ b/modules/core/types/index.ts @@ -1,6 +1,9 @@ -export interface ResponsePexel { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type TODO = any; + +export interface ResponseImage { id: string; - media: PexelPhoto[]; + media: ImagePhoto[]; page: number; per_page: number; total_results: number; @@ -8,9 +11,8 @@ export interface ResponsePexel { next_page?: string; } -export interface PexelPhoto { +export interface ImagePhoto { alt: string; - avg_color: string; height: number; width: number; id: number; diff --git a/modules/game/components/Piece.vue b/modules/game/components/Piece.vue index a874ea3..a839e69 100644 --- a/modules/game/components/Piece.vue +++ b/modules/game/components/Piece.vue @@ -16,7 +16,13 @@ withDefaults(defineProps(), { selectedPositions: () => [], }); -const emit = defineEmits(["swap", "drag-enter", "drag-start", "drag-end", "select"]); +const emit = defineEmits([ + "swap", + "drag-enter", + "drag-start", + "drag-end", + "select", +]); const startDrag = ( evt: DragEvent & { dataTransfer?: DataTransfer | null }, @@ -31,7 +37,10 @@ const startDrag = ( evt.dataTransfer.effectAllowed = "move"; evt.dataTransfer.setData("position", position); // Przekaz listę zaznaczonych slotów, żeby drop wiedział o grupie - evt.dataTransfer.setData("selectedPositions", JSON.stringify(selectedPositions)); + evt.dataTransfer.setData( + "selectedPositions", + JSON.stringify(selectedPositions) + ); // Customowy ghost image gdy przeciagamy grupę if (selectedPositions.length > 1) { @@ -40,7 +49,11 @@ const startDrag = ( "position:fixed;top:-200px;left:-200px;display:flex;align-items:center;justify-content:center;background:rgba(59,130,246,0.9);color:white;font-weight:bold;font-size:16px;border-radius:10px;padding:8px 16px;pointer-events:none;box-shadow:0 4px 12px rgba(0,0,0,0.3);gap:6px;"; ghost.innerHTML = `x${selectedPositions.length}klocki`; document.body.appendChild(ghost); - evt.dataTransfer.setDragImage(ghost, ghost.offsetWidth / 2, ghost.offsetHeight / 2); + evt.dataTransfer.setDragImage( + ghost, + ghost.offsetWidth / 2, + ghost.offsetHeight / 2 + ); setTimeout(() => { if (document.body.contains(ghost)) document.body.removeChild(ghost); }, 0); @@ -52,7 +65,8 @@ const onDragEnter = (evt: DragEvent, position: number | string) => { evt.stopPropagation(); const draggedPosition = evt.dataTransfer?.getData("position"); - const rawSelectedPositions = evt.dataTransfer?.getData("selectedPositions") ?? ""; + const rawSelectedPositions = + evt.dataTransfer?.getData("selectedPositions") ?? ""; let selectedPositions: number[] = []; try { diff --git a/modules/game/components/SelectImageModal.vue b/modules/game/components/SelectImageModal.vue index e0fbf25..9d4cca8 100644 --- a/modules/game/components/SelectImageModal.vue +++ b/modules/game/components/SelectImageModal.vue @@ -1,9 +1,9 @@ @@ -36,7 +36,8 @@ const onClick = (data: PexelPhoto) => { Image + alt="Image" + /> diff --git a/modules/game/composables/useEventGame.ts b/modules/game/composables/useEventGame.ts index 8771d93..416b9c6 100644 --- a/modules/game/composables/useEventGame.ts +++ b/modules/game/composables/useEventGame.ts @@ -1,3 +1,4 @@ +import { computed } from "vue"; import type { GameData, GameProps } from "../types"; import type { ImagePieces, DisplacedMapping } from "~core/types"; import { Levels } from "~/modules/core/constants"; @@ -6,13 +7,13 @@ export interface Options { openModal: () => void; } -export async function useEventGame( +export function useEventGame( props: GameProps, data: GameData, { openModal }: Options ) { const { startStopwatch, stopStopwatch, resetStopwatch, displayTime } = - await useStopwatch(); + useStopwatch(); /** Liczba kolumn na podstawie wybranego poziomu */ const cols = computed(() => Number(Levels[props.level])); @@ -239,19 +240,28 @@ export async function useEventGame( if (!pieces) return; // Znajdź klocek, który aktualnie leży na slocie pos1 i pos2 - const idx1 = pieces.findIndex((item: ImagePieces) => item.position === pos1); - const idx2 = pieces.findIndex((item: ImagePieces) => item.position === pos2); + const idx1 = pieces.findIndex( + (item: ImagePieces) => item.position === pos1 + ); + const idx2 = pieces.findIndex( + (item: ImagePieces) => item.position === pos2 + ); if (idx1 === -1 || idx2 === -1) return; const newPieces = [...pieces]; // Zamień pozycje (sloty) między klockami const tmp = newPieces[idx1].position; - newPieces[idx1] = { ...newPieces[idx1], position: newPieces[idx2].position }; + newPieces[idx1] = { + ...newPieces[idx1], + position: newPieces[idx2].position, + }; newPieces[idx2] = { ...newPieces[idx2], position: tmp }; // Posortuj według slotu aby siatka renderowała się w porządku - data.shuffledPieces = [...newPieces].sort((a, b) => a.position - b.position); + data.shuffledPieces = [...newPieces].sort( + (a, b) => a.position - b.position + ); clearHighlight(); data.moves = data.moves + 1; clearSelection(); @@ -312,7 +322,9 @@ export async function useEventGame( return p; }); - data.shuffledPieces = [...newPieces].sort((a, b) => a.position - b.position); + data.shuffledPieces = [...newPieces].sort( + (a, b) => a.position - b.position + ); clearHighlight(); data.moves = data.moves + 1; // Po ruchu grupowym aktualizuj selectedPositions do nowych slotów diff --git a/modules/game/composables/useShuffle.ts b/modules/game/composables/useShuffle.ts index 4100a63..a957163 100644 --- a/modules/game/composables/useShuffle.ts +++ b/modules/game/composables/useShuffle.ts @@ -1,9 +1,10 @@ +import { computed } from "vue"; import { Levels, WIDTH_GAME } from "~/modules/core/constants"; import type { ImagePieces } from "~/modules/core/types"; import type { GameData, GameProps } from "../types"; -async function useShuffle(props: GameProps, data: GameData) { - const { shuffle } = await useImage(); +function useShuffle(props: GameProps, data: GameData) { + const { shuffle } = useImage(); const heightElement = computed( () => diff --git a/modules/game/index.ts b/modules/game/index.ts index 6a3771a..3313a1f 100644 --- a/modules/game/index.ts +++ b/modules/game/index.ts @@ -1,5 +1,7 @@ +// @ts-expect-error Nuxt types not required for auto-discovery import { defineNuxtModule } from "@nuxt/kit"; import { resolve, join } from "path"; +// @ts-expect-error Nuxt schema types import type { Nuxt } from "@nuxt/schema"; export default defineNuxtModule({ diff --git a/modules/game/pages/game/[...id].vue b/modules/game/pages/game/[...id].vue index 48f7730..9f76a1c 100644 --- a/modules/game/pages/game/[...id].vue +++ b/modules/game/pages/game/[...id].vue @@ -1,11 +1,21 @@ diff --git a/modules/core/store/images.ts b/modules/core/store/images.ts index 9b5b7a0..575303e 100644 --- a/modules/core/store/images.ts +++ b/modules/core/store/images.ts @@ -3,7 +3,7 @@ import type { ImagePieces, ImagePhoto, ResponseImage, -} from "~/modules/core/types"; +} from "@core/types"; interface State { shuffledPieces: ImagePieces[]; diff --git a/modules/game/components/FinishModal.vue b/modules/game/components/FinishModal.vue index 1527ba2..04ed74a 100644 --- a/modules/game/components/FinishModal.vue +++ b/modules/game/components/FinishModal.vue @@ -1,5 +1,5 @@