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('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(); }); });