feat(workspace): allow sharing notes and shared folders as editor
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m13s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m13s
The share dialog for notes and shared folders now offers "Bearbeiten" and "Nur Lesen" and stores write/read accordingly. Editors already edit through the sync engine since phase 2. Adds e2e/share.e2e.mjs: shares a note as editor through the dialog and checks that the recipient can edit it; e2e/run.sh runs both browser tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
51f90eb7e4
commit
08d9b2dc77
7 files changed
+122
-13
No files matched your search
@@ -0,0 +1,108 @@
|
||||
// Shares a note as editor through the share dialog and checks that the editor can then edit it.
|
||||
// Needs a server in demo mode (accounts admin/admin and user/user); see e2e/README.md.
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
const BASE = process.env.MINDSIFT_URL || 'http://localhost:18080';
|
||||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
||||
let failures = 0;
|
||||
const check = (cond, msg) => {
|
||||
console.log(`${cond ? 'OK ' : 'FAIL'} ${msg}`);
|
||||
if (!cond) failures++;
|
||||
};
|
||||
|
||||
async function api(token, method, path, body) {
|
||||
const res = await fetch(BASE + path, {
|
||||
method,
|
||||
headers: { 'Content-Type': 'application/json', ...(token ? { Authorization: `Bearer ${token}` } : {}) },
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
});
|
||||
const text = await res.text();
|
||||
if (!res.ok) throw new Error(`${method} ${path}: ${res.status} ${text}`);
|
||||
return text ? JSON.parse(text) : null;
|
||||
}
|
||||
|
||||
async function waitFor(fn, ms) {
|
||||
const end = Date.now() + ms;
|
||||
while (Date.now() < end) {
|
||||
try {
|
||||
const v = await fn();
|
||||
if (v) return v;
|
||||
} catch {}
|
||||
await sleep(250);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async function openDoc(browser, token, title) {
|
||||
const ctx = await browser.newContext({ viewport: { width: 1280, height: 900 } });
|
||||
await ctx.addInitScript((t) => localStorage.setItem('mindsift_jwt_token', t), token);
|
||||
const page = await ctx.newPage();
|
||||
await page.goto(BASE);
|
||||
await page.getByRole('button', { name: /Workspace/ }).first().click();
|
||||
await page.getByText(title, { exact: true }).first().click();
|
||||
await page.waitForFunction(() => document.querySelectorAll('textarea').length > 1, null, { timeout: 10000 });
|
||||
return { ctx, page };
|
||||
}
|
||||
|
||||
const TITLE = `Freigabe-Test ${Date.now()}`;
|
||||
|
||||
const main = async () => {
|
||||
const admin = (await api(null, 'POST', '/api/v1/auth/login', { login: 'admin', password: 'admin' })).token;
|
||||
const user = (await api(null, 'POST', '/api/v1/auth/login', { login: 'user', password: 'user' })).token;
|
||||
const doc = await api(admin, 'POST', '/api/v1/documents', {
|
||||
title: TITLE,
|
||||
blocks: [{ id: `fr_${Date.now()}`, type: 'bullet', content: 'Vom Eigentümer', level: 0 }],
|
||||
});
|
||||
|
||||
const browser = await chromium.launch();
|
||||
const A = await openDoc(browser, admin, TITLE);
|
||||
|
||||
// Share as editor through the dialog
|
||||
await A.page.getByTitle('Notiz teilen und Berechtigungen').click();
|
||||
await A.page.getByRole('button', { name: 'Benutzer', exact: true }).click();
|
||||
const search = A.page.getByPlaceholder(/Benutzer suchen/);
|
||||
await search.click();
|
||||
await search.type('user');
|
||||
await sleep(300);
|
||||
await search.press('ArrowDown');
|
||||
await search.press('Enter');
|
||||
const roleSelect = A.page.locator('select').filter({ has: A.page.locator('option[value="editor"]') }).last();
|
||||
check((await roleSelect.isEnabled()) && (await roleSelect.isVisible()), 'Rollenauswahl ist im Dialog verfügbar');
|
||||
await roleSelect.selectOption('editor');
|
||||
await A.page.getByRole('button', { name: /Benutzer hinzufügen/ }).click();
|
||||
const shares = await waitFor(async () => {
|
||||
const list = await api(admin, 'GET', `/api/v1/documents/${doc.id}/shares`);
|
||||
return list.length > 0 ? list : null;
|
||||
}, 5000);
|
||||
check(shares && shares[0].permission === 'write', `Freigabe gespeichert als ${shares?.[0]?.permission}`);
|
||||
const badge = await A.page
|
||||
.getByText('Bearbeiter', { exact: true })
|
||||
.first()
|
||||
.waitFor({ state: 'visible', timeout: 5000 })
|
||||
.then(() => true)
|
||||
.catch(() => false);
|
||||
check(badge, 'Dialog zeigt die Rolle „Bearbeiter“');
|
||||
|
||||
// The editor can edit
|
||||
const snap = await api(user, 'GET', `/api/v1/docs/${doc.id}/snapshot`);
|
||||
check(snap.role === 'editor', `Empfänger hat die Rolle ${snap.role}`);
|
||||
const B = await openDoc(browser, user, TITLE);
|
||||
check(!(await B.page.getByText('schreibgeschützt mit dir geteilt', { exact: false }).first().isVisible().catch(() => false)), 'Kein Nur-Lesen-Hinweis beim Bearbeiter');
|
||||
await B.page.getByText('Vom Eigentümer', { exact: true }).first().click();
|
||||
await B.page.keyboard.press('End');
|
||||
await B.page.keyboard.type(' und vom Bearbeiter');
|
||||
const saved = await waitFor(async () => {
|
||||
const s = await api(admin, 'GET', `/api/v1/docs/${doc.id}/snapshot`);
|
||||
return s.blocks.some((b) => b.content === 'Vom Eigentümer und vom Bearbeiter');
|
||||
}, 6000);
|
||||
check(!!saved, 'Bearbeiter kann die geteilte Notiz bearbeiten');
|
||||
|
||||
await browser.close();
|
||||
console.log(failures ? `${failures} FEHLGESCHLAGEN` : 'ALLE PRÜFUNGEN BESTANDEN');
|
||||
process.exit(failures ? 1 : 0);
|
||||
};
|
||||
|
||||
main().catch((e) => {
|
||||
console.error(e);
|
||||
process.exit(2);
|
||||
});
|
||||
Reference in new issue
Block a user