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
166 lines
6.4 KiB
TypeScript
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();
|
|
});
|
|
});
|