diff options
Diffstat (limited to 'frontend/tests')
| -rw-r--r-- | frontend/tests/auth.spec.ts | 167 | ||||
| -rw-r--r-- | frontend/tests/crawl.spec.ts | 32 | ||||
| -rw-r--r-- | frontend/tests/e2e.spec.ts | 64 | ||||
| -rw-r--r-- | frontend/tests/font.spec.ts | 31 | ||||
| -rw-r--r-- | frontend/tests/mocked-api.spec.ts | 167 |
5 files changed, 0 insertions, 461 deletions
diff --git a/frontend/tests/auth.spec.ts b/frontend/tests/auth.spec.ts deleted file mode 100644 index 33ada85..0000000 --- a/frontend/tests/auth.spec.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { test, expect } from '@playwright/test'; - -/** - * E2E tests for authentication flows. - * - * These tests verify login behavior both with and without a password configured. - * The current setup assumes no password (default for dev), so the password-required - * tests are marked as skip. To run those, start the backend with --password=testpass. - */ - -test.describe('Authentication - No Password Required', () => { - test('should allow direct access to dashboard without login', async ({ page }) => { - // When no password is configured, users should be able to access - // the dashboard directly without seeing the login page - await page.goto('/v2/'); - - // Should not redirect to login - await expect(page).toHaveURL(/.*\/v2\/?$/); - - // Should see the dashboard elements - await expect(page.locator('h1.logo')).toContainText('🐱'); - await expect(page.getByText('Logout')).toBeVisible(); - }); - - test('should allow login with empty password', async ({ page }) => { - // Visit login page - await page.goto('/v2/login'); - - // Fill username and submit with empty password - await page.fill('input[id="username"]', 'neko'); - await page.click('button[type="submit"]'); - - // Should redirect to dashboard - await expect(page).toHaveURL(/.*\/v2\/?$/, { timeout: 5000 }); - await expect(page.locator('h1.logo')).toContainText('🐱'); - }); - - test('should report authenticated status via API when no password', async ({ request }) => { - // Check auth status - const response = await request.get('/api/auth'); - expect(response.ok()).toBeTruthy(); - - const data = await response.json(); - expect(data.authenticated).toBe(true); - }); -}); - -test.describe('Authentication - Password Required', () => { - // These tests require the backend to be started with a password - // Example: neko --password=testpass - // Skip by default since dev environment has no password - - test.skip('should redirect to login when accessing protected routes', async ({ page, context }) => { - // Clear any existing cookies - await context.clearCookies(); - - // Try to access dashboard - await page.goto('/v2/'); - - // Should redirect to login - await expect(page).toHaveURL(/.*\/login/, { timeout: 5000 }); - }); - - test.skip('should reject incorrect password', async ({ page }) => { - await page.goto('/v2/login'); - - // Enter wrong password - await page.fill('input[id="username"]', 'neko'); - await page.fill('input[type="password"]', 'wrongpassword'); - await page.click('button[type="submit"]'); - - // Should show error message - await expect(page.getByText(/bad credentials|login failed/i)).toBeVisible({ timeout: 3000 }); - - // Should still be on login page - await expect(page).toHaveURL(/.*\/login/); - }); - - test.skip('should accept correct password and redirect to dashboard', async ({ page }) => { - await page.goto('/v2/login'); - - // Enter correct password (must match what the server was started with) - await page.fill('input[id="username"]', 'neko'); - await page.fill('input[type="password"]', 'testpass'); - await page.click('button[type="submit"]'); - - // Should redirect to dashboard - await expect(page).toHaveURL(/.*\/v2\/?$/, { timeout: 5000 }); - await expect(page.locator('h1.logo')).toContainText('🐱'); - await expect(page.getByText('Logout')).toBeVisible(); - }); - - test.skip('should persist authentication across page reloads', async ({ page }) => { - // Login first - await page.goto('/v2/login'); - await page.fill('input[id="username"]', 'neko'); - await page.fill('input[type="password"]', 'testpass'); - await page.click('button[type="submit"]'); - await expect(page).toHaveURL(/.*\/v2\/?$/); - - // Reload the page - await page.reload(); - - // Should still be authenticated (not redirected to login) - await expect(page).toHaveURL(/.*\/v2\/?$/); - await expect(page.locator('h1.logo')).toContainText('🐱'); - }); - - test.skip('should logout and redirect to login page', async ({ page }) => { - // Login first - await page.goto('/v2/login'); - await page.fill('input[id="username"]', 'neko'); - await page.fill('input[type="password"]', 'testpass'); - await page.click('button[type="submit"]'); - await expect(page).toHaveURL(/.*\/v2\/?$/); - - // Click logout - await page.click('text=Logout'); - - // Should redirect to login - await expect(page).toHaveURL(/.*\/login/); - - // Try to access dashboard again - should redirect to login - await page.goto('/v2/'); - await expect(page).toHaveURL(/.*\/login/); - }); - - test.skip('should report unauthenticated status via API', async ({ request, context }) => { - // Clear cookies - await context.clearCookies(); - - // Check auth status - const response = await request.get('/api/auth'); - expect(response.ok()).toBeTruthy(); - - const data = await response.json(); - expect(data.authenticated).toBe(false); - }); -}); - -test.describe('Authentication - Complete Flow', () => { - test('should handle complete user flow without password', async ({ page }) => { - // 1. Access dashboard directly - await page.goto('/v2/'); - await expect(page.locator('h1.logo')).toContainText('🐱'); - - // 2. Navigate to settings - await page.click('text=Settings'); - await expect(page).toHaveURL(/.*\/settings/); - - // 3. Add a feed (this tests that API calls work when no password) - const feedUrl = 'http://example.com/rss.xml'; - await page.fill('input[type="url"]', feedUrl); - await page.click('text=Add Feed'); - - // Wait for success (feed should appear) - await expect(page.getByText(feedUrl)).toBeVisible({ timeout: 3000 }); - - // 4. Navigate back to main view - await page.goto('/v2/'); - await expect(page.locator('h1.logo')).toContainText('🐱'); - - // 5. Logout (should work even with no password) - await page.click('text=Logout'); - await expect(page).toHaveURL(/.*\/login/); - }); -}); diff --git a/frontend/tests/crawl.spec.ts b/frontend/tests/crawl.spec.ts deleted file mode 100644 index 175a764..0000000 --- a/frontend/tests/crawl.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Crawl Integration', () => { - test('should add a feed and see items after crawl', async ({ page }) => { - const mockFeedUrl = 'http://localhost:9090/mock_feed.xml'; - - // 1. Login and go to Settings - await page.goto('/v2/settings'); - - // 2. Add the mock feed - await page.fill('input[type="url"]', mockFeedUrl); - await page.click('text=Add Feed'); - - // Wait for feed to be added - await expect(page.getByText(mockFeedUrl)).toBeVisible({ timeout: 5000 }); - - // 3. Trigger Crawl - const crawlButton = page.getByRole('button', { name: /crawl/i }); - await expect(crawlButton).toBeVisible(); - - // Handle the alert - page.on('dialog', dialog => dialog.accept()); - await crawlButton.click(); - - // 4. Go to Home and check for items - await page.goto('/v2/'); - - // The mock feed has "Mock Item 1" and "Mock Item 2" - await expect(page.getByText('Mock Item 1')).toBeVisible({ timeout: 10000 }); - await expect(page.getByText('Mock Item 2')).toBeVisible({ timeout: 10000 }); - }); -}); diff --git a/frontend/tests/e2e.spec.ts b/frontend/tests/e2e.spec.ts deleted file mode 100644 index 2bdf478..0000000 --- a/frontend/tests/e2e.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Neko Reader E2E', () => { - test('should allow login, viewing feeds, and logout', async ({ page }) => { - // 1. Go to Login - await page.goto('/v2/login'); - await expect(page).toHaveTitle(/Neko/); - - // 2. Login - // 2. Login - // Password is empty by default in test env - await page.click('button[type="submit"]'); - - // Check for error message if login failed (optional, for debugging) - // await expect(page.locator('.error-message')).toBeVisible({ timeout: 2000 }).catch(() => {}); - - // 3. Verify Dashboard - // Keep checking for /v2/ or /v2 - await expect(page).toHaveURL(/.*\/v2\/?$/); - await expect(page.locator('h1.logo')).toContainText('🐱'); - await expect(page.getByText('Logout')).toBeVisible(); - - // 4. Verify Feed List - await page.click('text=Settings'); - await expect(page).toHaveURL(/.*\/v2\/settings/); - - // Add a feed - const feedUrl = 'http://localhost:9090/mock_feed.xml'; - await page.fill('input[type="url"]', feedUrl); - await page.click('text=Add Feed'); - - // Wait for it to appear - await expect(page.getByText(feedUrl)).toBeVisible(); - - const waitForLoader = async () => { - await page.waitForFunction(() => { - const loading = document.querySelector('.feed-items-loading') || - document.body.innerText.includes('Loading...'); - return !loading; - }, { timeout: 15000 }); - }; - - // 5. Navigate to Feed - console.log('Step 5: Navigate to Home'); - await page.goto('/v2/'); - await expect(page).toHaveURL(/.*\/v2\/?$/); - // Default view is now the stream. - // It should NOT show "Select a feed" anymore. - // Wait for items or "No items found" or loading state - await waitForLoader(); - await expect( - page - .locator('.feed-items') - .or(page.getByText('No items found')) - .or(page.locator('.feed-items-error')) - ).toBeVisible({ timeout: 10000 }); - - // 6. Verify Tag View - // 6. Logout - console.log('Step 6: Logout'); - await page.click('text=Logout'); - await expect(page).toHaveURL(/.*\/v2\/login/); - }); -}); diff --git a/frontend/tests/font.spec.ts b/frontend/tests/font.spec.ts deleted file mode 100644 index 0723f38..0000000 --- a/frontend/tests/font.spec.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Font Theme Settings', () => { - test('should change font family when theme starts', async ({ page }) => { - // 1. Login - await page.goto('/v2/login'); - await page.click('button[type="submit"]'); - await expect(page).toHaveURL(/.*\/v2\/?$/); - - // 2. Go to Settings - await page.click('text=Settings'); - await expect(page).toHaveURL(/.*\/v2\/settings/); - - // 3. Verify Default Font (Palatino) - // We check the computed style of the dashboard container or a body element - const dashboard = page.locator('.dashboard'); - await expect(dashboard).toHaveCSS('font-family', /Palatino/); - - // 4. Change to Sans-Serif - await page.selectOption('select.font-select', 'sans'); - - // 5. Verify Sans Font (Inter) - await expect(dashboard).toHaveCSS('font-family', /Inter/); - - // 6. Change to Monospace - await page.selectOption('select.font-select', 'mono'); - - // 7. Verify Mono Font (Menlo or Monaco or Courier) - await expect(dashboard).toHaveCSS('font-family', /Menlo|Monaco|Courier/); - }); -}); diff --git a/frontend/tests/mocked-api.spec.ts b/frontend/tests/mocked-api.spec.ts deleted file mode 100644 index 06f6c17..0000000 --- a/frontend/tests/mocked-api.spec.ts +++ /dev/null @@ -1,167 +0,0 @@ -import { test, expect } from '@playwright/test'; - -test.describe('Mocked API UI Tests', () => { - test.beforeEach(async ({ page }) => { - // Log browser console for debugging - page.on('console', msg => { - if (msg.type() === 'error') console.log(`BROWSER ERROR: ${msg.text()} `); - }); - - // 1. Mock Auth - simulate logged in session - await page.route('**/api/auth', async (route) => { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ status: 'ok', authenticated: true }), - }); - }); - - // 2. Mock Feeds - await page.route('**/api/feed/', async (route) => { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify([ - { _id: 1, title: 'Mock Feed 1', url: 'http://mock1.com', category: 'News' }, - { _id: 2, title: 'Mock Feed 2', url: 'http://mock2.com', category: 'Tech' }, - ]), - }); - }); - - // 3. Mock Tags - await page.route('**/api/tag', async (route) => { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify([ - { title: 'News' }, - { title: 'Tech' }, - ]), - }); - }); - - // 4. Mock Stream/Items - await page.route('**/api/stream*', async (route) => { - const url = new URL(route.request().url()); - const maxId = url.searchParams.get('max_id'); - - if (maxId) { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify([]), - }); - return; - } - - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify([ - { - _id: 101, - feed_id: 1, - title: 'Mock Item Unread', - url: 'http://mock1.com/1', - description: 'This is an unread item', - publish_date: new Date().toISOString(), - read: false, - starred: false, - feed_title: 'Mock Feed 1' - }, - { - _id: 102, - feed_id: 2, - title: 'Mock Item Starred', - url: 'http://mock2.com/1', - description: 'This is a starred and read item', - publish_date: new Date().toISOString(), - read: true, - starred: true, - feed_title: 'Mock Feed 2' - } - ]), - }); - }); - - // 5. Mock Item Update (for marking read/starred) - await page.route('**/api/item/**', async (route) => { - if (route.request().method() === 'PUT') { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ status: 'ok' }), - }); - } else { - await route.continue(); - } - }); - - // 6. Mock Logout - await page.route('**/api/logout', async (route) => { - await route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ status: 'ok' }), - }); - }); - }); - - test('should load dashboard with mocked feeds and items', async ({ page }) => { - await page.goto('/v2/'); - - // Wait for the logo to appear (means we are on the dashboard) - await expect(page.locator('h1.logo')).toBeVisible({ timeout: 15000 }); - - // Verify items in main view (ensure they load first) - await expect(page.getByText('Mock Item Unread')).toBeVisible({ timeout: 10000 }); - await expect(page.getByText('Mock Item Starred')).toBeVisible(); - - // Verify feeds in sidebar - // Click on the Feeds header to expand - await page.getByText(/Feeds/i).click(); - - // Wait for mocked feeds to appear in sidebar - // We use a more specific selector to avoid matching the feed_title in the item list - await expect(page.locator('.feed-list-items').getByText('Mock Feed 1')).toBeVisible({ timeout: 10000 }); - await expect(page.locator('.feed-list-items').getByText('Mock Feed 2')).toBeVisible(); - - // Verify "unread" filter is active by default - const unreadFilterLink = page.locator('.unread_filter a'); - await expect(unreadFilterLink).toHaveClass(/active/); - }); - - test('should filter by mocked tag', async ({ page }) => { - await page.goto('/v2/'); - - // Click on Tech tag - await page.getByText('Tech', { exact: true }).click(); - - // URL should update - await expect(page).toHaveURL(/.*\/tag\/Tech/); - - // Verify feed items are still visible (mock stream returns both regardless of tag in this simple mock) - await expect(page.getByText('Mock Item Unread')).toBeVisible(); - }); - - test('should toggle item star status', async ({ page }) => { - await page.goto('/v2/'); - - const unreadItem = page.locator('.feed-item.unread').first(); - const starButton = unreadItem.getByTitle('Star'); - - await starButton.click(); - - // Expect star to change to "Unstar" (UI optimistic update) - await expect(starButton).toHaveAttribute('title', 'Unstar'); - }); - - test('should logout using mocked API', async ({ page }) => { - await page.goto('/v2/'); - - await page.getByText('logout').click(); - - // Should redirect to login - await expect(page).toHaveURL(/.*\/login/); - }); -}); |
