Fix an issue with tasks appearing in today without a flag (#640)

This commit is contained in:
Chris 2025-12-03 16:48:47 +02:00 committed by GitHub
parent 08be7f8eda
commit 270a80f71b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 49 additions and 53 deletions

View file

@ -116,6 +116,7 @@ async function filterTasksByParams(
}; };
whereClause[Op.or] = [ whereClause[Op.or] = [
{ {
// Non-recurring tasks that are marked for today
[Op.and]: [ [Op.and]: [
{ {
[Op.or]: [ [Op.or]: [
@ -124,16 +125,20 @@ async function filterTasksByParams(
], ],
}, },
{ recurring_parent_id: null }, { recurring_parent_id: null },
{ today: true },
], ],
}, },
{ {
// Recurring parent tasks that are marked for today
[Op.and]: [ [Op.and]: [
{ recurrence_type: { [Op.ne]: 'none' } }, { recurrence_type: { [Op.ne]: 'none' } },
{ recurrence_type: { [Op.ne]: null } }, { recurrence_type: { [Op.ne]: null } },
{ recurring_parent_id: null }, { recurring_parent_id: null },
{ today: true },
], ],
}, },
{ {
// Recurring task instances that are due today (regardless of today flag)
[Op.and]: [ [Op.and]: [
{ recurring_parent_id: { [Op.ne]: null } }, { recurring_parent_id: { [Op.ne]: null } },
{ {

View file

@ -152,19 +152,19 @@ describe('Project Sharing Integration Tests', () => {
}); });
const taskDueToday = taskDueTodayResponse.body; const taskDueToday = taskDueTodayResponse.body;
// Fetch today's tasks as shared user // Fetch today's tasks as shared user with include_lists to get tasks_due_today
const response = await sharedUserAgent.get('/api/tasks?type=today'); const response = await sharedUserAgent.get(
'/api/tasks?type=today&include_lists=true'
);
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body.tasks).toBeDefined(); expect(response.body.tasks).toBeDefined();
// Check if the task appears in the main tasks or metrics // Task should appear in tasks_due_today since it has a due date but today=false
const allTasks = [ expect(response.body.tasks_due_today).toBeDefined();
...response.body.tasks, const foundTask = response.body.tasks_due_today.find(
...(response.body.metrics?.tasks_due_today || []), (t) => t.id === taskDueToday.id
]; );
const foundTask = allTasks.find((t) => t.id === taskDueToday.id);
expect(foundTask).toBeDefined(); expect(foundTask).toBeDefined();
expect(foundTask.name).toBe('Task due today in shared project'); expect(foundTask.name).toBe('Task due today in shared project');
}); });

View file

@ -99,13 +99,13 @@ describe('Tasks Routes', () => {
expect(response.body.tasks.map((t) => t.id)).toContain(task2.id); expect(response.body.tasks.map((t) => t.id)).toContain(task2.id);
}); });
it('should filter today tasks (returns all user tasks)', async () => { it('should filter today tasks (returns only tasks with today=true)', async () => {
const response = await agent.get('/api/tasks?type=today'); const response = await agent.get('/api/tasks?type=today');
expect(response.status).toBe(200); expect(response.status).toBe(200);
expect(response.body.tasks).toBeDefined(); expect(response.body.tasks).toBeDefined();
expect(response.body.tasks.length).toBe(2); expect(response.body.tasks.length).toBe(1);
// Both tasks should be returned as "today" doesn't filter by the today field expect(response.body.tasks[0].id).toBe(task1.id);
}); });
it('should require authentication', async () => { it('should require authentication', async () => {

View file

@ -8,15 +8,15 @@ const slowMoMs = Number(process.env.E2E_SLOWMO || '0') || 0;
export default defineConfig({ export default defineConfig({
testDir: './tests', testDir: './tests',
timeout: 60_000, timeout: 30_000, // Reduced from 60s to 30s
expect: { timeout: 10_000 }, expect: { timeout: 5_000 }, // Reduced from 10s to 5s
fullyParallel: true, fullyParallel: true,
workers: process.env.CI ? 1 : undefined, // Use default workers locally, 1 in CI workers: process.env.CI ? 1 : 4, // Use 4 workers locally for speed
reporter: [['list']], reporter: [['list']],
use: { use: {
baseURL, baseURL,
trace: 'on-first-retry', trace: 'on-first-retry',
video: 'retain-on-failure', video: 'off', // Disable video for speed (was 'retain-on-failure')
screenshot: 'only-on-failure', screenshot: 'only-on-failure',
viewport: { width: 1280, height: 800 }, viewport: { width: 1280, height: 800 },
launchOptions: { slowMo: slowMoMs }, launchOptions: { slowMo: slowMoMs },

View file

@ -1,7 +1,7 @@
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
test.describe.serial('User Registration', () => { test.describe.serial('Registration', () => {
test.describe.serial('User Registration - Enabled', () => { test.describe.serial('Enabled', () => {
test.beforeAll(async ({ request, baseURL }) => { test.beforeAll(async ({ request, baseURL }) => {
const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080'; const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
@ -37,7 +37,7 @@ test.describe.serial('User Registration - Enabled', () => {
await page.goto(appUrl + '/register'); await page.goto(appUrl + '/register');
}); });
test('should display registration form', async ({ page }) => { test('Shows form', async ({ page }) => {
await expect(page.getByTestId('register-heading')).toBeVisible(); await expect(page.getByTestId('register-heading')).toBeVisible();
await expect(page.getByTestId('register-email')).toBeVisible(); await expect(page.getByTestId('register-email')).toBeVisible();
await expect(page.getByTestId('register-password')).toBeVisible(); await expect(page.getByTestId('register-password')).toBeVisible();
@ -45,7 +45,7 @@ test.describe.serial('User Registration - Enabled', () => {
await expect(page.getByTestId('register-submit')).toBeVisible(); await expect(page.getByTestId('register-submit')).toBeVisible();
}); });
test('should show error when passwords do not match', async ({ page }) => { test('Password mismatch error', async ({ page }) => {
const timestamp = Date.now(); const timestamp = Date.now();
const email = `test${timestamp}@example.com`; const email = `test${timestamp}@example.com`;
@ -58,7 +58,7 @@ test.describe.serial('User Registration - Enabled', () => {
await expect(page.getByTestId('register-error')).toContainText(/passwords do not match/i); await expect(page.getByTestId('register-error')).toContainText(/passwords do not match/i);
}); });
test('should prevent submission when password is too short (HTML5 validation)', async ({ page }) => { test('Password too short', async ({ page }) => {
const timestamp = Date.now(); const timestamp = Date.now();
const email = `test${timestamp}@example.com`; const email = `test${timestamp}@example.com`;
@ -75,7 +75,7 @@ test.describe.serial('User Registration - Enabled', () => {
expect(validationMessage).toBeTruthy(); expect(validationMessage).toBeTruthy();
}); });
test('should successfully register a new user or show email error', async ({ page }) => { test('Register successfully', async ({ page }) => {
const timestamp = Date.now(); const timestamp = Date.now();
const email = `test${timestamp}@example.com`; const email = `test${timestamp}@example.com`;
const password = 'password123'; const password = 'password123';
@ -107,13 +107,13 @@ test.describe.serial('User Registration - Enabled', () => {
} }
}); });
test('should navigate to login page from link', async ({ page }) => { test('Link to login', async ({ page }) => {
await page.getByTestId('register-login-link').click(); await page.getByTestId('register-login-link').click();
await expect(page).toHaveURL(/\/login$/); await expect(page).toHaveURL(/\/login$/);
}); });
test('should show error for duplicate email registration', async ({ page }) => { test('Duplicate email error', async ({ page }) => {
const email = process.env.E2E_EMAIL || 'test@tududi.com'; const email = process.env.E2E_EMAIL || 'test@tududi.com';
const password = 'password123'; const password = 'password123';
@ -127,7 +127,7 @@ test.describe.serial('User Registration - Enabled', () => {
}); });
}); });
test.describe.serial('User Registration - Disabled', () => { test.describe.serial('Disabled', () => {
test.beforeAll(async ({ request, baseURL }) => { test.beforeAll(async ({ request, baseURL }) => {
const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080'; const appUrl = baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
@ -163,7 +163,7 @@ test.describe.serial('User Registration - Disabled', () => {
await page.goto(appUrl + '/register'); await page.goto(appUrl + '/register');
}); });
test('should show registration disabled error', async ({ page }) => { test('Shows disabled error', async ({ page }) => {
const timestamp = Date.now(); const timestamp = Date.now();
const email = `test${timestamp}@example.com`; const email = `test${timestamp}@example.com`;
const password = 'password123'; const password = 'password123';

View file

@ -1,27 +1,25 @@
import { test, expect } from '@playwright/test'; import { test, expect } from '@playwright/test';
test.describe.serial('Today View', () => { test.describe('Today', () => {
// Helper function to login via UI // Helper function to login via UI
async function loginViaUI(page, baseURL) { async function loginViaUI(page, baseURL) {
const appUrl = const appUrl =
baseURL ?? process.env.APP_URL ?? 'http://localhost:8080'; baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
await page.goto(`${appUrl}/login`); await page.goto(`${appUrl}/login`);
await page.fill( await page
'input[type="email"]', .getByTestId('login-email')
process.env.E2E_EMAIL || 'test@tududi.com' .fill(process.env.E2E_EMAIL || 'test@tududi.com');
); await page
await page.fill( .getByTestId('login-password')
'input[type="password"]', .fill(process.env.E2E_PASSWORD || 'password123');
process.env.E2E_PASSWORD || 'password123' await page.getByTestId('login-submit').click();
);
await page.click('button[type="submit"]');
// Wait for redirect to dashboard or today page // Wait for redirect to dashboard or today page
await page.waitForURL(/\/(dashboard|today)/, { timeout: 10000 }); await page.waitForURL(/\/(dashboard|today)/, { timeout: 10000 });
} }
test('should only show tasks with today flag in Planned section', async ({ test('Planned: only today=true tasks', async ({
page, page,
context, context,
baseURL, baseURL,
@ -62,11 +60,10 @@ test.describe.serial('Today View', () => {
} }
} }
// Navigate to today page and wait for metrics to load // Navigate to today page
await page.goto(`${appUrl}/today`); await page.goto(`${appUrl}/today`);
await page.waitForLoadState('networkidle');
// Check if Planned section exists using data-testid // Wait for Planned section to appear (indicates page loaded)
const plannedSection = page.getByTestId('planned-section'); const plannedSection = page.getByTestId('planned-section');
await expect(plannedSection).toBeVisible({ timeout: 10000 }); await expect(plannedSection).toBeVisible({ timeout: 10000 });
@ -88,7 +85,7 @@ test.describe.serial('Today View', () => {
} }
}); });
test('should show overdue tasks in Overdue section', async ({ test('Overdue: shows overdue tasks', async ({
page, page,
context, context,
baseURL, baseURL,
@ -122,10 +119,6 @@ test.describe.serial('Today View', () => {
// Navigate to today page // Navigate to today page
await page.goto(`${appUrl}/today`); 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 // Check if Overdue section exists using data-testid
const overdueSection = page.getByTestId('overdue-section'); const overdueSection = page.getByTestId('overdue-section');
@ -153,7 +146,7 @@ test.describe.serial('Today View', () => {
} }
}); });
test('should show tasks due today in Due Today section', async ({ test('Due Today: shows tasks', async ({
page, page,
context, context,
baseURL, baseURL,
@ -187,10 +180,6 @@ test.describe.serial('Today View', () => {
// Navigate to today page // Navigate to today page
await page.goto(`${appUrl}/today`); 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 // Check if Due Today section exists using data-testid
const dueTodaySection = page.getByTestId('due-today-section'); const dueTodaySection = page.getByTestId('due-today-section');
@ -217,7 +206,7 @@ test.describe.serial('Today View', () => {
} }
}); });
test('should allow collapsing and expanding sections', async ({ test('Collapse/expand sections', async ({
page, page,
baseURL, baseURL,
}) => { }) => {
@ -227,7 +216,6 @@ test.describe.serial('Today View', () => {
const appUrl = const appUrl =
baseURL ?? process.env.APP_URL ?? 'http://localhost:8080'; baseURL ?? process.env.APP_URL ?? 'http://localhost:8080';
await page.goto(`${appUrl}/today`); await page.goto(`${appUrl}/today`);
await page.waitForLoadState('networkidle');
// Test Planned section collapse/expand if it exists using data-testid // Test Planned section collapse/expand if it exists using data-testid
const plannedHeader = page.getByTestId('planned-section-header'); const plannedHeader = page.getByTestId('planned-section-header');

View file

@ -183,6 +183,7 @@ const Login: React.FC = () => {
setEmail(e.target.value) setEmail(e.target.value)
} }
className="w-full px-4 py-2 border dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100" className="w-full px-4 py-2 border dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
data-testid="login-email"
required required
/> />
</div> </div>
@ -202,12 +203,14 @@ const Login: React.FC = () => {
setPassword(e.target.value) setPassword(e.target.value)
} }
className="w-full px-4 py-2 border dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100" className="w-full px-4 py-2 border dark:border-gray-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100"
data-testid="login-password"
required required
/> />
</div> </div>
<button <button
type="submit" type="submit"
className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 transition-colors" className="w-full bg-blue-500 text-white py-2 rounded-lg hover:bg-blue-600 transition-colors"
data-testid="login-submit"
> >
{t('auth.login', 'Login')} {t('auth.login', 'Login')}
</button> </button>

View file

@ -355,7 +355,7 @@
"unknownProject": "Άγνωστο έργο", "unknownProject": "Άγνωστο έργο",
"tasks": "εργασίες", "tasks": "εργασίες",
"showingItems": "Εμφάνιση {{current}} από {{total}} στοιχεία", "showingItems": "Εμφάνιση {{current}} από {{total}} στοιχεία",
"overdue": "Υπερβολικό", "overdue": "Εκπρόθεσμο",
"planned": "Προγραμματισμένο", "planned": "Προγραμματισμένο",
"open": "Άνοιγμα", "open": "Άνοιγμα",
"completed": "Ολοκληρώθηκε" "completed": "Ολοκληρώθηκε"