Refactored frontend directory
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m46s
Some checks failed
Chore App Build, Test, and Push Docker Images / build-and-push (push) Failing after 2m46s
This commit is contained in:
@@ -44,15 +44,15 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: "20.19.0"
|
node-version: "20.19.0"
|
||||||
cache: "npm"
|
cache: "npm"
|
||||||
cache-dependency-path: frontend/vue-app/package-lock.json
|
cache-dependency-path: frontend/package-lock.json
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
run: npm ci
|
run: npm ci
|
||||||
working-directory: frontend/vue-app
|
working-directory: frontend
|
||||||
|
|
||||||
- name: Run frontend unit tests
|
- name: Run frontend unit tests
|
||||||
run: npm run test:unit --if-present
|
run: npm run test:unit --if-present
|
||||||
working-directory: frontend/vue-app
|
working-directory: frontend
|
||||||
|
|
||||||
- name: Build Backend Docker Image
|
- name: Build Backend Docker Image
|
||||||
run: |
|
run: |
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Frontend Docker Image
|
- name: Build Frontend Docker Image
|
||||||
run: |
|
run: |
|
||||||
docker build -t git.ryankegel.com:3000/kegel/chores/frontend:${{ steps.vars.outputs.tag }} ./frontend/vue-app
|
docker build -t git.ryankegel.com:3000/kegel/chores/frontend:${{ steps.vars.outputs.tag }} ./frontend
|
||||||
|
|
||||||
- name: Log in to Registry
|
- name: Log in to Registry
|
||||||
uses: docker/login-action@v2
|
uses: docker/login-action@v2
|
||||||
|
|||||||
2
.github/agents/ui-planner.agent.md
vendored
2
.github/agents/ui-planner.agent.md
vendored
@@ -17,7 +17,7 @@ You are a Senior UI/UX Architect specializing in clean, highly reactive web appl
|
|||||||
|
|
||||||
This is the **Reward** app — a family chore/reward tracker. Vue 3 (Composition API / `<script setup lang="ts">`) frontend. Key files:
|
This is the **Reward** app — a family chore/reward tracker. Vue 3 (Composition API / `<script setup lang="ts">`) frontend. Key files:
|
||||||
|
|
||||||
- Theme tokens: `frontend/vue-app/src/assets/colors.css` — **always read this first** when reviewing a component.
|
- Theme tokens: `frontend/src/assets/colors.css` — **always read this first** when reviewing a component.
|
||||||
- Layout wrappers: `ParentLayout` (admin views) and `ChildLayout` (child dashboard views).
|
- Layout wrappers: `ParentLayout` (admin views) and `ChildLayout` (child dashboard views).
|
||||||
- All `.vue` files use `<style scoped>`. Child component styling uses `:deep()` selectors.
|
- All `.vue` files use `<style scoped>`. Child component styling uses `:deep()` selectors.
|
||||||
- File order within `.vue` files: `<template>`, then `<script>`, then `<style>`.
|
- File order within `.vue` files: `<template>`, then `<script>`, then `<style>`.
|
||||||
|
|||||||
18
.github/copilot-instructions.md
vendored
18
.github/copilot-instructions.md
vendored
@@ -5,7 +5,7 @@
|
|||||||
- **Stack**: Flask (Python, backend) + Vue 3 (TypeScript, frontend) + TinyDB (JSON, thread-safe, see `db/`).
|
- **Stack**: Flask (Python, backend) + Vue 3 (TypeScript, frontend) + TinyDB (JSON, thread-safe, see `db/`).
|
||||||
- **API**: RESTful endpoints in `api/`, grouped by entity (child, reward, task, user, image, etc). Each API file maps to a business domain.
|
- **API**: RESTful endpoints in `api/`, grouped by entity (child, reward, task, user, image, etc). Each API file maps to a business domain.
|
||||||
- **Nginx Proxy**: Frontend nginx proxies `/api/*` to backend, stripping the `/api` prefix. Backend endpoints should NOT include `/api` in their route definitions. Example: Backend defines `@app.route('/user')`, frontend calls `/api/user`.
|
- **Nginx Proxy**: Frontend nginx proxies `/api/*` to backend, stripping the `/api` prefix. Backend endpoints should NOT include `/api` in their route definitions. Example: Backend defines `@app.route('/user')`, frontend calls `/api/user`.
|
||||||
- **Models**: Maintain strict 1:1 mapping between Python `@dataclass`es (`backend/models/`) and TypeScript interfaces (`frontend/vue-app/src/common/models.ts`).
|
- **Models**: Maintain strict 1:1 mapping between Python `@dataclass`es (`backend/models/`) and TypeScript interfaces (`frontend/src/common/models.ts`).
|
||||||
- **Database**: Use TinyDB with `from_dict()`/`to_dict()` for serialization. All logic should operate on model instances, not raw dicts.
|
- **Database**: Use TinyDB with `from_dict()`/`to_dict()` for serialization. All logic should operate on model instances, not raw dicts.
|
||||||
- **Events**: Real-time updates via Server-Sent Events (SSE). Every mutation (add/edit/delete/trigger) must call `send_event_for_current_user` (see `backend/events/`).
|
- **Events**: Real-time updates via Server-Sent Events (SSE). Every mutation (add/edit/delete/trigger) must call `send_event_for_current_user` (see `backend/events/`).
|
||||||
- **Changes**: Do not use comments to replace code. All changes must be reflected in both backend and frontend files as needed.
|
- **Changes**: Do not use comments to replace code. All changes must be reflected in both backend and frontend files as needed.
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
## 🚦 Frontend Logic & Event Bus
|
## 🚦 Frontend Logic & Event Bus
|
||||||
|
|
||||||
- **SSE Event Management**: Register listeners in `onMounted`, clean up in `onUnmounted`. Listen for events like `child_task_triggered`, `child_reward_request`, `task_modified`, etc. See `frontend/vue-app/src/common/backendEvents.ts` and `components/BackendEventsListener.vue`.
|
- **SSE Event Management**: Register listeners in `onMounted`, clean up in `onUnmounted`. Listen for events like `child_task_triggered`, `child_reward_request`, `task_modified`, etc. See `frontend/src/common/backendEvents.ts` and `components/BackendEventsListener.vue`.
|
||||||
- **Layout Hierarchy**: Use `ParentLayout` for admin/management, `ChildLayout` for dashboard/focus views.
|
- **Layout Hierarchy**: Use `ParentLayout` for admin/management, `ChildLayout` for dashboard/focus views.
|
||||||
|
|
||||||
## ⚖️ Business Logic & Safeguards
|
## ⚖️ Business Logic & Safeguards
|
||||||
@@ -37,9 +37,9 @@
|
|||||||
|
|
||||||
- **Backend**: Run Flask with `python -m flask run --host=0.0.0.0 --port=5000` from the `backend/` directory. Main entry: `backend/main.py`.
|
- **Backend**: Run Flask with `python -m flask run --host=0.0.0.0 --port=5000` from the `backend/` directory. Main entry: `backend/main.py`.
|
||||||
- **Virtual Env**: Python is running from a virtual environment located at `backend/.venv/`.
|
- **Virtual Env**: Python is running from a virtual environment located at `backend/.venv/`.
|
||||||
- **Frontend**: From `frontend/vue-app/`, run `npm install` then `npm run dev`.
|
- **Frontend**: From `frontend/`, run `npm install` then `npm run dev`.
|
||||||
- **Tests**: Run backend tests with `pytest` in `backend/tests/`. Frontend component tests: `npm run test` in `frontend/vue-app/components/__tests__/`. E2E tests: `npx playwright test` from `frontend/vue-app/` — requires both servers running (use the `flask-backend` and `vue-frontend` skills).
|
- **Tests**: Run backend tests with `pytest` in `backend/tests/`. Frontend component tests: `npm run test` in `frontend/components/__tests__/`. E2E tests: `npx playwright test` from `frontend/` — requires both servers running (use the `flask-backend` and `vue-frontend` skills).
|
||||||
- **E2E Setup**: Playwright config is at `frontend/vue-app/playwright.config.ts`. Tests live in `frontend/vue-app/tests/`. The `globalSetup` in `playwright.config.ts` seeds the database and logs in once; all tests receive a pre-authenticated session via `storageState` — do NOT navigate to `/auth/login` in tests. Import `E2E_EMAIL` and `E2E_PASSWORD` from `tests/global-setup.ts` rather than hardcoding credentials. The backend must be started with `DB_ENV=e2e DATA_ENV=e2e` (the `flask-backend` skill does this) so test data goes to `backend/test_data/` and never touches production data.
|
- **E2E Setup**: Playwright config is at `frontend/playwright.config.ts`. Tests live in `frontend/tests/`. The `globalSetup` in `playwright.config.ts` seeds the database and logs in once; all tests receive a pre-authenticated session via `storageState` — do NOT navigate to `/auth/login` in tests. Import `E2E_EMAIL` and `E2E_PASSWORD` from `tests/global-setup.ts` rather than hardcoding credentials. The backend must be started with `DB_ENV=e2e DATA_ENV=e2e` (the `flask-backend` skill does this) so test data goes to `backend/test_data/` and never touches production data.
|
||||||
- **Debugging**: Use VS Code launch configs or run Flask/Vue dev servers directly. For SSE, use browser dev tools to inspect event streams.
|
- **Debugging**: Use VS Code launch configs or run Flask/Vue dev servers directly. For SSE, use browser dev tools to inspect event streams.
|
||||||
|
|
||||||
## 📁 Key Files & Directories
|
## 📁 Key Files & Directories
|
||||||
@@ -48,10 +48,10 @@
|
|||||||
- `backend/models/` — Python dataclasses (business logic, serialization)
|
- `backend/models/` — Python dataclasses (business logic, serialization)
|
||||||
- `backend/db/` — TinyDB setup and helpers
|
- `backend/db/` — TinyDB setup and helpers
|
||||||
- `backend/events/` — SSE event types, broadcaster, payloads
|
- `backend/events/` — SSE event types, broadcaster, payloads
|
||||||
- `frontend/vue-app/` — Vue 3 frontend (see `src/common/`, `src/components/`, `src/layout/`) - Where tests are run from
|
- `frontend/` — Vue 3 frontend (see `src/common/`, `src/components/`, `src/layout/`) - Where tests are run from
|
||||||
- `frontend/vue-app/src/common/models.ts` — TypeScript interfaces (mirror Python models)
|
- `frontend/src/common/models.ts` — TypeScript interfaces (mirror Python models)
|
||||||
- `frontend/vue-app/src/common/api.ts` — API helpers, error parsing, validation
|
- `frontend/src/common/api.ts` — API helpers, error parsing, validation
|
||||||
- `frontend/vue-app/src/common/backendEvents.ts` — SSE event types and handlers
|
- `frontend/src/common/backendEvents.ts` — SSE event types and handlers
|
||||||
|
|
||||||
## 🧠 Integration & Cross-Component Patterns
|
## 🧠 Integration & Cross-Component Patterns
|
||||||
|
|
||||||
|
|||||||
4
.github/skills/vue-frontend/SKILL.md
vendored
4
.github/skills/vue-frontend/SKILL.md
vendored
@@ -8,10 +8,10 @@ disable-model-invocation: true
|
|||||||
|
|
||||||
Use this skill when the user wants to "start the frontend," "run vue," or "launch the dev server."
|
Use this skill when the user wants to "start the frontend," "run vue," or "launch the dev server."
|
||||||
|
|
||||||
1. **Verify Directory:** Navigate to `./frontend/vue-app`.
|
1. **Verify Directory:** Navigate to `./frontend`.
|
||||||
- _Self-Correction:_ If the directory doesn't exist, search the workspace for `package.json` files and ask for clarification.
|
- _Self-Correction:_ If the directory doesn't exist, search the workspace for `package.json` files and ask for clarification.
|
||||||
|
|
||||||
2. **Check Dependencies:** - Before running, check if `node_modules` exists in `./frontend/vue-app`.
|
2. **Check Dependencies:** - Before running, check if `node_modules` exists in `./frontend`.
|
||||||
- If missing, ask the user: "Should I run `npm install` first?"
|
- If missing, ask the user: "Should I run `npm install` first?"
|
||||||
|
|
||||||
3. **Execution:** - Run the command: `npm run dev`
|
3. **Execution:** - Run the command: `npm run dev`
|
||||||
|
|||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -1,12 +1,12 @@
|
|||||||
.env
|
.env
|
||||||
backend/test_data/
|
backend/test_data/
|
||||||
logs/
|
logs/
|
||||||
frontend/vue-app/resources/
|
frontend/resources/
|
||||||
frontend/vue-app/playwright-report/
|
frontend/playwright-report/
|
||||||
frontend/vue-app/test-results/
|
frontend/test-results/
|
||||||
backend/test-results/
|
backend/test-results/
|
||||||
.vscode/keybindings.json
|
.vscode/keybindings.json
|
||||||
.DS_Store
|
.DS_Store
|
||||||
**/.DS_Store
|
**/.DS_Store
|
||||||
frontend/vue-app/cert.pem
|
frontend/cert.pem
|
||||||
frontend/vue-app/key.pem
|
frontend/key.pem
|
||||||
|
|||||||
6
.vscode/launch.json
vendored
6
.vscode/launch.json
vendored
@@ -35,7 +35,7 @@
|
|||||||
"run",
|
"run",
|
||||||
"dev"
|
"dev"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceFolder}/frontend/vue-app",
|
"cwd": "${workspaceFolder}/frontend",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"serverReadyAction": {
|
"serverReadyAction": {
|
||||||
"pattern": "Local:.*https://localhost:([0-9]+)",
|
"pattern": "Local:.*https://localhost:([0-9]+)",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"type": "pwa-chrome",
|
"type": "pwa-chrome",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"url": "http://localhost:5173",
|
"url": "http://localhost:5173",
|
||||||
"webRoot": "${workspaceFolder}/frontend/vue-app"
|
"webRoot": "${workspaceFolder}/frontend"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Python: Backend Tests",
|
"name": "Python: Backend Tests",
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
"run",
|
"run",
|
||||||
"test:unit"
|
"test:unit"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceFolder}/frontend/vue-app",
|
"cwd": "${workspaceFolder}/frontend",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
"osx": {
|
"osx": {
|
||||||
"env": {
|
"env": {
|
||||||
|
|||||||
6
.vscode/launch.json.bak
vendored
6
.vscode/launch.json.bak
vendored
@@ -28,7 +28,7 @@
|
|||||||
"run",
|
"run",
|
||||||
"dev"
|
"dev"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceFolder}/frontend/vue-app",
|
"cwd": "${workspaceFolder}/frontend",
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
"type": "chrome",
|
"type": "chrome",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"url": "https://localhost:5173", // or your Vite dev server port
|
"url": "https://localhost:5173", // or your Vite dev server port
|
||||||
"webRoot": "${workspaceFolder}/frontend/vue-app"
|
"webRoot": "${workspaceFolder}/frontend"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Python: Backend Tests",
|
"name": "Python: Backend Tests",
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"runtimeArgs": [
|
"runtimeArgs": [
|
||||||
"vitest"
|
"vitest"
|
||||||
],
|
],
|
||||||
"cwd": "${workspaceFolder}/frontend/vue-app",
|
"cwd": "${workspaceFolder}/frontend",
|
||||||
"console": "integratedTerminal"
|
"console": "integratedTerminal"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
2
.vscode/mcp.json
vendored
2
.vscode/mcp.json
vendored
@@ -8,7 +8,7 @@
|
|||||||
"run-test-mcp-server",
|
"run-test-mcp-server",
|
||||||
"--config=playwright.config.ts"
|
"--config=playwright.config.ts"
|
||||||
],
|
],
|
||||||
"cwd": "frontend/vue-app"
|
"cwd": "frontend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"inputs": []
|
"inputs": []
|
||||||
|
|||||||
22
.vscode/tasks.json
vendored
22
.vscode/tasks.json
vendored
@@ -44,56 +44,56 @@
|
|||||||
{
|
{
|
||||||
"label": "PW: Task Modification Tests",
|
"label": "PW: Task Modification Tests",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification",
|
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification",
|
||||||
"isBackground": false,
|
"isBackground": false,
|
||||||
"group": "test"
|
"group": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "PW: Task Modification Tests (PS)",
|
"label": "PW: Task Modification Tests (PS)",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "powershell -Command \"cd '$env:APPDATA/../../../d/Python Utilities/Reward/frontend/vue-app'; npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification\"",
|
"command": "powershell -Command \"cd '$env:APPDATA/../../../d/Python Utilities/Reward/frontend'; npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification\"",
|
||||||
"isBackground": false,
|
"isBackground": false,
|
||||||
"group": "test"
|
"group": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "PW: Task Modification Tests (cmd)",
|
"label": "PW: Task Modification Tests (cmd)",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cmd /c \"cd /d \\\"D:\\Python Utilities\\Reward\\frontend\\vue-app\\\" && npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification 2>&1\"",
|
"command": "cmd /c \"cd /d \\\"D:\\Python Utilities\\Reward\\frontend\\\" && npx playwright test e2e/mode_parent/task-modification --project=chromium-task-modification 2>&1\"",
|
||||||
"isBackground": false,
|
"isBackground": false,
|
||||||
"group": "test"
|
"group": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "PW: User Profile Tests",
|
"label": "PW: User Profile Tests",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
|
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
|
||||||
"isBackground": false,
|
"isBackground": false,
|
||||||
"group": "test"
|
"group": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "PW: User Profile Tests 2",
|
"label": "PW: User Profile Tests 2",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
|
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
|
||||||
"isBackground": false,
|
"isBackground": false,
|
||||||
"group": "test"
|
"group": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "PW: User Profile Tests Final",
|
"label": "PW: User Profile Tests Final",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
|
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test e2e/mode_parent/user-profile --project=chromium-user-profile --project=chromium-user-profile-pin --project=chromium-user-profile-delete",
|
||||||
"isBackground": false,
|
"isBackground": false,
|
||||||
"group": "test"
|
"group": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "PW: Full Test Suite",
|
"label": "PW: Full Test Suite",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test --reporter=line",
|
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test --reporter=line",
|
||||||
"isBackground": false,
|
"isBackground": false,
|
||||||
"group": "test"
|
"group": "test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"label": "PW: Full Suite",
|
"label": "PW: Full Suite",
|
||||||
"type": "shell",
|
"type": "shell",
|
||||||
"command": "cd \"${workspaceFolder}/frontend/vue-app\" && npx playwright test --reporter=line",
|
"command": "cd \"${workspaceFolder}/frontend\" && npx playwright test --reporter=line",
|
||||||
"isBackground": false,
|
"isBackground": false,
|
||||||
"group": "test"
|
"group": "test"
|
||||||
},
|
},
|
||||||
@@ -104,7 +104,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"-NoProfile",
|
"-NoProfile",
|
||||||
"-Command",
|
"-Command",
|
||||||
"cd 'D:\\Python Utilities\\Reward\\frontend\\vue-app'; npx playwright test --reporter=line 2>&1"
|
"cd 'D:\\Python Utilities\\Reward\\frontend'; npx playwright test --reporter=line 2>&1"
|
||||||
],
|
],
|
||||||
"group": "test",
|
"group": "test",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
@@ -119,7 +119,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"-NoProfile",
|
"-NoProfile",
|
||||||
"-Command",
|
"-Command",
|
||||||
"cd 'D:\\Python Utilities\\Reward\\frontend\\vue-app'; npx playwright test e2e/mode_parent/tasks/penalty-default.spec.ts --project=chromium-default-tasks --reporter=line 2>&1"
|
"cd 'D:\\Python Utilities\\Reward\\frontend'; npx playwright test e2e/mode_parent/tasks/penalty-default.spec.ts --project=chromium-default-tasks --reporter=line 2>&1"
|
||||||
],
|
],
|
||||||
"group": "test",
|
"group": "test",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
@@ -134,7 +134,7 @@
|
|||||||
"args": [
|
"args": [
|
||||||
"-NoProfile",
|
"-NoProfile",
|
||||||
"-Command",
|
"-Command",
|
||||||
"cd 'D:\\Python Utilities\\Reward\\frontend\\vue-app'; npx playwright test e2e/mode_parent/user-profile/profile-editing.spec.ts --project=chromium-user-profile --reporter=line 2>&1"
|
"cd 'D:\\Python Utilities\\Reward\\frontend'; npx playwright test e2e/mode_parent/user-profile/profile-editing.spec.ts --project=chromium-user-profile --reporter=line 2>&1"
|
||||||
],
|
],
|
||||||
"group": "test",
|
"group": "test",
|
||||||
"presentation": {
|
"presentation": {
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ python -m flask run --host=0.0.0.0 --port=5000
|
|||||||
### Frontend
|
### Frontend
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd frontend/vue-app
|
cd frontend
|
||||||
npm install
|
npm install
|
||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
@@ -114,7 +114,7 @@ pytest tests/
|
|||||||
### Frontend Tests
|
### Frontend Tests
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd frontend/vue-app
|
cd frontend
|
||||||
npm run test
|
npm run test
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -151,7 +151,6 @@ npm run test
|
|||||||
│ ├── tests/ # Backend tests
|
│ ├── tests/ # Backend tests
|
||||||
│ └── utils/ # Utilities (scheduler, etc)
|
│ └── utils/ # Utilities (scheduler, etc)
|
||||||
├── frontend/
|
├── frontend/
|
||||||
│ └── vue-app/
|
|
||||||
│ └── src/
|
│ └── src/
|
||||||
│ ├── common/ # Shared utilities
|
│ ├── common/ # Shared utilities
|
||||||
│ ├── components/ # Vue components
|
│ ├── components/ # Vue components
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# vue-app
|
# frontend
|
||||||
|
|
||||||
This template should help get you started developing with Vue 3 in Vite.
|
This template should help get you started developing with Vue 3 in Vite.
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB |
@@ -2,20 +2,20 @@
|
|||||||
"cookies": [
|
"cookies": [
|
||||||
{
|
{
|
||||||
"name": "refresh_token",
|
"name": "refresh_token",
|
||||||
"value": "yQZhoeeYUidDfolHE467zNEWguWXzMIinFT1LNLU5yM",
|
"value": "oh4sQB_Q1Se4xydZHTIolU6FhvlKPIlDPsxfd_qe4wQ",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/api/auth",
|
"path": "/api/auth",
|
||||||
"expires": 1784509278.633774,
|
"expires": 1784867824.852037,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Strict"
|
"sameSite": "Strict"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "access_token",
|
"name": "access_token",
|
||||||
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiI4ZmZkYTMyNC01MDE1LTRiYjktODY3Yi1jZjFlNTU1NjBjMzciLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzY3MzQxNzh9.YvFGLV1bdoRpRb8z5YtO19dpD7vkC0p5XMhGJgSJol8",
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1jY0B0ZXN0LmNvbSIsInVzZXJfaWQiOiI4NGY1Y2I3NS05NWU1LTRjMTItOTExOS02MzdkYjJlMWY1MGYiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcwOTI3MjR9.7-d4EvJEYtYSi_5ZFiU7tuxgc4ilVMBxhwzgAQZNTvY",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"expires": 1776734178.633726,
|
"expires": 1777092724.851439,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Lax"
|
"sameSite": "Lax"
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
"localStorage": [
|
"localStorage": [
|
||||||
{
|
{
|
||||||
"name": "authSyncEvent",
|
"name": "authSyncEvent",
|
||||||
"value": "{\"type\":\"logout\",\"at\":1776733278500}"
|
"value": "{\"type\":\"logout\",\"at\":1777091824594}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "parentAuth",
|
"name": "parentAuth",
|
||||||
"value": "{\"expiresAt\":1776906078798}"
|
"value": "{\"expiresAt\":1777264625262}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,20 @@
|
|||||||
"cookies": [
|
"cookies": [
|
||||||
{
|
{
|
||||||
"name": "refresh_token",
|
"name": "refresh_token",
|
||||||
"value": "j90QCKu8d9QvNG-k-m31G5PoxU0yBuz-wyx8NPYCOG8",
|
"value": "lIhurtvmo0oGcimN_8v59h9y3CrglEAloQO1QFw8RsQ",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/api/auth",
|
"path": "/api/auth",
|
||||||
"expires": 1784509278.541961,
|
"expires": 1784867824.995155,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Strict"
|
"sameSite": "Strict"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "access_token",
|
"name": "access_token",
|
||||||
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiNzA3ZGYwYmUtOWQxMy00MGVlLWI3M2QtYzYwZmFmNDg2NmU2IiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc2NzM0MTc4fQ.cEKtxo4zVTd7kBZzfjjI_uxqez32gUxmZYuUa8-RYdc",
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZS1kZWxldGVAdGVzdC5jb20iLCJ1c2VyX2lkIjoiOWYzYTljNWMtM2IwMy00YTZlLTliNmUtYjE0MTQzZWNhMzNkIiwidG9rZW5fdmVyc2lvbiI6MCwiZXhwIjoxNzc3MDkyNzI0fQ.UB5MCIZ2PHbn3UyohNzmFbOCb3TR-KHxJPWB9qkWwCQ",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"expires": 1776734178.541918,
|
"expires": 1777092724.99406,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Lax"
|
"sameSite": "Lax"
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
"localStorage": [
|
"localStorage": [
|
||||||
{
|
{
|
||||||
"name": "authSyncEvent",
|
"name": "authSyncEvent",
|
||||||
"value": "{\"type\":\"logout\",\"at\":1776733278395}"
|
"value": "{\"type\":\"logout\",\"at\":1777091824687}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "parentAuth",
|
"name": "parentAuth",
|
||||||
"value": "{\"expiresAt\":1776906078675}"
|
"value": "{\"expiresAt\":1777264625511}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -2,20 +2,20 @@
|
|||||||
"cookies": [
|
"cookies": [
|
||||||
{
|
{
|
||||||
"name": "refresh_token",
|
"name": "refresh_token",
|
||||||
"value": "RETiHvb_RUs2-0xb-klx8BwvSwr2TMtz1D-NmB5825Y",
|
"value": "uMExObHukEdFnlszorcDq2XlbRW0AeIV71x_vSovEF4",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/api/auth",
|
"path": "/api/auth",
|
||||||
"expires": 1784509276.929905,
|
"expires": 1784867821.107956,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Strict"
|
"sameSite": "Strict"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "access_token",
|
"name": "access_token",
|
||||||
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiJhNjZmYjdjNy04ZDI5LTRkNjAtYmVjNi0zMmEzODg1MGI1OGIiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzY3MzQxNzZ9.WSka-ZbN1ZKbD_Q4UpoP9qfN1i73G51nrx88F2TbbOE",
|
"value": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJlbWFpbCI6ImUyZUB0ZXN0LmNvbSIsInVzZXJfaWQiOiI2ZWE5YWZmZC1jMGExLTRkMzUtOGJmYS1mZDBkOWY5NzFmOWQiLCJ0b2tlbl92ZXJzaW9uIjowLCJleHAiOjE3NzcwOTI3MjF9.rb64zKqS6uXqkgX_4kke37Rc6om68elt4Qa24Fy9NPo",
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
"path": "/",
|
"path": "/",
|
||||||
"expires": 1776734176.929863,
|
"expires": 1777092721.107403,
|
||||||
"httpOnly": true,
|
"httpOnly": true,
|
||||||
"secure": true,
|
"secure": true,
|
||||||
"sameSite": "Lax"
|
"sameSite": "Lax"
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
"localStorage": [
|
"localStorage": [
|
||||||
{
|
{
|
||||||
"name": "authSyncEvent",
|
"name": "authSyncEvent",
|
||||||
"value": "{\"type\":\"logout\",\"at\":1776733276784}"
|
"value": "{\"type\":\"logout\",\"at\":1777091820880}"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "parentAuth",
|
"name": "parentAuth",
|
||||||
"value": "{\"expiresAt\":1776906077058}"
|
"value": "{\"expiresAt\":1777264621275}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Before Width: | Height: | Size: 409 KiB After Width: | Height: | Size: 409 KiB |
@@ -98,8 +98,9 @@ test.describe('Digest Action Token — Approve Chore', () => {
|
|||||||
test('Approve-chore action is performed: points awarded and confirmation removed', async ({
|
test('Approve-chore action is performed: points awarded and confirmation removed', async ({
|
||||||
request,
|
request,
|
||||||
}) => {
|
}) => {
|
||||||
// Wait for action to complete
|
// Execute the action via authenticated POST (GET only redirects, POST consumes the token)
|
||||||
await new Promise((r) => setTimeout(r, 300))
|
const execRes = await request.post(`/api/digest-action/${approveToken}`)
|
||||||
|
expect(execRes.status()).toBe(200)
|
||||||
|
|
||||||
const childRes = await request.get(`/api/child/list`)
|
const childRes = await request.get(`/api/child/list`)
|
||||||
const children = (await childRes.json()).children ?? []
|
const children = (await childRes.json()).children ?? []
|
||||||
@@ -93,7 +93,9 @@ test.describe('Digest Action Token — Approve Reward', () => {
|
|||||||
|
|
||||||
// 7.6 Approve-reward action: reward triggered, child points deducted
|
// 7.6 Approve-reward action: reward triggered, child points deducted
|
||||||
test('Approve-reward action: reward triggered and child points deducted', async ({ request }) => {
|
test('Approve-reward action: reward triggered and child points deducted', async ({ request }) => {
|
||||||
await new Promise((r) => setTimeout(r, 300))
|
// Execute the action via authenticated POST (GET only redirects, POST consumes the token)
|
||||||
|
const execRes = await request.post(`/api/digest-action/${approveToken}`)
|
||||||
|
expect(execRes.status()).toBe(200)
|
||||||
|
|
||||||
const childRes = await request.get('/api/child/list')
|
const childRes = await request.get('/api/child/list')
|
||||||
const children = (await childRes.json()).children ?? []
|
const children = (await childRes.json()).children ?? []
|
||||||
@@ -87,7 +87,9 @@ test.describe('Digest Action Token — Deny Chore', () => {
|
|||||||
test('Deny-chore action is performed: confirmation removed, points unchanged', async ({
|
test('Deny-chore action is performed: confirmation removed, points unchanged', async ({
|
||||||
request,
|
request,
|
||||||
}) => {
|
}) => {
|
||||||
await new Promise((r) => setTimeout(r, 300))
|
// Execute the action via authenticated POST (GET only redirects, POST consumes the token)
|
||||||
|
const execRes = await request.post(`/api/digest-action/${denyToken}`)
|
||||||
|
expect(execRes.status()).toBe(200)
|
||||||
|
|
||||||
const childRes = await request.get(`/api/child/list`)
|
const childRes = await request.get(`/api/child/list`)
|
||||||
const children = (await childRes.json()).children ?? []
|
const children = (await childRes.json()).children ?? []
|
||||||
@@ -92,7 +92,9 @@ test.describe('Digest Action Token — Deny Reward', () => {
|
|||||||
|
|
||||||
// 7.8 Deny-reward action: pending request removed, points unchanged
|
// 7.8 Deny-reward action: pending request removed, points unchanged
|
||||||
test('Deny-reward action: pending request removed and points unchanged', async ({ request }) => {
|
test('Deny-reward action: pending request removed and points unchanged', async ({ request }) => {
|
||||||
await new Promise((r) => setTimeout(r, 300))
|
// Execute the action via authenticated POST (GET only redirects, POST consumes the token)
|
||||||
|
const execRes = await request.post(`/api/digest-action/${denyToken}`)
|
||||||
|
expect(execRes.status()).toBe(200)
|
||||||
|
|
||||||
const childRes = await request.get('/api/child/list')
|
const childRes = await request.get('/api/child/list')
|
||||||
const children = (await childRes.json()).children ?? []
|
const children = (await childRes.json()).children ?? []
|
||||||
@@ -94,11 +94,11 @@ test.describe('Digest Action Token — Error Paths', () => {
|
|||||||
|
|
||||||
const unauthCtx = await getUnauthContext(playwright)
|
const unauthCtx = await getUnauthContext(playwright)
|
||||||
|
|
||||||
// First use — should succeed (302)
|
// First use — authenticated POST executes and consumes the token
|
||||||
const first = await unauthCtx.get(`/api/digest-action/${token}`, { maxRedirects: 0 })
|
const first = await request.post(`/api/digest-action/${token}`)
|
||||||
expect(first.status()).toBe(302)
|
expect(first.status()).toBe(200)
|
||||||
|
|
||||||
// Second use — token is consumed
|
// Second use — token is consumed, unauthenticated GET should return 400
|
||||||
const second = await unauthCtx.get(`/api/digest-action/${token}`, { maxRedirects: 0 })
|
const second = await unauthCtx.get(`/api/digest-action/${token}`, { maxRedirects: 0 })
|
||||||
expect(second.status()).toBe(400)
|
expect(second.status()).toBe(400)
|
||||||
await unauthCtx.dispose()
|
await unauthCtx.dispose()
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user