2026-10-25 01:08:28 -04:00
|
|
|
import fs from 'node:fs';
|
|
|
|
|
import { test, expect } from '@playwright/test';
|
|
|
|
|
import { api, ensureUser, hsReachable, loginUI, uniq } from './localHs';
|
|
|
|
|
|
|
|
|
|
// [Gitea #114] Export / import "your data": a round trip through the file, the
|
|
|
|
|
// merge summary, and the keep-mine / use-the-file's choice.
|
|
|
|
|
const enc = encodeURIComponent;
|
|
|
|
|
|
|
|
|
|
test.describe('your data (#114)', () => {
|
|
|
|
|
test.beforeAll(async () => {
|
|
|
|
|
test.skip(!(await hsReachable()), 'local homeserver not running');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('export, change things, import merges with the chosen conflict rule', async ({ page }) => {
|
|
|
|
|
test.setTimeout(150_000);
|
|
|
|
|
const user = await ensureUser(uniq('yd'));
|
|
|
|
|
const put = (type: string, content: unknown) =>
|
|
|
|
|
api(
|
|
|
|
|
'PUT',
|
|
|
|
|
`/_matrix/client/v3/user/${enc(user.userId)}/account_data/${type}`,
|
|
|
|
|
user.token,
|
|
|
|
|
content,
|
|
|
|
|
);
|
|
|
|
|
const get = (type: string) =>
|
|
|
|
|
api<Record<string, unknown>>(
|
|
|
|
|
'GET',
|
|
|
|
|
`/_matrix/client/v3/user/${enc(user.userId)}/account_data/${type}`,
|
|
|
|
|
user.token,
|
|
|
|
|
);
|
|
|
|
|
const bm = (eventId: string) => ({ roomId: '!r:localhost', eventId, savedAt: 1 });
|
|
|
|
|
await put('io.lotus.bookmarks', { bookmarks: [bm('$one'), bm('$two')] });
|
|
|
|
|
await put('io.lotus.user_notes', { '@friend:localhost': 'met at the LAN party' });
|
|
|
|
|
|
|
|
|
|
await loginUI(page, user);
|
|
|
|
|
await page
|
|
|
|
|
.getByText('Connecting...')
|
|
|
|
|
.first()
|
|
|
|
|
.waitFor({ state: 'detached', timeout: 90_000 })
|
|
|
|
|
.catch(() => undefined);
|
|
|
|
|
const openGeneral = async () => {
|
|
|
|
|
await page.getByRole('button', { name: 'User Settings' }).click();
|
2026-10-25 02:07:23 -04:00
|
|
|
await page.getByRole('dialog').getByText('Data & storage', { exact: true }).first().click();
|
2026-10-25 01:08:28 -04:00
|
|
|
await page
|
|
|
|
|
.getByRole('dialog')
|
|
|
|
|
.getByText('Your data', { exact: true })
|
|
|
|
|
.scrollIntoViewIfNeeded();
|
|
|
|
|
};
|
|
|
|
|
await openGeneral();
|
|
|
|
|
|
|
|
|
|
// Export.
|
|
|
|
|
const [download] = await Promise.all([
|
|
|
|
|
page.waitForEvent('download'),
|
|
|
|
|
page.getByRole('dialog').getByRole('button', { name: 'Export', exact: true }).click(),
|
|
|
|
|
]);
|
|
|
|
|
const path = await download.path();
|
|
|
|
|
const file = JSON.parse(fs.readFileSync(path!, 'utf8'));
|
|
|
|
|
expect(file.format).toBe('lotus-chat-data');
|
|
|
|
|
expect(file.accountData['io.lotus.bookmarks'].bookmarks).toHaveLength(2);
|
|
|
|
|
expect(file.local.settings).toBeTruthy();
|
|
|
|
|
|
|
|
|
|
// Lose a bookmark, change the note.
|
|
|
|
|
await put('io.lotus.bookmarks', { bookmarks: [bm('$one')] });
|
|
|
|
|
await put('io.lotus.user_notes', { '@friend:localhost': 'changed my mind' });
|
|
|
|
|
await page.waitForTimeout(3000); // let sync deliver the new account data
|
|
|
|
|
|
|
|
|
|
const importFile = async () => {
|
|
|
|
|
await page
|
|
|
|
|
.locator('input[type="file"][aria-label="Lotus Chat data file"]')
|
|
|
|
|
.setInputFiles(path!);
|
|
|
|
|
await expect(page.getByRole('region', { name: 'Import summary' })).toBeVisible();
|
|
|
|
|
};
|
|
|
|
|
await importFile();
|
|
|
|
|
const summary = page.getByRole('region', { name: 'Import summary' });
|
|
|
|
|
await expect(summary).toContainText('Bookmarks: 1 new');
|
|
|
|
|
await expect(summary).toContainText('Private notes about people: 1 differ');
|
|
|
|
|
|
|
|
|
|
// Keep mine: the bookmark comes back, the note stays as it is now.
|
|
|
|
|
await summary.getByRole('button', { name: 'Import', exact: true }).click();
|
|
|
|
|
await expect(page.getByText('Imported.')).toBeVisible({ timeout: 20_000 });
|
|
|
|
|
expect(((await get('io.lotus.bookmarks')).bookmarks as unknown[]).length).toBe(2);
|
|
|
|
|
expect((await get('io.lotus.user_notes'))['@friend:localhost']).toBe('changed my mind');
|
|
|
|
|
|
|
|
|
|
// Again, using the file's: the note goes back.
|
|
|
|
|
await page.waitForTimeout(2000);
|
|
|
|
|
await importFile();
|
|
|
|
|
await summary.getByText("Use the file's").click();
|
|
|
|
|
await summary.getByRole('button', { name: 'Import', exact: true }).click();
|
|
|
|
|
await expect(page.getByText('Imported.')).toBeVisible({ timeout: 20_000 });
|
|
|
|
|
expect((await get('io.lotus.user_notes'))['@friend:localhost']).toBe('met at the LAN party');
|
|
|
|
|
|
|
|
|
|
// Wrong files are refused with a plain message.
|
|
|
|
|
fs.writeFileSync(`${path}.bad`, '{"format":"something-else"}');
|
|
|
|
|
await page
|
|
|
|
|
.locator('input[type="file"][aria-label="Lotus Chat data file"]')
|
|
|
|
|
.setInputFiles(`${path}.bad`);
|
|
|
|
|
await expect(page.getByText("This isn't a Lotus Chat data file.")).toBeVisible();
|
|
|
|
|
});
|
|
|
|
|
});
|