Files
Remote-Terminal-for-MeshCore/tests/e2e/specs/channel-info-and-search.spec.ts
2026-03-08 14:30:47 -07:00

148 lines
5.3 KiB
TypeScript

import { test, expect } from '@playwright/test';
import { seedChannelMessages } from '../helpers/seed';
import { ensureFlightlessChannel } from '../helpers/api';
const CHANNEL_NAME = '#flightless';
const SEED_COUNT = 30;
/**
* Seed #flightless with unique, searchable messages.
* Returns the channel key and a unique search token.
*/
function seedFlightlessMessages() {
const token = `e2e-search-${Date.now()}`;
const seeded = seedChannelMessages({
channelName: CHANNEL_NAME,
count: SEED_COUNT,
// Use unique text so search can find them reliably
// Note: seedChannelMessages uses "seed-N" as text by default,
// but we need our unique token in there. We'll search for "seed-" instead
// since that's what the seed helper generates.
outgoingEvery: 5,
includePaths: true,
});
return { key: seeded.key, token };
}
test.describe('Channel info pane', () => {
let channelKey: string;
test.beforeAll(async () => {
await ensureFlightlessChannel();
const seeded = seedFlightlessMessages();
channelKey = seeded.key;
});
test('opens channel info pane and shows message activity', async ({ page }) => {
await page.goto(`/#channel/${channelKey}/flightless`);
await expect(page.getByRole('status', { name: 'Radio OK' })).toBeVisible();
// Wait for messages to load
await expect(page.getByText('seed-0')).toBeVisible({ timeout: 15_000 });
// Click the channel name in the header to open info pane
const headerTitle = page.locator('h2').filter({ hasText: '#flightless' });
await headerTitle.click();
// Channel info pane should open as a sheet
const infoPane = page.getByRole('dialog');
await expect(infoPane).toBeVisible({ timeout: 10_000 });
// Should show channel name
await expect(infoPane.getByText('#flightless')).toBeVisible();
// Should show channel key
await expect(infoPane.getByText(channelKey.toLowerCase())).toBeVisible();
// Should show "Hashtag" badge
await expect(infoPane.getByText('Hashtag')).toBeVisible();
// Should show "Message Activity" section with counts
await expect(infoPane.getByText('Message Activity')).toBeVisible();
await expect(infoPane.getByText('All Time')).toBeVisible();
// All Time count should be non-zero (our seeded messages)
// InfoItem renders: <span>All Time</span><p>VALUE</p> — use CSS sibling selector
const allTimeValue = infoPane.locator('span:text-is("All Time") + p');
const count = await allTimeValue.textContent();
expect(Number(count?.replace(/,/g, ''))).toBeGreaterThanOrEqual(SEED_COUNT);
// Should show "First Message" section
await expect(infoPane.getByText('First Message')).toBeVisible();
});
});
test.describe('Message search and jump-to-message', () => {
let channelKey: string;
test.beforeAll(async () => {
await ensureFlightlessChannel();
const seeded = seedFlightlessMessages();
channelKey = seeded.key;
});
test('search finds seeded messages', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('status', { name: 'Radio OK' })).toBeVisible();
// Open search view via sidebar
await page.getByText('Message Search').click();
// Should show search input
const searchInput = page.getByPlaceholder('Search all messages...');
await expect(searchInput).toBeVisible();
// Search for seeded messages (seed helper creates "seed-N" text)
await searchInput.fill('seed-1');
// Wait for search results to appear
await expect(page.getByText('seed-1', { exact: false }).first()).toBeVisible({
timeout: 10_000,
});
// Results should show the channel name
await expect(page.getByText('#flightless').first()).toBeVisible();
// Results should show "Channel" badge
await expect(page.getByText('Channel').first()).toBeVisible();
});
test('clicking a search result jumps to the message in conversation', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('status', { name: 'Radio OK' })).toBeVisible();
// Open search
await page.getByText('Message Search').click();
const searchInput = page.getByPlaceholder('Search all messages...');
await searchInput.fill('seed-15');
// Wait for results
const result = page.getByText('seed-15', { exact: false }).first();
await expect(result).toBeVisible({ timeout: 10_000 });
// Click the search result to jump to it
await result.click();
// Should navigate to the #flightless conversation
await expect(page.getByPlaceholder(/message #flightless/i)).toBeVisible({ timeout: 15_000 });
// The target message should be visible in the conversation (not search results)
// Scope to [data-message-id] to avoid matching leftover search <mark> elements
const messageContainer = page.locator('[data-message-id]').filter({ hasText: 'seed-15' });
await expect(messageContainer.first()).toBeVisible({ timeout: 10_000 });
});
test('search returns no results for nonsense query', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('status', { name: 'Radio OK' })).toBeVisible();
await page.getByText('Message Search').click();
const searchInput = page.getByPlaceholder('Search all messages...');
await searchInput.fill('zzz-nonexistent-query-zzz');
await expect(page.getByText(/No messages found/)).toBeVisible({ timeout: 10_000 });
});
});