diff --git a/web/frontend/src/pages/form/GroupedResult.tsx b/web/frontend/src/pages/form/GroupedResult.tsx index bb077bfd4..b510b857a 100644 --- a/web/frontend/src/pages/form/GroupedResult.tsx +++ b/web/frontend/src/pages/form/GroupedResult.tsx @@ -124,9 +124,15 @@ const GroupedResult: FC = ({ rankResult, selectResult, textR const select = element as SelectQuestion; if (selectResult.has(id)) { - res = countSelectResult(selectResult.get(id)).resultsInPercent.map((percent, index) => { - return { Candidate: select.Choices[index], Percentage: `${percent}%` }; - }); + res = countSelectResult(selectResult.get(id)) + .map(([, totalCount], index) => { + return { + Candidate: select.Choices[index], + TotalCount: totalCount, + NumberOfBallots: selectResult.get(id).length, + }; + }) + .sort((x, y) => y.TotalCount - x.TotalCount); dataToDownload.push({ Title: element.Title.En, Results: res }); } break; diff --git a/web/frontend/src/pages/form/components/ProgressBar.tsx b/web/frontend/src/pages/form/components/ProgressBar.tsx index 4fc8e1790..0d1e6f94f 100644 --- a/web/frontend/src/pages/form/components/ProgressBar.tsx +++ b/web/frontend/src/pages/form/components/ProgressBar.tsx @@ -5,7 +5,14 @@ type ProgressBarProps = { children: string; }; -const ProgressBar: FC = ({ isBest, children }) => { +type SelectProgressBarProps = { + percent: string; + totalCount: number; + numberOfBallots: number; + isBest: boolean; +}; + +export const ProgressBar: FC = ({ isBest, children }) => { return (
@@ -21,4 +28,23 @@ const ProgressBar: FC = ({ isBest, children }) => { ); }; -export default ProgressBar; +export const SelectProgressBar: FC = ({ + percent, + totalCount, + numberOfBallots, + isBest, +}) => { + return ( +
+
+
+ +
{`${totalCount}/${numberOfBallots}`}
+
+
+ ); +}; diff --git a/web/frontend/src/pages/form/components/RankResult.tsx b/web/frontend/src/pages/form/components/RankResult.tsx index c00b594d4..a3a07d168 100644 --- a/web/frontend/src/pages/form/components/RankResult.tsx +++ b/web/frontend/src/pages/form/components/RankResult.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { RankQuestion } from 'types/configuration'; -import ProgressBar from './ProgressBar'; +import { ProgressBar } from './ProgressBar'; import { countRankResult } from './utils/countResult'; import { default as i18n } from 'i18next'; diff --git a/web/frontend/src/pages/form/components/SelectResult.tsx b/web/frontend/src/pages/form/components/SelectResult.tsx index a227d9b8f..1dc49ffc2 100644 --- a/web/frontend/src/pages/form/components/SelectResult.tsx +++ b/web/frontend/src/pages/form/components/SelectResult.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { SelectQuestion } from 'types/configuration'; -import ProgressBar from './ProgressBar'; +import { SelectProgressBar } from './ProgressBar'; import { countSelectResult } from './utils/countResult'; import { default as i18n } from 'i18next'; @@ -11,12 +11,16 @@ type SelectResultProps = { // Display the results of a select question. const SelectResult: FC = ({ select, selectResult }) => { - const { resultsInPercent, maxIndices } = countSelectResult(selectResult); + const sortedResults = countSelectResult(selectResult) + .map((result, index) => { + const tempResult: [string, number, number] = [...result, index]; + return tempResult; + }) + .sort((x, y) => y[1] - x[1]); + const maxCount = sortedResults[0][1]; const displayResults = () => { - return resultsInPercent.map((percent, index) => { - const isBest = maxIndices.includes(index); - + return sortedResults.map(([percent, totalCount, origIndex], index) => { return (
@@ -24,12 +28,16 @@ const SelectResult: FC = ({ select, selectResult }) => { { (select.ChoicesMap.has(i18n.language) ? select.ChoicesMap.get(i18n.language) - : select.ChoicesMap.get('en'))[index] + : select.ChoicesMap.get('en'))[origIndex] } :
- {percent} +
); }); diff --git a/web/frontend/src/pages/form/components/TextResult.tsx b/web/frontend/src/pages/form/components/TextResult.tsx index 4a010331e..059f4d127 100644 --- a/web/frontend/src/pages/form/components/TextResult.tsx +++ b/web/frontend/src/pages/form/components/TextResult.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react'; import { TextQuestion } from 'types/configuration'; -import ProgressBar from './ProgressBar'; +import { ProgressBar } from './ProgressBar'; import { countTextResult } from './utils/countResult'; import { default as i18n } from 'i18next'; diff --git a/web/frontend/src/pages/form/components/utils/countResult.ts b/web/frontend/src/pages/form/components/utils/countResult.ts index c0bf7537b..20e487c6e 100644 --- a/web/frontend/src/pages/form/components/utils/countResult.ts +++ b/web/frontend/src/pages/form/components/utils/countResult.ts @@ -42,31 +42,20 @@ const countRankResult = (rankResult: number[][], rank: RankQuestion) => { // percentage of the total number of votes and which candidate(s) in the // select.Choices has the most votes const countSelectResult = (selectResult: number[][]) => { - const resultsInPercent: string[] = []; - const maxIndices: number[] = []; - let max = 0; - - const results = selectResult.reduce((a, b) => { - return a.map((value, index) => { - const current = value + b[index]; - - if (current >= max) { - max = current; - } - return current; + let results: [string, number][] = []; + + selectResult + .reduce( + (tally, currBallot) => tally.map((currCount, index) => currCount + currBallot[index]), + new Array(selectResult[0].length).fill(0) + ) + .forEach((totalCount) => { + results.push([ + (Math.round((totalCount / selectResult.length) * 100 * 100) / 100).toFixed(2).toString(), + totalCount, + ]); }); - }, new Array(selectResult[0].length).fill(0)); - - results.forEach((count, index) => { - if (count === max) { - maxIndices.push(index); - } - - const percentage = (count / selectResult.length) * 100; - const roundedPercentage = (Math.round(percentage * 100) / 100).toFixed(2); - resultsInPercent.push(roundedPercentage); - }); - return { resultsInPercent, maxIndices }; + return results; }; // Count the number of votes for each candidate and returns the counts and the diff --git a/web/frontend/src/types/form.ts b/web/frontend/src/types/form.ts index 6ea5a80dd..71d5be18a 100644 --- a/web/frontend/src/types/form.ts +++ b/web/frontend/src/types/form.ts @@ -80,7 +80,12 @@ type TextResults = Map; interface DownloadedResults { Title: string; - Results?: { Candidate: string; Percentage: string }[]; + Results?: { + Candidate: string; + Percent?: string; + TotalCount?: number; + NumberOfBallots?: number; + }[]; } interface BallotResults { BallotNumber: number; diff --git a/web/frontend/tests/json/evoting/forms/combined.json b/web/frontend/tests/json/evoting/forms/combined.json index 2c1eb59a0..d3edf82b1 100644 --- a/web/frontend/tests/json/evoting/forms/combined.json +++ b/web/frontend/tests/json/evoting/forms/combined.json @@ -116,11 +116,11 @@ "SelectResult": [ [ false, - false, + true, true ], [ - false, + true, false, false, true diff --git a/web/frontend/tests/result.spec.ts b/web/frontend/tests/result.spec.ts index 7b8d1922e..d4ccfeacf 100644 --- a/web/frontend/tests/result.spec.ts +++ b/web/frontend/tests/result.spec.ts @@ -51,22 +51,29 @@ test('Assert form titles are displayed correctly', async ({ page }) => { test('Assert grouped results are displayed correctly', async ({ page }) => { // grouped results are displayed by default - for (const [index, scaffold] of Form.Configuration.Scaffold.entries()) { + let i = 1; + for (const expected of [ + [ + ['Blue', '3/4'], + ['Green', '2/4'], + ['Red', '1/4'], + ], + [ + ['Cyan', '2/4'], + ['Magenta', '2/4'], + ['Yellow', '1/4'], + ['Key', '1/4'], + ], + ]) { const resultGrid = await page .getByTestId('content') - .locator(`xpath=./div/div/div[2]/div/div[2]/div/div/div[${index + 1}]/div/div/div[2]`); + .locator(`xpath=./div/div/div[2]/div/div[2]/div/div/div[${i}]/div/div/div[2]`); + i += 1; let j = 1; - for (const [i, choice] of scaffold.Selects.at(0).Choices.entries()) { - await expect(resultGrid.locator(`xpath=./div[${j}]/span`)).toContainText( - JSON.parse(choice).en - ); + for (const [title, totalCount] of expected) { + await expect(resultGrid.locator(`xpath=./div[${j}]/span`)).toContainText(title); await expect(resultGrid.locator(`xpath=./div[${j + 1}]/div/div[2]`)).toContainText( - [ - ['33.33%', '33.33%', '66.67%'], - ['25.00%', '50.00%', '25.00%', '25.00%'], - ] - .at(index) - .at(i) + totalCount ); j += 2; }