Files
Luncher/server/src/tests/suggestions.test.ts
T
mates cc09ddbd2c
CI / Generate TypeScript types (push) Successful in 10s
CI / Server unit tests (push) Successful in 21s
CI / Build server (push) Successful in 24s
CI / Build client (push) Successful in 36s
CI / Playwright E2E tests (push) Successful in 1m18s
CI / Build and push Docker image (push) Successful in 41s
CI / Notify (push) Successful in 2s
feat: možnost označení návrhu jako vyřešeného (resolved)
2026-06-10 18:38:55 +02:00

166 lines
6.4 KiB
TypeScript

import { resetMemoryStorage } from '../storage/memory';
import getStorage from '../storage';
import { addSuggestion, deleteSuggestion, listSuggestions, voteSuggestion } from '../suggestions';
/** Ručně označí návrh jako vyřešený - simuluje zásah do dat (Redis/JSON). */
async function markResolved(id: string) {
const storage = getStorage();
const data = await storage.getData<any[]>('suggestions');
data!.find(s => s.id === id).resolved = true;
await storage.setData('suggestions', data);
}
const AUTHOR = 'tomas';
const VOTER = 'petr';
const OTHER = 'jana';
beforeEach(() => {
resetMemoryStorage();
});
/** Pomocná funkce: vytvoří návrh a vrátí jeho id (z pohledu autora). */
async function createSuggestion(author = AUTHOR, title = 'Tmavý režim', description = 'Přidat tmavý režim aplikace') {
const list = await addSuggestion(author, title, description);
return list.find(s => s.title === title)!.id;
}
describe('addSuggestion', () => {
test('přidá návrh a autorovi nastaví hlas pro', async () => {
const list = await addSuggestion(AUTHOR, 'Tmavý režim', 'Popis');
expect(list).toHaveLength(1);
const s = list[0];
expect(s.author).toBe(AUTHOR);
expect(s.title).toBe('Tmavý režim');
expect(s.description).toBe('Popis');
expect(s.voteScore).toBe(1);
expect(s.myVote).toBe('up');
expect(s.isMine).toBe(true);
});
test('ořízne mezery a odmítne prázdný název i popis', async () => {
await expect(addSuggestion(AUTHOR, ' ', 'popis')).rejects.toThrow();
await expect(addSuggestion(AUTHOR, 'název', ' ')).rejects.toThrow();
const list = await addSuggestion(AUTHOR, ' Název ', ' Popis ');
expect(list[0].title).toBe('Název');
expect(list[0].description).toBe('Popis');
});
test('jeden uživatel může přidat více návrhů', async () => {
await addSuggestion(AUTHOR, 'První', 'popis');
const list = await addSuggestion(AUTHOR, 'Druhý', 'popis');
expect(list).toHaveLength(2);
});
});
describe('listSuggestions', () => {
test('řadí sestupně dle skóre', async () => {
const lowId = await createSuggestion(AUTHOR, 'Nízké', 'popis');
const highId = await createSuggestion(OTHER, 'Vysoké', 'popis');
// "Vysoké" dostane další hlas pro
await voteSuggestion(VOTER, highId, 'up');
const list = await listSuggestions(VOTER);
expect(list[0].id).toBe(highId);
expect(list[0].voteScore).toBe(2);
expect(list[1].id).toBe(lowId);
});
test('myVote a isMine jsou relativní k uživateli', async () => {
const id = await createSuggestion(AUTHOR);
const asAuthor = (await listSuggestions(AUTHOR))[0];
expect(asAuthor.isMine).toBe(true);
expect(asAuthor.myVote).toBe('up');
const asOther = (await listSuggestions(VOTER))[0];
expect(asOther.isMine).toBe(false);
expect(asOther.myVote).toBeUndefined();
// Seznamy hlasujících se klientovi neposílají
expect((asOther as any).upvoters).toBeUndefined();
expect(id).toBeDefined();
});
});
describe('voteSuggestion', () => {
test('hlas pro a proti od jiného uživatele', async () => {
const id = await createSuggestion();
let list = await voteSuggestion(VOTER, id, 'up');
expect(list[0].voteScore).toBe(2);
expect(list.find(s => s.id === id)!.voteScore).toBe(2);
// přehlasování z pro na proti
list = await voteSuggestion(VOTER, id, 'down');
// autor +1, voter -1 => 0
expect(list[0].voteScore).toBe(0);
expect((await listSuggestions(VOTER))[0].myVote).toBe('down');
});
test('klik na aktivní směr hlas zruší', async () => {
const id = await createSuggestion();
await voteSuggestion(VOTER, id, 'up');
const list = await voteSuggestion(VOTER, id, 'up');
// zůstává jen autorův hlas
expect(list[0].voteScore).toBe(1);
expect((await listSuggestions(VOTER))[0].myVote).toBeUndefined();
});
test('autor může svůj automatický hlas odebrat', async () => {
const id = await createSuggestion();
const list = await voteSuggestion(AUTHOR, id, 'up');
expect(list[0].voteScore).toBe(0);
expect((await listSuggestions(AUTHOR))[0].myVote).toBeUndefined();
});
test('hlasování pro neexistující návrh vyhodí chybu', async () => {
await expect(voteSuggestion(VOTER, 'neexistuje', 'up')).rejects.toThrow();
});
});
describe('vyřešené návrhy', () => {
test('listSuggestions vrací příznak resolved', async () => {
const id = await createSuggestion();
expect((await listSuggestions(AUTHOR))[0].resolved).toBe(false);
await markResolved(id);
expect((await listSuggestions(AUTHOR))[0].resolved).toBe(true);
});
test('pro vyřešený návrh nelze hlasovat', async () => {
const id = await createSuggestion();
await markResolved(id);
await expect(voteSuggestion(VOTER, id, 'up')).rejects.toThrow();
// skóre zůstává nezměněné (jen autorův hlas)
expect((await listSuggestions(VOTER))[0].voteScore).toBe(1);
});
test('autor může vyřešený návrh stále smazat', async () => {
const id = await createSuggestion();
await markResolved(id);
const list = await deleteSuggestion(AUTHOR, id);
expect(list).toHaveLength(0);
});
test('cizí uživatel nemůže smazat ani vyřešený návrh', async () => {
const id = await createSuggestion();
await markResolved(id);
await expect(deleteSuggestion(VOTER, id)).rejects.toThrow();
});
});
describe('deleteSuggestion', () => {
test('autor smaže svůj návrh včetně hlasů', async () => {
const id = await createSuggestion();
await voteSuggestion(VOTER, id, 'up');
const list = await deleteSuggestion(AUTHOR, id);
expect(list).toHaveLength(0);
});
test('cizí uživatel nemůže smazat návrh', async () => {
const id = await createSuggestion();
await expect(deleteSuggestion(VOTER, id)).rejects.toThrow();
expect(await listSuggestions(AUTHOR)).toHaveLength(1);
});
test('smazání neexistujícího návrhu vyhodí chybu', async () => {
await expect(deleteSuggestion(AUTHOR, 'neexistuje')).rejects.toThrow();
});
});