Skip to content

Commit

Permalink
Merge pull request #3 from RonaldTreur/refactor/remove-redundant-code…
Browse files Browse the repository at this point in the history
…-and-streamline

refactor: remove redundancy and improve clarity
  • Loading branch information
Buzzec authored Dec 3, 2024
2 parents 259d5d3 + 31e6b2a commit 91b2489
Show file tree
Hide file tree
Showing 2 changed files with 104 additions and 167 deletions.
149 changes: 54 additions & 95 deletions packages/stv/src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ import {
CandidateMapItem,
distributeVotes,
eliminateLowestCandidate,
getCandidateMapFromVoteRecords,
getCandidatesAboveQuota,
initializeCandidateSet,
organizeVotesByNextCandidate,
redistributeExcessVotes,
redistributeToCandidates,
removeCandidateFromAllVotes,
Expand Down Expand Up @@ -284,48 +283,71 @@ describe('calculateQuota', () => {
});
});

describe('initializeCandidateSet', () => {
it('should initialize a candidate set with correct total votes', () => {
describe('getCandidateMapFromVoteRecords', () => {
it('should create a candidate map with correct total votes', () => {
const voteRecords: VoteRecord[] = [
{ voteCount: 3, voteOrder: ['A', 'B', 'C'] },
{ voteCount: 5, voteOrder: ['A', 'C', 'B'] },
{ voteCount: 2, voteOrder: ['B', 'A', 'C'] },
];
const candidateSet = initializeCandidateSet(voteRecords);
expect(candidateSet.get('A')?.totalVotes).toBe(8);
expect(candidateSet.get('B')?.totalVotes).toBe(2);
const candidateMap = getCandidateMapFromVoteRecords(voteRecords);
expect(candidateMap.size).toBe(2);
expect(candidateMap.get('A')?.totalVotes).toBe(8);
expect(candidateMap.get('B')?.totalVotes).toBe(2);
});

it('should handle empty vote records', () => {
it('should handle empty vote records gracefully', () => {
const voteRecords: VoteRecord[] = [];
const candidateSet = initializeCandidateSet(voteRecords);
expect(candidateSet.size).toBe(0);
const candidateMap = getCandidateMapFromVoteRecords(voteRecords);
expect(candidateMap.size).toBe(0);
});

it('should handle a single vote record', () => {
const voteRecords: VoteRecord[] = [{ voteCount: 1, voteOrder: ['A'] }];
const candidateSet = initializeCandidateSet(voteRecords);
expect(candidateSet.size).toBe(1);
expect(candidateSet.get('A')?.totalVotes).toBe(1);
const candidateMap = getCandidateMapFromVoteRecords(voteRecords);
expect(candidateMap.size).toBe(1);
expect(candidateMap.get('A')?.totalVotes).toBe(1);
});

it('should aggregate votes correctly when there are multiple records for the same candidate', () => {
const voteRecords: VoteRecord[] = [
{ voteCount: 3, voteOrder: ['A', 'B'] },
{ voteCount: 4, voteOrder: ['A', 'C'] },
];
const candidateSet = initializeCandidateSet(voteRecords);
expect(candidateSet.get('A')?.totalVotes).toBe(7);
const candidateMap = getCandidateMapFromVoteRecords(voteRecords);
expect(candidateMap.size).toBe(1);
expect(candidateMap.get('A')?.totalVotes).toBe(7);
});

it('should ignore records with empty vote orders', () => {
const voteRecords: VoteRecord[] = [
{ voteCount: 3, voteOrder: [] },
{ voteCount: 4, voteOrder: ['A', 'C'] },
];
const candidateSet = initializeCandidateSet(voteRecords);
expect(candidateSet.size).toBe(1);
expect(candidateSet.get('A')?.totalVotes).toBe(4);
const candidateMap = getCandidateMapFromVoteRecords(voteRecords);
expect(candidateMap.size).toBe(1);
expect(candidateMap.get('A')?.totalVotes).toBe(4);
});

it('should handle the case where votes have different next candidates', () => {
const votes: VoteRecord[] = [
{ voteCount: 10, voteOrder: ['B', 'C'] },
{ voteCount: 20, voteOrder: ['C', 'A'] },
];
const candidateMap = getCandidateMapFromVoteRecords(votes);
expect(candidateMap.size).toBe(2);
expect(candidateMap.get('B')?.totalVotes).toBe(10);
expect(candidateMap.get('C')?.totalVotes).toBe(20);
});

it('should handle votes with only one candidate', () => {
const votes: VoteRecord[] = [
{ voteCount: 10, voteOrder: ['B'] },
{ voteCount: 20, voteOrder: ['B'] },
];
const candidateMap = getCandidateMapFromVoteRecords(votes);
expect(candidateMap.size).toBe(1);
expect(candidateMap.get('B')?.totalVotes).toBe(30);
});
});

Expand Down Expand Up @@ -563,11 +585,11 @@ describe('distributeVotes', () => {
],
]);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
distributeVotes('A', candidateSet.get('A')!, candidateSet, 25, {
distributeVotes('A', candidateSet.get('A')!, candidateSet, 5, {
value: 60,
});
expect(candidateSet.has('A')).toBe(false);
expect(candidateSet.get('B')?.totalVotes).toBeGreaterThan(20); // B should receive additional votes
expect(candidateSet.get('B')?.totalVotes).toBe(25); // B should receive additional votes
});

it('should handle the case where no excess votes are present', () => {
Expand All @@ -588,7 +610,7 @@ describe('distributeVotes', () => {
],
]);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
distributeVotes('A', candidateSet.get('A')!, candidateSet, 25, {
distributeVotes('A', candidateSet.get('A')!, candidateSet, 0, {
value: 45,
});
expect(candidateSet.has('A')).toBe(false);
Expand All @@ -607,7 +629,7 @@ describe('distributeVotes', () => {
],
]);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
distributeVotes('A', candidateSet.get('A')!, candidateSet, 25, {
distributeVotes('A', candidateSet.get('A')!, candidateSet, 0, {
value: 20,
});
expect(candidateSet.has('A')).toBe(false);
Expand All @@ -619,7 +641,7 @@ describe('distributeVotes', () => {
['A', { totalVotes: 30, votes: [{ voteCount: 30, voteOrder: ['A'] }] }],
]);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
distributeVotes('A', candidateSet.get('A')!, candidateSet, 25, {
distributeVotes('A', candidateSet.get('A')!, candidateSet, 5, {
value: 30,
});
expect(candidateSet.has('A')).toBe(false);
Expand Down Expand Up @@ -704,11 +726,12 @@ describe('redistributeExcessVotes', () => {
['B', { totalVotes: 10, votes: [{ voteCount: 10, voteOrder: ['B'] }] }],
['C', { totalVotes: 0, votes: [] }],
]);

const candidateData = {
totalVotes: 30,
votes: [{ voteCount: 30, voteOrder: ['B', 'C'] }],
};
redistributeExcessVotes(candidateData, candidateSet, 25, { value: 60 });
redistributeExcessVotes(candidateData, candidateSet, 5, { value: 60 });
expect(candidateSet.get('B')?.totalVotes).toBe(10 + (30 - 25));
});

Expand All @@ -721,7 +744,7 @@ describe('redistributeExcessVotes', () => {
totalVotes: 30,
votes: [{ voteCount: 30, voteOrder: ['A'] }],
};
redistributeExcessVotes(candidateData, candidateSet, 25, { value: 60 });
redistributeExcessVotes(candidateData, candidateSet, 5, { value: 60 });
expect(candidateSet.get('B')?.totalVotes).toBe(10);
});

Expand All @@ -733,7 +756,7 @@ describe('redistributeExcessVotes', () => {
totalVotes: 25,
votes: [{ voteCount: 25, voteOrder: ['A', 'B'] }],
};
redistributeExcessVotes(candidateData, candidateSet, 25, { value: 50 });
redistributeExcessVotes(candidateData, candidateSet, 0, { value: 50 });
expect(candidateSet.get('B')?.totalVotes).toBe(10);
});

Expand All @@ -744,75 +767,11 @@ describe('redistributeExcessVotes', () => {
totalVotes: 30,
votes: [{ voteCount: 30, voteOrder: [] }],
};
redistributeExcessVotes(
candidateData,
candidateSet,
25,
newQuotaTotalVotes,
);
redistributeExcessVotes(candidateData, candidateSet, 5, newQuotaTotalVotes);
expect(newQuotaTotalVotes.value).toBeLessThan(100);
});
});

describe('organizeVotesByNextCandidate', () => {
it('should organize votes by the next candidate in the preference list', () => {
const votes: VoteRecord[] = [
{ voteCount: 10, voteOrder: ['B', 'C'] },
{ voteCount: 20, voteOrder: ['B', 'A'] },
];
const organizedVotes = organizeVotesByNextCandidate(votes);
expect(organizedVotes.size).toBe(1);
expect(organizedVotes.get('B')?.totalVotes).toBe(30);
});

it('should handle the case where votes have different next candidates', () => {
const votes: VoteRecord[] = [
{ voteCount: 10, voteOrder: ['B', 'C'] },
{ voteCount: 20, voteOrder: ['C', 'A'] },
];
const organizedVotes = organizeVotesByNextCandidate(votes);
expect(organizedVotes.size).toBe(2);
expect(organizedVotes.get('B')?.totalVotes).toBe(10);
expect(organizedVotes.get('C')?.totalVotes).toBe(20);
});

it('should ignore votes that have no next candidate', () => {
const votes: VoteRecord[] = [
{ voteCount: 10, voteOrder: [] },
{ voteCount: 20, voteOrder: ['C', 'A'] },
];
const organizedVotes = organizeVotesByNextCandidate(votes);
expect(organizedVotes.size).toBe(1);
expect(organizedVotes.get('C')?.totalVotes).toBe(20);
});

it('should aggregate votes for the same next candidate', () => {
const votes: VoteRecord[] = [
{ voteCount: 10, voteOrder: ['B', 'C'] },
{ voteCount: 5, voteOrder: ['B', 'A'] },
];
const organizedVotes = organizeVotesByNextCandidate(votes);
expect(organizedVotes.size).toBe(1);
expect(organizedVotes.get('B')?.totalVotes).toBe(15);
});

it('should handle votes with only one candidate', () => {
const votes: VoteRecord[] = [
{ voteCount: 10, voteOrder: ['B'] },
{ voteCount: 20, voteOrder: ['B'] },
];
const organizedVotes = organizeVotesByNextCandidate(votes);
expect(organizedVotes.size).toBe(1);
expect(organizedVotes.get('B')?.totalVotes).toBe(30);
});

it('should handle empty vote records gracefully', () => {
const votes: VoteRecord[] = [];
const organizedVotes = organizeVotesByNextCandidate(votes);
expect(organizedVotes.size).toBe(0);
});
});

describe('redistributeToCandidates', () => {
it('should correctly redistribute votes to existing candidates', () => {
const candidateSet = new Map<Candidate, CandidateMapItem>([
Expand All @@ -829,7 +788,7 @@ describe('redistributeToCandidates', () => {
{ totalVotes: 20, votes: [{ voteCount: 20, voteOrder: ['C', 'A'] }] },
],
]);
redistributeToCandidates(organizedVotes, candidateSet, 1, 10, 30);
redistributeToCandidates(organizedVotes, candidateSet, 10, 30);
expect(candidateSet.get('B')?.totalVotes).toBeCloseTo(13.33, 2); // B should receive approximately 3.33 additional votes
expect(candidateSet.get('C')?.totalVotes).toBeCloseTo(26.67, 2); // C should receive approximately 6.67 additional votes
});
Expand All @@ -844,7 +803,7 @@ describe('redistributeToCandidates', () => {
{ totalVotes: 20, votes: [{ voteCount: 20, voteOrder: ['C', 'A'] }] },
],
]);
redistributeToCandidates(organizedVotes, candidateSet, 1, 10, 30);
redistributeToCandidates(organizedVotes, candidateSet, 10, 30);
expect(candidateSet.has('C')).toBe(true);
expect(candidateSet.get('C')?.totalVotes).toBeCloseTo(6.67, 2); // C should receive approximately 6.67 additional votes
});
Expand All @@ -859,7 +818,7 @@ describe('redistributeToCandidates', () => {
{ totalVotes: 10, votes: [{ voteCount: 10, voteOrder: ['B', 'C'] }] },
],
]);
redistributeToCandidates(organizedVotes, candidateSet, 0.5, 10, 20);
redistributeToCandidates(organizedVotes, candidateSet, 10, 20);
expect(candidateSet.get('B')?.totalVotes).toBe(15); // B should receive 5 additional votes
});

Expand All @@ -868,7 +827,7 @@ describe('redistributeToCandidates', () => {
['B', { totalVotes: 10, votes: [] }],
]);
const organizedVotes = new Map<Candidate, CandidateMapItem>();
redistributeToCandidates(organizedVotes, candidateSet, 1, 10, 20);
redistributeToCandidates(organizedVotes, candidateSet, 10, 20);
expect(candidateSet.get('B')?.totalVotes).toBe(10); // No votes redistributed, B should remain the same
});
});
Expand Down
Loading

0 comments on commit 91b2489

Please sign in to comment.