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,73 @@
|
||||
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("Revelado granular", () => {
|
||||
it("el autor revela solo su propia nota", 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 colId = alice.last!.columns[0].id;
|
||||
|
||||
alice.emit("note:add", { columnId: colId, text: "mía" });
|
||||
let bobState = await bob.waitFor((s) => s.notes.length === 1);
|
||||
expect(bobState.notes[0].text).toBe(""); // oculta
|
||||
|
||||
const noteId = (await alice.waitFor((s) => s.notes.length === 1)).notes[0].id;
|
||||
alice.emit("note:reveal", { noteId, revealed: true });
|
||||
|
||||
bobState = await bob.waitFor((s) => s.notes[0]?.text === "mía");
|
||||
expect(bobState.notes[0].text).toBe("mía");
|
||||
|
||||
alice.close();
|
||||
bob.close();
|
||||
});
|
||||
|
||||
it("los totales de votos se ocultan hasta que el moderador los revela", 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 colId = alice.last!.columns[0].id;
|
||||
|
||||
alice.emit("note:add", { columnId: colId, text: "vota esto" });
|
||||
const noteId = (await alice.waitFor((s) => s.notes.length === 1)).notes[0].id;
|
||||
await bob.waitFor((s) => s.notes.length === 1);
|
||||
|
||||
// Bob vota; Alice NO debe ver el voto de Bob (totales ocultos)
|
||||
bob.emit("vote:toggle", { targetType: "note", targetId: noteId });
|
||||
await bob.waitFor((s) => s.votes.some((v) => v.targetId === noteId)); // Bob ve el suyo
|
||||
|
||||
const aliceView = alice.last!;
|
||||
expect(aliceView.votes.filter((v) => v.targetId === noteId)).toHaveLength(0);
|
||||
|
||||
// El moderador revela los votos => Alice ve el de Bob
|
||||
alice.emit("board:revealVotes", { revealed: true, moderatorToken: board.moderatorToken });
|
||||
const revealed = await alice.waitFor((s) => s.votesRevealed && s.votes.length === 1);
|
||||
expect(revealed.votes[0].participantId).toBe("bob");
|
||||
|
||||
alice.close();
|
||||
bob.close();
|
||||
});
|
||||
|
||||
it("cambiar los ajustes del board requiere token de moderador", async () => {
|
||||
const board = await createBoard(server.url, { votesPerUser: 3 });
|
||||
const alice = await join(server.url, board.boardId, "alice", "Alice");
|
||||
|
||||
// sin token: ignorado
|
||||
alice.emit("board:update", { title: "Hackeado", votesPerUser: 99 });
|
||||
// con token: aplica
|
||||
alice.emit("board:update", { title: "Renombrado", votesPerUser: 7, moderatorToken: board.moderatorToken });
|
||||
|
||||
const updated = await alice.waitFor((s) => s.title === "Renombrado");
|
||||
expect(updated.votesPerUser).toBe(7);
|
||||
|
||||
alice.close();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user