Fix an issue with tasks appearing in today without a flag (#640)
This commit is contained in:
parent
08be7f8eda
commit
270a80f71b
8 changed files with 49 additions and 53 deletions
|
|
@ -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 } },
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -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 () => {
|
||||||
|
|
|
||||||
|
|
@ -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 },
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,7 @@
|
||||||
"unknownProject": "Άγνωστο έργο",
|
"unknownProject": "Άγνωστο έργο",
|
||||||
"tasks": "εργασίες",
|
"tasks": "εργασίες",
|
||||||
"showingItems": "Εμφάνιση {{current}} από {{total}} στοιχεία",
|
"showingItems": "Εμφάνιση {{current}} από {{total}} στοιχεία",
|
||||||
"overdue": "Υπερβολικό",
|
"overdue": "Εκπρόθεσμο",
|
||||||
"planned": "Προγραμματισμένο",
|
"planned": "Προγραμματισμένο",
|
||||||
"open": "Άνοιγμα",
|
"open": "Άνοιγμα",
|
||||||
"completed": "Ολοκληρώθηκε"
|
"completed": "Ολοκληρώθηκε"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue