#459 General held 76 of ~110 settings (11 screens). Its sections now live on pages named for what people come to do: Account, Appearance, Messages (incl. editor, date and time), Notifications, Calls & audio, Privacy & security (privacy options + the former Devices page), Data & storage (storage, sync, your data), Desktop (desktop app only), Emojis & stickers, Advanced (the former Developer Tools + "show hidden events" / "legacy username colour"), About. The longest page is now ~3 screens (Appearance). Settings opens on Account. Deep links keep working: "devices" lands on Privacy & security, the old "general" on Appearance, the update notice on Desktop. Text that pointed at the old page names is updated. The push-rule editor gets one line explaining the Matrix terms. Remaining Title Case labels on Account and Devices are sentence case. #460 Chat background, seasonal theme and avatar decoration grids sit behind one row: the current choice and a Change button (the first two close after a choice; decorations keep their Save step). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017PCb59uY3UKqDqt7B6hzd5
50 lines
2.0 KiB
TypeScript
50 lines
2.0 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { ensureUser, hsReachable, loginUI, uniq } from './localHs';
|
|
|
|
// [Gitea #201 KE-1] When the server keeps rejecting this device's key uploads
|
|
// with "One time key … already exists", the app says so and offers the fix.
|
|
const CONFLICT = {
|
|
errcode: 'M_UNKNOWN',
|
|
error: 'One time key signed_curve25519:AAAAAAAAAA0 already exists. Old key: {}; new key: {}',
|
|
};
|
|
|
|
test.describe('key upload recovery (#201)', () => {
|
|
test.beforeAll(async () => {
|
|
test.skip(!(await hsReachable()), 'local homeserver not running');
|
|
});
|
|
|
|
test('a diverged device sees the banner, and Fix opens the logout dialog', async ({ page }) => {
|
|
test.setTimeout(240_000);
|
|
const user = await ensureUser(uniq('kup'));
|
|
let rejected = 0;
|
|
await page.route(/\/_matrix\/client\/v3\/keys\/upload/, async (route) => {
|
|
rejected += 1;
|
|
await route.fulfill({ status: 400, json: CONFLICT });
|
|
});
|
|
await loginUI(page, user);
|
|
|
|
const banner = page
|
|
.getByRole('alert')
|
|
.filter({ hasText: /Encryption on this device is out of sync/ });
|
|
await expect(banner).toBeVisible({ timeout: 200_000 });
|
|
console.log(`key uploads rejected: ${rejected}`);
|
|
expect(rejected).toBeGreaterThanOrEqual(3);
|
|
|
|
await banner.getByRole('button', { name: 'Details' }).click();
|
|
await expect(banner).toContainText('Settings → Privacy & security');
|
|
await expect(banner).toContainText('already exists');
|
|
|
|
await banner.getByRole('button', { name: /Fix: sign out/ }).click();
|
|
await expect(page.getByRole('dialog').getByText('Logout').first()).toBeVisible();
|
|
});
|
|
|
|
test('a healthy device never sees it', async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
const user = await ensureUser(uniq('kok'));
|
|
await loginUI(page, user);
|
|
await page.getByText('Connecting...').first().waitFor({ state: 'detached', timeout: 90_000 });
|
|
await page.waitForTimeout(5000);
|
|
await expect(page.getByText(/Encryption on this device is out of sync/)).toHaveCount(0);
|
|
});
|
|
});
|