aboutsummaryrefslogtreecommitdiffstats
path: root/frontend/tests
diff options
context:
space:
mode:
Diffstat (limited to 'frontend/tests')
-rw-r--r--frontend/tests/auth.spec.ts167
-rw-r--r--frontend/tests/crawl.spec.ts32
-rw-r--r--frontend/tests/e2e.spec.ts64
-rw-r--r--frontend/tests/font.spec.ts31
-rw-r--r--frontend/tests/mocked-api.spec.ts167
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/);
- });
-});