Keep/keep-notes/tests/collaboration.spec.ts
sepehr 640fcb26f7 fix: improve note interactions and markdown LaTeX support
## Bug Fixes

### Note Card Actions
- Fix broken size change functionality (missing state declaration)
- Implement React 19 useOptimistic for instant UI feedback
- Add startTransition for non-blocking updates
- Ensure smooth animations without page refresh
- All note actions now work: pin, archive, color, size, checklist

### Markdown LaTeX Rendering
- Add remark-math and rehype-katex plugins
- Support inline equations with dollar sign syntax
- Support block equations with double dollar sign syntax
- Import KaTeX CSS for proper styling
- Equations now render correctly instead of showing raw LaTeX

## Technical Details

- Replace undefined currentNote references with optimistic state
- Add optimistic updates before server actions for instant feedback
- Use router.refresh() in transitions for smart cache invalidation
- Install remark-math, rehype-katex, and katex packages

## Testing

- Build passes successfully with no TypeScript errors
- Dev server hot-reloads changes correctly
2026-01-09 22:13:49 +01:00

133 lines
4.8 KiB
TypeScript

import { test, expect } from '@playwright/test';
test.describe('Collaboration Feature', () => {
test.beforeEach(async ({ page }) => {
// Login before each test
await page.goto('http://localhost:3000/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await page.waitForURL('http://localhost:3000/');
});
test('COLLAB-2: Add collaborator to existing note', async ({ page, context }) => {
// Start from the main page
await page.goto('http://localhost:3000/');
// Find a note card (should have at least one)
const noteCard = page.locator('[data-testid="note-card"]').first();
await expect(noteCard).toBeVisible();
// Open collaborator dialog from the note menu
await noteCard.hover();
await page.click('[aria-label="More options"]');
// Click "Share with collaborators"
await page.click('text=Share with collaborators');
// Verify dialog opens
const dialog = page.locator('[role="dialog"]');
await expect(dialog).toBeVisible();
await expect(dialog.locator('text=Share with collaborators')).toBeVisible();
// Try to add a collaborator (note: user may not exist)
await page.fill('input[type="email"]', 'collaborator@example.com');
await page.click('button:has-text("Invite")');
// Wait for response (either success or error toast)
await page.waitForTimeout(2000);
// Verify either:
// 1. Success: User added to list
// 2. Error: User not found message
const collaboratorInList = page.locator('text=collaborator@example.com');
const errorMessage = page.locator('.toast:has-text("User not found")');
const collaboratorAdded = await collaboratorInList.count() > 0;
const errorShown = await errorMessage.count() > 0;
expect(collaboratorAdded || errorShown).toBeTruthy();
});
test('COLLAB-2: Remove collaborator from note', async ({ page }) => {
// This test assumes a note with collaborators exists
await page.goto('http://localhost:3000/');
// Find a note card
const noteCard = page.locator('[data-testid="note-card"]').first();
await noteCard.hover();
await page.click('[aria-label="More options"]');
await page.click('text=Share with collaborators');
const dialog = page.locator('[role="dialog"]');
await expect(dialog).toBeVisible();
// Check if there are any collaborators listed
const collaboratorItems = dialog.locator('[data-testid="collaborator-item"]');
const count = await collaboratorItems.count();
if (count > 0) {
// Get initial count
const initialCount = count;
// Remove first collaborator
await collaboratorItems.first().hover();
await collaboratorItems.first().locator('button[aria-label="Remove"]').click();
// Wait for update
await page.waitForTimeout(1000);
// Verify count decreased
const newCount = await dialog.locator('[data-testid="collaborator-item"]').count();
expect(newCount).toBeLessThan(initialCount);
} else {
// Skip test if no collaborators exist
test.skip(true, 'No collaborators to remove');
}
});
test('COLLAB-2: View collaborators list', async ({ page }) => {
await page.goto('http://localhost:3000/');
const noteCard = page.locator('[data-testid="note-card"]').first();
await noteCard.hover();
await page.click('[aria-label="More options"]');
await page.click('text=Share with collaborators');
const dialog = page.locator('[role="dialog"]');
await expect(dialog).toBeVisible();
// Verify "People with access" section exists
await expect(dialog.locator('text=People with access')).toBeVisible();
// Check for owner badge
const ownerBadge = dialog.locator('text=Owner');
await expect(ownerBadge).toBeVisible();
});
test('COLLAB-2: Non-owner cannot remove collaborators', async ({ page }) => {
// This test requires setup with a shared note where current user is not owner
// For now, we'll test the UI shows correct state
await page.goto('http://localhost:3000/');
const noteCard = page.locator('[data-testid="note-card"]').first();
await noteCard.hover();
await page.click('[aria-label="More options"]');
await page.click('text=Share with collaborators');
const dialog = page.locator('[role="dialog"]');
await expect(dialog).toBeVisible();
// Verify "Only the owner can manage collaborators" message appears if not owner
const description = dialog.locator('text=You have access to this note');
const isNotOwner = await description.count() > 0;
if (isNotOwner) {
// Verify no remove buttons are visible
const removeButtons = dialog.locator('button[aria-label="Remove"]');
expect(await removeButtons.count()).toBe(0);
}
});
});