Fix bug 619 (#629)

* Add tasks today plan fixes

* fixup! Add tasks today plan fixes

* fixup! fixup! Add tasks today plan fixes

* fixup! fixup! fixup! Add tasks today plan fixes
This commit is contained in:
Chris 2025-12-02 18:00:36 +02:00 committed by GitHub
parent f663ad5d52
commit 2d2a989a5f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 1041 additions and 357 deletions

View file

@ -12,26 +12,17 @@ red() { printf "\033[31m%s\033[0m\n" "$*"; }
green() { printf "\033[32m%s\033[0m\n" "$*"; }
yellow() { printf "\033[33m%s\033[0m\n" "$*"; }
# Ensure dependencies in e2e/
# Ensure dependencies in root
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
E2E_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
ROOT_DIR="$(cd "$E2E_DIR/.." && pwd)"
cd "$E2E_DIR"
if [ ! -f package.json ]; then
red "e2e/package.json not found"
exit 1
fi
# Install e2e deps and browsers
if [ ! -d node_modules ]; then
yellow "Installing e2e dependencies..."
npm ci
fi
cd "$ROOT_DIR"
# Check if Playwright is installed
if ! npx playwright --version >/dev/null 2>&1; then
yellow "Installing Playwright browsers..."
npm run install-browsers
npx playwright install --with-deps
fi
# Start backend and frontend
@ -108,8 +99,8 @@ for i in {1..60}; do
fi
done
# Run tests
cd "$E2E_DIR"
# Run tests (specify config file since we're running from root)
cd "$ROOT_DIR"
yellow "Running Playwright tests..."
APP_URL="$FRONTEND_URL" \
@ -117,11 +108,11 @@ E2E_EMAIL="${E2E_EMAIL:-test@tududi.com}" \
E2E_PASSWORD="${E2E_PASSWORD:-password123}" \
bash -c '
if [ "${E2E_MODE:-}" = "ui" ]; then
npm run test:ui
npx playwright test --ui --config=e2e/playwright.config.ts
elif [ "${E2E_MODE:-}" = "headed" ]; then
# Respect E2E_SLOWMO and run only Firefox sequentially
npx playwright test --headed --project=Firefox --workers=1
# Respect E2E_SLOWMO and run only Chromium sequentially
npx playwright test --headed --project=Chromium --workers=1 --config=e2e/playwright.config.ts
else
npx playwright test --workers=5
npx playwright test --config=e2e/playwright.config.ts
fi
'

92
e2e/package-lock.json generated
View file

@ -1,92 +0,0 @@
{
"name": "tududi-e2e",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "tududi-e2e",
"version": "0.1.0",
"devDependencies": {
"@playwright/test": "^1.47.2",
"dotenv": "^16.5.0"
}
},
"node_modules/@playwright/test": {
"version": "1.54.2",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.54.2.tgz",
"integrity": "sha512-A+znathYxPf+72riFd1r1ovOLqsIIB0jKIoPjyK2kqEIe30/6jF6BC7QNluHuwUmsD2tv1XZVugN8GqfTMOxsA==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright": "1.54.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/dotenv": {
"version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
"integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"dev": true,
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/playwright": {
"version": "1.54.2",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.54.2.tgz",
"integrity": "sha512-Hu/BMoA1NAdRUuulyvQC0pEqZ4vQbGfn8f7wPXcnqQmM+zct9UliKxsIkLNmz/ku7LElUNqmaiv1TG/aL5ACsw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.54.2"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.54.2",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.54.2.tgz",
"integrity": "sha512-n5r4HFbMmWsB4twG7tJLDN9gmBUeSPcsBZiWSE4DnYz9mJMAFqr2ID7+eGC9kpEnxExJ1epttwR59LEWCk8mtA==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
}
}
}

View file

@ -1,16 +0,0 @@
{
"name": "tududi-e2e",
"private": true,
"version": "0.1.0",
"description": "End-to-end tests for tududi using Playwright",
"scripts": {
"test": "playwright test",
"test:ui": "playwright test --ui",
"codegen": "playwright codegen ${APP_URL:-http://localhost:8080}",
"install-browsers": "playwright install --with-deps"
},
"devDependencies": {
"@playwright/test": "^1.47.2",
"dotenv": "^16.5.0"
}
}

View file

@ -11,6 +11,7 @@ export default defineConfig({
timeout: 60_000,
expect: { timeout: 10_000 },
fullyParallel: true,
workers: process.env.CI ? 1 : undefined, // Use default workers locally, 1 in CI
reporter: [['list']],
use: {
baseURL,

View file

@ -0,0 +1,245 @@
import { test, expect } from '@playwright/test';
test.describe.serial('Today View', () => {
// Helper function to login via UI
async function loginViaUI(page, baseURL) {
const appUrl =
baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
await page.goto(`${appUrl}/login`);
await page.fill(
'input[type="email"]',
process.env.E2E_EMAIL || 'test@tududi.com'
);
await page.fill(
'input[type="password"]',
process.env.E2E_PASSWORD || 'password123'
);
await page.click('button[type="submit"]');
// Wait for redirect to dashboard or today page
await page.waitForURL(/\/(dashboard|today)/, { timeout: 10000 });
}
test('should only show tasks with today flag in Planned section', async ({
page,
context,
baseURL,
}) => {
// Login first
await loginViaUI(page, baseURL);
const appUrl =
baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
const timestamp = Date.now();
// Create tasks via API using the logged-in context
const tasksToCreate = [
{
name: `High Priority Planned ${timestamp}`,
today: true,
priority: 2,
}, // 2 = HIGH
{
name: `Task Without Today Flag ${timestamp}`,
today: false,
priority: 2,
}, // 2 = HIGH
];
const taskIds: string[] = [];
const createdTasks: any[] = [];
for (const taskData of tasksToCreate) {
const response = await context.request.post(`${appUrl}/api/task`, {
data: taskData,
});
if (response.ok()) {
const task = await response.json();
taskIds.push(task.id);
createdTasks.push(task);
}
}
// Navigate to today page and wait for metrics to load
await page.goto(`${appUrl}/today`);
await page.waitForLoadState('networkidle');
// Check if Planned section exists using data-testid
const plannedSection = page.getByTestId('planned-section');
await expect(plannedSection).toBeVisible({ timeout: 10000 });
// Verify task with today flag is visible in the Planned section
const withTodayFlagTask = plannedSection.getByTestId(
`task-item-${taskIds[0]}`
);
await expect(withTodayFlagTask).toBeVisible({ timeout: 10000 });
// Verify task without today flag is NOT visible in Planned section
const withoutFlagTask = plannedSection.getByTestId(
`task-item-${taskIds[1]}`
);
await expect(withoutFlagTask).not.toBeVisible();
// Clean up created tasks
for (const taskId of taskIds) {
await context.request.delete(`${appUrl}/api/task/${taskId}`);
}
});
test('should show overdue tasks in Overdue section', async ({
page,
context,
baseURL,
}) => {
// Login first
await loginViaUI(page, baseURL);
const appUrl =
baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
const timestamp = Date.now();
// Calculate yesterday's date
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const yesterdayStr = yesterday.toISOString().split('T')[0];
// Create an overdue task
const response = await context.request.post(`${appUrl}/api/task`, {
data: {
name: `Overdue Task ${timestamp}`,
due_date: yesterdayStr,
priority: 2,
}, // 2 = HIGH
});
let taskId: string | null = null;
if (response.ok()) {
const task = await response.json();
taskId = task.id;
}
// Navigate to today page
await page.goto(`${appUrl}/today`);
await page.waitForLoadState('networkidle');
// Wait a bit for React to render the sections
await page.waitForTimeout(2000);
// Check if Overdue section exists using data-testid
const overdueSection = page.getByTestId('overdue-section');
const isOverdueVisible = await overdueSection
.isVisible()
.catch(() => false);
// If overdue section is visible, verify the task is in it
if (isOverdueVisible) {
const overdueTask = overdueSection.getByTestId(
`task-item-${taskId}`
);
await expect(overdueTask).toBeVisible();
} else {
// If section not visible, the settings might be hiding it
// Skip this assertion but don't fail the test
console.log(
'Overdue section not visible - may be hidden by settings'
);
}
// Clean up
if (taskId) {
await context.request.delete(`${appUrl}/api/task/${taskId}`);
}
});
test('should show tasks due today in Due Today section', async ({
page,
context,
baseURL,
}) => {
// Login first
await loginViaUI(page, baseURL);
const appUrl =
baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
const timestamp = Date.now();
// Calculate today's date
const today = new Date();
const todayStr = today.toISOString().split('T')[0];
// Create a task due today (but not in today plan)
const response = await context.request.post(`${appUrl}/api/task`, {
data: {
name: `Due Today Task ${timestamp}`,
due_date: todayStr,
priority: 2,
today: false,
}, // 2 = HIGH
});
let taskId: string | null = null;
if (response.ok()) {
const task = await response.json();
taskId = task.id;
}
// Navigate to today page
await page.goto(`${appUrl}/today`);
await page.waitForLoadState('networkidle');
// Wait a bit for React to render the sections
await page.waitForTimeout(2000);
// Check if Due Today section exists using data-testid
const dueTodaySection = page.getByTestId('due-today-section');
const isDueTodayVisible = await dueTodaySection
.isVisible()
.catch(() => false);
// If due today section is visible, verify the task is in it
if (isDueTodayVisible) {
const dueTodayTask = dueTodaySection.getByTestId(
`task-item-${taskId}`
);
await expect(dueTodayTask).toBeVisible();
} else {
// If section not visible, the settings might be hiding it
console.log(
'Due Today section not visible - may be hidden by settings'
);
}
// Clean up
if (taskId) {
await context.request.delete(`${appUrl}/api/task/${taskId}`);
}
});
test('should allow collapsing and expanding sections', async ({
page,
baseURL,
}) => {
// Login first
await loginViaUI(page, baseURL);
const appUrl =
baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
await page.goto(`${appUrl}/today`);
await page.waitForLoadState('networkidle');
// Test Planned section collapse/expand if it exists using data-testid
const plannedHeader = page.getByTestId('planned-section-header');
const isPlannedVisible = await plannedHeader
.isVisible({ timeout: 5000 })
.catch(() => false);
if (isPlannedVisible) {
// Verify header is clickable
await expect(plannedHeader).toBeVisible();
// This test just verifies the section exists and is clickable
// Actual collapse/expand behavior depends on having tasks
}
});
});