feat: Memento avec dates, Markdown, reminders et auth
Tests Playwright validés ✅:
- Création de notes: OK
- Modification titre: OK
- Modification contenu: OK
- Markdown éditable avec preview: OK
Fonctionnalités:
- date-fns: dates relatives sur cards
- react-markdown + remark-gfm
- Markdown avec toggle edit/preview
- Recherche améliorée (titre/contenu/labels/checkItems)
- Reminder recurrence/location (schema)
- NextAuth.js + User/Account/Session
- userId dans Note (optionnel)
- 4 migrations créées
Ready for production + auth integration
This commit is contained in:
423
keep-notes/tests/reminder-dialog.spec.ts
Normal file
423
keep-notes/tests/reminder-dialog.spec.ts
Normal file
@@ -0,0 +1,423 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
|
||||
test.describe('Note Input - Reminder Dialog', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
// Expand the note input
|
||||
await page.click('input[placeholder="Take a note..."]');
|
||||
await expect(page.locator('input[placeholder="Title"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should open dialog when clicking Bell icon (not prompt)', async ({ page }) => {
|
||||
// Set up listener for prompt dialogs - should NOT appear
|
||||
let promptAppeared = false;
|
||||
page.on('dialog', () => {
|
||||
promptAppeared = true;
|
||||
});
|
||||
|
||||
// Click the Bell button
|
||||
const bellButton = page.locator('button:has(svg.lucide-bell)');
|
||||
await bellButton.click();
|
||||
|
||||
// Verify dialog opened (NOT a browser prompt)
|
||||
const dialog = page.locator('[role="dialog"]');
|
||||
await expect(dialog).toBeVisible();
|
||||
|
||||
// Verify dialog title
|
||||
await expect(page.locator('h2:has-text("Set Reminder")')).toBeVisible();
|
||||
|
||||
// Verify no prompt appeared
|
||||
expect(promptAppeared).toBe(false);
|
||||
|
||||
// Verify date and time inputs exist
|
||||
await expect(page.locator('input[type="date"]')).toBeVisible();
|
||||
await expect(page.locator('input[type="time"]')).toBeVisible();
|
||||
|
||||
// Verify buttons
|
||||
await expect(page.locator('button:has-text("Cancel")')).toBeVisible();
|
||||
await expect(page.locator('button:has-text("Set Reminder")')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should have default values (tomorrow 9am)', async ({ page }) => {
|
||||
// Click Bell
|
||||
await page.click('button:has(svg.lucide-bell)');
|
||||
|
||||
// Get tomorrow's date
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const expectedDate = tomorrow.toISOString().split('T')[0];
|
||||
|
||||
// Check date input
|
||||
const dateInput = page.locator('input[type="date"]');
|
||||
await expect(dateInput).toHaveValue(expectedDate);
|
||||
|
||||
// Check time input
|
||||
const timeInput = page.locator('input[type="time"]');
|
||||
await expect(timeInput).toHaveValue('09:00');
|
||||
});
|
||||
|
||||
test('should close dialog on Cancel', async ({ page }) => {
|
||||
// Open dialog
|
||||
await page.click('button:has(svg.lucide-bell)');
|
||||
await expect(page.locator('[role="dialog"]')).toBeVisible();
|
||||
|
||||
// Click Cancel
|
||||
await page.click('button:has-text("Cancel")');
|
||||
|
||||
// Dialog should close
|
||||
await expect(page.locator('[role="dialog"]')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should close dialog on X button', async ({ page }) => {
|
||||
// Open dialog
|
||||
await page.click('button:has(svg.lucide-bell)');
|
||||
await expect(page.locator('[role="dialog"]')).toBeVisible();
|
||||
|
||||
// Click X button (close button in dialog)
|
||||
const closeButton = page.locator('[role="dialog"] button[data-slot="dialog-close"]');
|
||||
await closeButton.click();
|
||||
|
||||
// Dialog should close
|
||||
await expect(page.locator('[role="dialog"]')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should validate empty date/time', async ({ page }) => {
|
||||
// Open dialog
|
||||
await page.click('button:has(svg.lucide-bell)');
|
||||
|
||||
// Clear date
|
||||
const dateInput = page.locator('input[type="date"]');
|
||||
await dateInput.fill('');
|
||||
|
||||
// Click Set Reminder
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
|
||||
// Should show warning toast (not close dialog)
|
||||
// We look for the toast notification
|
||||
await expect(page.locator('text=Please enter date and time')).toBeVisible();
|
||||
|
||||
// Dialog should still be open
|
||||
await expect(page.locator('[role="dialog"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should validate past date', async ({ page }) => {
|
||||
// Open dialog
|
||||
await page.click('button:has(svg.lucide-bell)');
|
||||
|
||||
// Set date to yesterday
|
||||
const yesterday = new Date();
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const pastDate = yesterday.toISOString().split('T')[0];
|
||||
|
||||
const dateInput = page.locator('input[type="date"]');
|
||||
await dateInput.fill(pastDate);
|
||||
|
||||
// Click Set Reminder
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
|
||||
// Should show error toast
|
||||
await expect(page.locator('text=Reminder must be in the future')).toBeVisible();
|
||||
|
||||
// Dialog should still be open
|
||||
await expect(page.locator('[role="dialog"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should set reminder successfully with valid date', async ({ page }) => {
|
||||
// Open dialog
|
||||
await page.click('button:has(svg.lucide-bell)');
|
||||
|
||||
// Set future date (tomorrow already default)
|
||||
const timeInput = page.locator('input[type="time"]');
|
||||
await timeInput.fill('14:30');
|
||||
|
||||
// Click Set Reminder
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
|
||||
// Should show success toast
|
||||
await expect(page.locator('text=/Reminder set for/')).toBeVisible();
|
||||
|
||||
// Dialog should close
|
||||
await expect(page.locator('[role="dialog"]')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should clear fields after successful reminder', async ({ page }) => {
|
||||
// Open dialog
|
||||
await page.click('button:has(svg.lucide-bell)');
|
||||
|
||||
// Set and confirm
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
|
||||
// Wait for dialog to close
|
||||
await expect(page.locator('[role="dialog"]')).not.toBeVisible();
|
||||
|
||||
// Open again
|
||||
await page.click('button:has(svg.lucide-bell)');
|
||||
|
||||
// Should have default values again (tomorrow 9am)
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const expectedDate = tomorrow.toISOString().split('T')[0];
|
||||
|
||||
await expect(page.locator('input[type="date"]')).toHaveValue(expectedDate);
|
||||
await expect(page.locator('input[type="time"]')).toHaveValue('09:00');
|
||||
});
|
||||
|
||||
test('should allow custom date and time selection', async ({ page }) => {
|
||||
// Open dialog
|
||||
await page.click('button:has(svg.lucide-bell)');
|
||||
|
||||
// Set custom date (next week)
|
||||
const nextWeek = new Date();
|
||||
nextWeek.setDate(nextWeek.getDate() + 7);
|
||||
const customDate = nextWeek.toISOString().split('T')[0];
|
||||
|
||||
const dateInput = page.locator('input[type="date"]');
|
||||
await dateInput.fill(customDate);
|
||||
|
||||
// Set custom time
|
||||
const timeInput = page.locator('input[type="time"]');
|
||||
await timeInput.fill('15:45');
|
||||
|
||||
// Submit
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
|
||||
// Should show success with the date/time
|
||||
await expect(page.locator('text=/Reminder set for/')).toBeVisible();
|
||||
await expect(page.locator('[role="dialog"]')).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Note Editor - Reminder Dialog', () => {
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
|
||||
// Create a test note
|
||||
await page.click('input[placeholder="Take a note..."]');
|
||||
await page.fill('input[placeholder="Title"]', 'Test Note for Reminder');
|
||||
await page.fill('textarea[placeholder="Take a note..."]', 'This note will have a reminder');
|
||||
await page.click('button:has-text("Add")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Open the note for editing
|
||||
await page.click('text=Test Note for Reminder');
|
||||
await page.waitForTimeout(300);
|
||||
});
|
||||
|
||||
test('should open reminder dialog in note editor', async ({ page }) => {
|
||||
// Click the Bell button in note editor
|
||||
const bellButton = page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first();
|
||||
await bellButton.click();
|
||||
|
||||
// Should open a second dialog for reminder
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Verify reminder dialog opened
|
||||
await expect(page.locator('h2:has-text("Set Reminder")')).toBeVisible();
|
||||
|
||||
// Verify date and time inputs exist
|
||||
await expect(page.locator('input[type="date"]')).toBeVisible();
|
||||
await expect(page.locator('input[type="time"]')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should set reminder on existing note', async ({ page }) => {
|
||||
// Click Bell button
|
||||
await page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first().click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Set date and time
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const dateString = tomorrow.toISOString().split('T')[0];
|
||||
|
||||
await page.fill('input[type="date"]', dateString);
|
||||
await page.fill('input[type="time"]', '14:30');
|
||||
|
||||
// Click Set Reminder
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
|
||||
// Should show success toast
|
||||
await expect(page.locator('text=/Reminder set for/')).toBeVisible();
|
||||
|
||||
// Reminder dialog should close
|
||||
await page.waitForTimeout(300);
|
||||
await expect(page.locator('h2:has-text("Set Reminder")')).not.toBeVisible();
|
||||
});
|
||||
|
||||
test('should show bell button as active when reminder is set', async ({ page }) => {
|
||||
// Set reminder
|
||||
await page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first().click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const dateString = tomorrow.toISOString().split('T')[0];
|
||||
|
||||
await page.fill('input[type="date"]', dateString);
|
||||
await page.fill('input[type="time"]', '10:00');
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Bell button should have active styling (text-blue-600)
|
||||
const bellButton = page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first();
|
||||
const className = await bellButton.getAttribute('class');
|
||||
expect(className).toContain('text-blue-600');
|
||||
});
|
||||
|
||||
test('should allow editing existing reminder', async ({ page }) => {
|
||||
// Set initial reminder
|
||||
await page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first().click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const dateString = tomorrow.toISOString().split('T')[0];
|
||||
|
||||
await page.fill('input[type="date"]', dateString);
|
||||
await page.fill('input[type="time"]', '10:00');
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Open reminder dialog again
|
||||
await page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first().click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Should show previous values
|
||||
const dateInput = page.locator('input[type="date"]');
|
||||
const timeInput = page.locator('input[type="time"]');
|
||||
|
||||
await expect(dateInput).toHaveValue(dateString);
|
||||
await expect(timeInput).toHaveValue('10:00');
|
||||
|
||||
// Change time
|
||||
await timeInput.fill('15:00');
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
|
||||
await expect(page.locator('text=/Reminder set for/')).toBeVisible();
|
||||
});
|
||||
|
||||
test('should allow removing reminder', async ({ page }) => {
|
||||
// Set reminder first
|
||||
await page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first().click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const dateString = tomorrow.toISOString().split('T')[0];
|
||||
|
||||
await page.fill('input[type="date"]', dateString);
|
||||
await page.fill('input[type="time"]', '10:00');
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Open reminder dialog again
|
||||
await page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first().click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
// Should see "Remove Reminder" button
|
||||
await expect(page.locator('button:has-text("Remove Reminder")')).toBeVisible();
|
||||
|
||||
// Click Remove Reminder
|
||||
await page.click('button:has-text("Remove Reminder")');
|
||||
|
||||
// Should show success toast
|
||||
await expect(page.locator('text=Reminder removed')).toBeVisible();
|
||||
|
||||
// Bell button should not be active anymore
|
||||
await page.waitForTimeout(300);
|
||||
const bellButton = page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first();
|
||||
const className = await bellButton.getAttribute('class');
|
||||
expect(className).not.toContain('text-blue-600');
|
||||
});
|
||||
|
||||
test('should persist reminder after saving note', async ({ page }) => {
|
||||
// Set reminder
|
||||
await page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first().click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const dateString = tomorrow.toISOString().split('T')[0];
|
||||
|
||||
await page.fill('input[type="date"]', dateString);
|
||||
await page.fill('input[type="time"]', '14:00');
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Save the note
|
||||
await page.click('button:has-text("Save")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Reopen the note
|
||||
await page.click('text=Test Note for Reminder');
|
||||
await page.waitForTimeout(300);
|
||||
|
||||
// Bell button should still be active
|
||||
const bellButton = page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first();
|
||||
const className = await bellButton.getAttribute('class');
|
||||
expect(className).toContain('text-blue-600');
|
||||
|
||||
// Open reminder dialog to verify values
|
||||
await bellButton.click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const dateInput = page.locator('input[type="date"]');
|
||||
const timeInput = page.locator('input[type="time"]');
|
||||
|
||||
await expect(dateInput).toHaveValue(dateString);
|
||||
await expect(timeInput).toHaveValue('14:00');
|
||||
});
|
||||
|
||||
test('should show bell icon on note card when reminder is set', async ({ page }) => {
|
||||
// Set reminder
|
||||
await page.locator('[role="dialog"]:visible button:has(svg.lucide-bell)').first().click();
|
||||
await page.waitForTimeout(200);
|
||||
|
||||
const tomorrow = new Date();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
const dateString = tomorrow.toISOString().split('T')[0];
|
||||
|
||||
await page.fill('input[type="date"]', dateString);
|
||||
await page.fill('input[type="time"]', '10:00');
|
||||
await page.click('button:has-text("Set Reminder")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Save and close
|
||||
await page.click('button:has-text("Save")');
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
// Check that note card has bell icon
|
||||
const noteCard = page.locator('text=Test Note for Reminder').locator('..');
|
||||
await expect(noteCard.locator('svg.lucide-bell')).toBeVisible();
|
||||
});
|
||||
|
||||
test.afterEach(async ({ page }) => {
|
||||
// Close any open dialogs
|
||||
const dialogs = page.locator('[role="dialog"]');
|
||||
const count = await dialogs.count();
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const cancelButton = page.locator('button:has-text("Cancel")').first();
|
||||
if (await cancelButton.isVisible()) {
|
||||
await cancelButton.click();
|
||||
await page.waitForTimeout(200);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete test note
|
||||
try {
|
||||
const testNote = page.locator('text=Test Note for Reminder').first();
|
||||
if (await testNote.isVisible()) {
|
||||
await testNote.hover();
|
||||
await page.click('button:has(svg.lucide-more-vertical)').first();
|
||||
await page.click('text=Delete').first();
|
||||
|
||||
// Confirm delete
|
||||
page.once('dialog', dialog => dialog.accept());
|
||||
await page.waitForTimeout(300);
|
||||
}
|
||||
} catch (e) {
|
||||
// Note might already be deleted
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user