Sternboard: free retro board, no signup (initial)
Servidor Fastify + Socket.IO + Drizzle/Postgres; front Vite+React (EN/ES, tema claro/oscuro). Tableros por columnas con plantillas, notas privadas/públicas, agrupación, votos configurables (multivoto), timer compartido con alarma, fases, action items, export Markdown/JSON e import, autodestrucción, gestión de columnas. Imagen única (front servido por el servidor) y CI que la construye y publica.
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from "vitest";
|
||||
import { startTestServer, createBoard, join, type TestServer } from "./helpers.js";
|
||||
|
||||
let server: TestServer;
|
||||
beforeAll(async () => {
|
||||
server = await startTestServer();
|
||||
});
|
||||
afterAll(async () => {
|
||||
await server.close();
|
||||
});
|
||||
|
||||
describe("Moderación y opciones", () => {
|
||||
it("por defecto cualquiera modera; al reclamar solo el reclamante", async () => {
|
||||
const board = await createBoard(server.url);
|
||||
const alice = await join(server.url, board.boardId, "alice", "Alice");
|
||||
const bob = await join(server.url, board.boardId, "bob", "Bob");
|
||||
|
||||
// modo abierto por defecto
|
||||
expect(alice.last!.moderationMode).toBe("everyone");
|
||||
|
||||
// Alice reclama (sin token, permitido en modo abierto) y recibe un token nuevo
|
||||
const claimed = new Promise<string>((resolve) =>
|
||||
alice.socket.once("board:claimed", (p: { moderatorToken: string }) => resolve(p.moderatorToken)),
|
||||
);
|
||||
alice.emit("board:claim", {});
|
||||
const token = await claimed;
|
||||
await alice.waitFor((s) => s.moderationMode === "single");
|
||||
|
||||
// Bob (sin token) ya no puede revelar; Alice con su token sí
|
||||
bob.emit("board:reveal", { revealed: true });
|
||||
await new Promise((r) => setTimeout(r, 150));
|
||||
expect(bob.last!.revealed).toBe(false);
|
||||
|
||||
alice.emit("board:reveal", { revealed: true, moderatorToken: token });
|
||||
const revealed = await alice.waitFor((s) => s.revealed === true);
|
||||
expect(revealed.revealed).toBe(true);
|
||||
|
||||
alice.close();
|
||||
bob.close();
|
||||
});
|
||||
|
||||
it("bloquear deja el board en solo lectura (no se añaden notas)", async () => {
|
||||
const board = await createBoard(server.url);
|
||||
const alice = await join(server.url, board.boardId, "alice", "Alice");
|
||||
const colId = alice.last!.columns[0].id;
|
||||
|
||||
alice.emit("board:update", { locked: true }); // modo abierto: cualquiera modera
|
||||
await alice.waitFor((s) => s.locked === true);
|
||||
|
||||
alice.emit("note:add", { columnId: colId, text: "no debería entrar" });
|
||||
await new Promise((r) => setTimeout(r, 200));
|
||||
expect(alice.last!.notes).toHaveLength(0);
|
||||
|
||||
// desbloquear y ahora sí
|
||||
alice.emit("board:update", { locked: false });
|
||||
await alice.waitFor((s) => s.locked === false);
|
||||
alice.emit("note:add", { columnId: colId, text: "ahora sí" });
|
||||
const after = await alice.waitFor((s) => s.notes.length === 1);
|
||||
expect(after.notes[0].text).toBe("ahora sí");
|
||||
|
||||
alice.close();
|
||||
});
|
||||
|
||||
it("votes:clear borra todos los votos", async () => {
|
||||
const board = await createBoard(server.url);
|
||||
const alice = await join(server.url, board.boardId, "alice", "Alice");
|
||||
const colId = alice.last!.columns[0].id;
|
||||
alice.emit("note:add", { columnId: colId, text: "x" });
|
||||
const noteId = (await alice.waitFor((s) => s.notes.length === 1)).notes[0].id;
|
||||
alice.emit("vote:add", { targetType: "note", targetId: noteId });
|
||||
await alice.waitFor((s) => s.votes.length === 1);
|
||||
|
||||
alice.emit("votes:clear", {});
|
||||
const cleared = await alice.waitFor((s) => s.votes.length === 0);
|
||||
expect(cleared.votes).toHaveLength(0);
|
||||
|
||||
alice.close();
|
||||
});
|
||||
|
||||
it("participant:rename actualiza la autoría de mis notas", async () => {
|
||||
const board = await createBoard(server.url);
|
||||
const alice = await join(server.url, board.boardId, "alice", "Alice");
|
||||
const colId = alice.last!.columns[0].id;
|
||||
alice.emit("note:add", { columnId: colId, text: "mía" });
|
||||
await alice.waitFor((s) => s.notes.length === 1 && s.notes[0].authorName === "Alice");
|
||||
|
||||
alice.emit("participant:rename", { name: "Alicia" });
|
||||
const renamed = await alice.waitFor((s) => s.notes[0]?.authorName === "Alicia");
|
||||
expect(renamed.notes[0].authorName).toBe("Alicia");
|
||||
expect(renamed.participants.find((p) => p.id === "alice")?.name).toBe("Alicia");
|
||||
|
||||
alice.close();
|
||||
});
|
||||
|
||||
it("la presencia lista a los participantes conectados", async () => {
|
||||
const board = await createBoard(server.url);
|
||||
const alice = await join(server.url, board.boardId, "alice", "Alice");
|
||||
const bob = await join(server.url, board.boardId, "bob", "Bob");
|
||||
const st = await alice.waitFor((s) => s.participants.length === 2);
|
||||
expect(st.participants.map((p) => p.name).sort()).toEqual(["Alice", "Bob"]);
|
||||
alice.close();
|
||||
bob.close();
|
||||
});
|
||||
|
||||
it("action item: marcar hecho", async () => {
|
||||
const board = await createBoard(server.url);
|
||||
const alice = await join(server.url, board.boardId, "alice", "Alice");
|
||||
alice.emit("actionitem:add", { text: "documentar" });
|
||||
const withItem = await alice.waitFor((s) => s.actionItems.length === 1);
|
||||
const id = withItem.actionItems[0].id;
|
||||
expect(withItem.actionItems[0].done).toBe(false);
|
||||
|
||||
alice.emit("actionitem:toggle", { id, done: true });
|
||||
const done = await alice.waitFor((s) => s.actionItems[0]?.done === true);
|
||||
expect(done.actionItems[0].done).toBe(true);
|
||||
|
||||
alice.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user