mirror of
https://github.com/jkingsman/Remote-Terminal-for-MeshCore.git
synced 2026-07-05 17:32:10 +02:00
Add a smatttering of tests and fix return-to-public after channel deletion
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { createChannel, deleteChannel, getChannels } from '../helpers/api';
|
||||
|
||||
test.describe('Conversation deletion flow', () => {
|
||||
test.beforeAll(async () => {
|
||||
const channels = await getChannels();
|
||||
if (!channels.some((c) => c.name === 'Public')) {
|
||||
await createChannel('Public');
|
||||
}
|
||||
});
|
||||
|
||||
test('deleting active channel removes it from sidebar and clears composer', async ({ page }) => {
|
||||
const channelName = `#e2edel${Date.now().toString().slice(-6)}`;
|
||||
const channel = await createChannel(channelName);
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText(channelName, { exact: true }).first().click();
|
||||
await expect(page.getByPlaceholder(new RegExp(`message\\s+${channelName}`, 'i'))).toBeVisible();
|
||||
|
||||
page.once('dialog', async (dialog) => {
|
||||
await dialog.accept();
|
||||
});
|
||||
await page.getByTitle('Delete').click();
|
||||
|
||||
await expect(page.getByText('Channel deleted')).toBeVisible();
|
||||
await expect(page.getByText(channelName, { exact: true })).not.toBeVisible();
|
||||
await expect(page.getByPlaceholder(new RegExp(`message\\s+${channelName}`, 'i'))).not.toBeVisible();
|
||||
|
||||
try {
|
||||
await deleteChannel(channel.key);
|
||||
} catch {
|
||||
// Best-effort cleanup
|
||||
}
|
||||
});
|
||||
|
||||
test('deleting active channel falls back to Public conversation', async ({ page }) => {
|
||||
const channelName = `#e2edel${Date.now().toString().slice(-6)}`;
|
||||
const channel = await createChannel(channelName);
|
||||
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText(channelName, { exact: true }).first().click();
|
||||
await expect(page.getByPlaceholder(new RegExp(`message\\s+${channelName}`, 'i'))).toBeVisible();
|
||||
|
||||
page.once('dialog', async (dialog) => {
|
||||
await dialog.accept();
|
||||
});
|
||||
await page.getByTitle('Delete').click();
|
||||
|
||||
await expect(page.getByPlaceholder(/message\s+public/i)).toBeVisible({ timeout: 15_000 });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,75 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import {
|
||||
createChannel,
|
||||
deleteChannel,
|
||||
getSettings,
|
||||
updateSettings,
|
||||
type Favorite,
|
||||
} from '../helpers/api';
|
||||
|
||||
test.describe('Favorites persistence', () => {
|
||||
let originalFavorites: Favorite[] = [];
|
||||
let channelName = '';
|
||||
let channelKey = '';
|
||||
|
||||
test.beforeAll(async () => {
|
||||
const settings = await getSettings();
|
||||
originalFavorites = settings.favorites ?? [];
|
||||
|
||||
// Start deterministic: no favorites
|
||||
await updateSettings({ favorites: [] });
|
||||
|
||||
channelName = `#e2efav${Date.now().toString().slice(-6)}`;
|
||||
const channel = await createChannel(channelName);
|
||||
channelKey = channel.key;
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
try {
|
||||
await deleteChannel(channelKey);
|
||||
} catch {
|
||||
// Best-effort cleanup
|
||||
}
|
||||
try {
|
||||
await updateSettings({ favorites: originalFavorites });
|
||||
} catch {
|
||||
// Best-effort cleanup
|
||||
}
|
||||
});
|
||||
|
||||
test('add and remove favorite channel with persistence across reload', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
|
||||
await page.getByText(channelName, { exact: true }).first().click();
|
||||
|
||||
const addFavoriteButton = page.getByTitle('Add to favorites');
|
||||
await expect(addFavoriteButton).toBeVisible();
|
||||
await addFavoriteButton.click();
|
||||
|
||||
await expect(page.getByTitle('Remove from favorites')).toBeVisible();
|
||||
await expect(page.getByText('Favorites')).toBeVisible();
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const settings = await getSettings();
|
||||
return settings.favorites.some((f) => f.type === 'channel' && f.id === channelKey);
|
||||
})
|
||||
.toBe(true);
|
||||
|
||||
await page.reload();
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
await page.getByText(channelName, { exact: true }).first().click();
|
||||
await expect(page.getByTitle('Remove from favorites')).toBeVisible();
|
||||
await expect(page.getByText('Favorites')).toBeVisible();
|
||||
|
||||
await page.getByTitle('Remove from favorites').click();
|
||||
await expect(page.getByTitle('Add to favorites')).toBeVisible();
|
||||
await expect
|
||||
.poll(async () => {
|
||||
const settings = await getSettings();
|
||||
return settings.favorites.some((f) => f.type === 'channel' && f.id === channelKey);
|
||||
})
|
||||
.toBe(false);
|
||||
await expect(page.getByText('Favorites')).not.toBeVisible();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,95 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { createChannel, createContact, deleteChannel, deleteContact } from '../helpers/api';
|
||||
|
||||
function randomHex(bytes: number): string {
|
||||
return randomBytes(bytes).toString('hex');
|
||||
}
|
||||
|
||||
function makeKeyWithPrefix(prefix: string): string {
|
||||
return `${prefix}${randomHex(26)}`;
|
||||
}
|
||||
|
||||
function escapeRegex(value: string): string {
|
||||
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
}
|
||||
|
||||
test.describe('Hash routing and conversation identity', () => {
|
||||
let channelName = '';
|
||||
let channelKey = '';
|
||||
let contactAKey = '';
|
||||
let contactAName = '';
|
||||
let contactBKey = '';
|
||||
let contactBName = '';
|
||||
|
||||
test.beforeAll(async () => {
|
||||
channelName = `#e2ehash${Date.now().toString().slice(-6)}`;
|
||||
const createdChannel = await createChannel(channelName);
|
||||
channelKey = createdChannel.key;
|
||||
|
||||
const sharedPrefix = randomHex(6);
|
||||
contactAKey = makeKeyWithPrefix(sharedPrefix);
|
||||
contactBKey = makeKeyWithPrefix(sharedPrefix);
|
||||
contactAName = `E2E Hash A ${Date.now().toString().slice(-5)}`;
|
||||
contactBName = `E2E Hash B ${Date.now().toString().slice(-5)}`;
|
||||
|
||||
await createContact(contactAKey, contactAName);
|
||||
await createContact(contactBKey, contactBName);
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
try {
|
||||
await deleteChannel(channelKey);
|
||||
} catch {
|
||||
// Best-effort cleanup
|
||||
}
|
||||
try {
|
||||
await deleteContact(contactAKey);
|
||||
} catch {
|
||||
// Best-effort cleanup
|
||||
}
|
||||
try {
|
||||
await deleteContact(contactBKey);
|
||||
} catch {
|
||||
// Best-effort cleanup
|
||||
}
|
||||
});
|
||||
|
||||
test('legacy channel-name hash resolves and rewrites to stable channel-key hash', async ({
|
||||
page,
|
||||
}) => {
|
||||
const legacyToken = channelName.slice(1); // no leading '#'
|
||||
await page.goto(`/#channel/${encodeURIComponent(legacyToken)}`);
|
||||
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
await expect(
|
||||
page.getByPlaceholder(new RegExp(`message\\s+${escapeRegex(channelName)}`, 'i'))
|
||||
).toBeVisible();
|
||||
|
||||
await expect.poll(() => page.url()).toContain(`#channel/${encodeURIComponent(channelKey)}/`);
|
||||
});
|
||||
|
||||
test('full-key contact hash selects the exact contact even with shared prefixes', async ({ page }) => {
|
||||
await page.goto(`/#contact/${contactBKey}`);
|
||||
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
await expect(
|
||||
page.getByPlaceholder(new RegExp(`message\\s+${escapeRegex(contactBName)}`, 'i'))
|
||||
).toBeVisible();
|
||||
await expect(page.getByText(contactBKey, { exact: true })).toBeVisible();
|
||||
|
||||
await expect.poll(() => page.url()).toContain(`#contact/${encodeURIComponent(contactBKey)}/`);
|
||||
});
|
||||
|
||||
test('legacy contact-name hash resolves and rewrites to stable full-key hash', async ({ page }) => {
|
||||
await page.goto(`/#contact/${encodeURIComponent(contactAName)}`);
|
||||
|
||||
await expect(page.getByText('Connected')).toBeVisible();
|
||||
await expect(
|
||||
page.getByPlaceholder(new RegExp(`message\\s+${escapeRegex(contactAName)}`, 'i'))
|
||||
).toBeVisible();
|
||||
await expect(page.getByText(contactAKey, { exact: true })).toBeVisible();
|
||||
|
||||
await expect.poll(() => page.url()).toContain(`#contact/${encodeURIComponent(contactAKey)}/`);
|
||||
});
|
||||
});
|
||||
@@ -158,8 +158,8 @@ test.describe('Incoming mesh messages', () => {
|
||||
await expect(modal).toBeVisible();
|
||||
|
||||
// Verify the modal has the basic structural elements every path modal should have
|
||||
await expect(modal.getByText('Sender')).toBeVisible();
|
||||
await expect(modal.getByText('Receiver (me)')).toBeVisible();
|
||||
await expect(modal.getByText('Sender:').first()).toBeVisible();
|
||||
await expect(modal.getByText('Receiver (me):').first()).toBeVisible();
|
||||
|
||||
// Title should be either "Message Path" (single) or "Message Paths (N)" (multiple)
|
||||
const titleEl = modal.locator('h2, [class*="DialogTitle"]').first();
|
||||
|
||||
Reference in New Issue
Block a user