From 6be3ea048a9c6e660914ae9ff6dbcf36c685e998 Mon Sep 17 00:00:00 2001 From: Joseph Dvorak Date: Mon, 19 Aug 2024 19:32:37 -0400 Subject: [PATCH] updated types, services, types, tests and stories to reflect additional badge type --- .../election-reminders-badge-icon.svg | 4 + .../progress/badges/badges.test.tsx | 148 +++++++++++++++--- .../user-record-parser.test.ts | 28 +++- .../progress/badges/action-badge.tsx | 33 ++-- .../progress/badges/player-badge.tsx | 14 +- src/model/types/action-badge.ts | 4 +- src/model/types/badge.ts | 5 - src/model/types/player-badge.ts | 3 +- .../user-record-parser/user-record-parser.ts | 4 +- .../progress/badges/badges.stories.tsx | 3 +- src/stories/pages/progress.stories.tsx | 2 +- 11 files changed, 184 insertions(+), 64 deletions(-) create mode 100644 public/static/images/pages/progress/election-reminders-badge-icon.svg diff --git a/public/static/images/pages/progress/election-reminders-badge-icon.svg b/public/static/images/pages/progress/election-reminders-badge-icon.svg new file mode 100644 index 00000000..b3d30146 --- /dev/null +++ b/public/static/images/pages/progress/election-reminders-badge-icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/__tests__/unit/components/progress/badges/badges.test.tsx b/src/__tests__/unit/components/progress/badges/badges.test.tsx index e62fb1a2..dff99010 100644 --- a/src/__tests__/unit/components/progress/badges/badges.test.tsx +++ b/src/__tests__/unit/components/progress/badges/badges.test.tsx @@ -1,38 +1,140 @@ import '@testing-library/jest-dom'; -import { render, cleanup } from '@testing-library/react'; +import { render, screen, cleanup } from '@testing-library/react'; import { Badges } from '@/components/progress/badges'; -import type { Badge } from '@/model/types/badge'; import { Actions } from '@/model/enums/actions'; +import { AVATARS } from '@/constants/avatars'; +import type { Badge } from '@/model/types/badge'; +import type { PlayerBadge } from '@/model/types/player-badge'; describe('Bagdes', () => { afterEach(cleanup); - it('does not modify the original badges array.', () => { - const badges: Badge[] = [ - { action: Actions.VoterRegistration }, - { action: Actions.SharedChallenge }, - { playerName: 'test', playerAvatar: '1' }, + // it('does not modify the original badges array.', () => { + // const badges: Badge[] = [ + // { action: Actions.VoterRegistration }, + // { action: Actions.SharedChallenge }, + // { playerName: 'test', playerAvatar: '1' }, + // ]; + // render(); + + // expect(badges).toHaveLength(3); + // }); + + // it('does not modify the original badges array', () => { + // const badges: Badge[] = [ + // { action: Actions.VoterRegistration }, + // { action: Actions.SharedChallenge }, + // { playerName: 'test', playerAvatar: '1' }, + // { playerName: 'test1', playerAvatar: '1' }, + // { playerName: 'test2', playerAvatar: '1' }, + // { playerName: 'test3', playerAvatar: '1' }, + // { playerName: 'test4', playerAvatar: '1' }, + // { playerName: 'test5', playerAvatar: '1' }, + // { playerName: 'test6', playerAvatar: '1' }, + // { playerName: 'test7', playerAvatar: '1' }, + // ]; + // render(); + + // expect(badges).toHaveLength(10); + // }); + + it('renders action badges.', () => { + const actionBadges = [ + { + action: Actions.SharedChallenge, + }, + { + action: Actions.VoterRegistration, + }, + { + action: Actions.ElectionReminders, + }, + ]; + + render(); + + const actionBadgeTextContent = [ + 'You Shared', + 'You Registered', + 'Got Alerts', ]; - render(); - expect(badges).toHaveLength(3); + actionBadgeTextContent.forEach(badgeText => { + expect(screen.queryByText(badgeText)).toBeInTheDocument(); + }); }); - it('does not modify the original badges array', () => { - const badges: Badge[] = [ - { action: Actions.VoterRegistration }, - { action: Actions.SharedChallenge }, - { playerName: 'test', playerAvatar: '1' }, - { playerName: 'test1', playerAvatar: '1' }, - { playerName: 'test2', playerAvatar: '1' }, - { playerName: 'test3', playerAvatar: '1' }, - { playerName: 'test4', playerAvatar: '1' }, - { playerName: 'test5', playerAvatar: '1' }, - { playerName: 'test6', playerAvatar: '1' }, - { playerName: 'test7', playerAvatar: '1' }, + it('renders player badges.', () => { + const playerBadges: PlayerBadge[] = [ + { + playerName: 'Bob', + playerAvatar: '0', + }, + { + playerName: 'Sue', + playerAvatar: '1', + }, + { + playerName: 'Jack', + playerAvatar: '2', + }, + { + playerName: 'Mary', + playerAvatar: '3', + }, ]; - render(); - expect(badges).toHaveLength(10); + render(); + + playerBadges.forEach(badge => { + expect(screen.queryByText(badge.playerName)).toBeInTheDocument(); + expect( + screen.queryByAltText(AVATARS[badge.playerAvatar].altText), + ).toBeInTheDocument(); + }); + }); + + it(`renders up to 8 badge placeholders when the number of badges it receives + is fewer than 8.`, () => { + const maxBadgeCount = 8; + const badges: Badge[] = []; + + while (badges.length < maxBadgeCount) { + render(); + + const firstEmptyBadgeNumber = badges.length + 1; + for (let i = firstEmptyBadgeNumber; i <= maxBadgeCount; i++) { + expect(screen.queryByText(i)).toBeInTheDocument(); + } + + const firstUnusedNumber = badges.length; + for (let i = 1; i <= firstUnusedNumber; i++) { + expect(screen.queryByText(i)).not.toBeInTheDocument(); + } + + cleanup(); + + badges.push({ + playerName: '', + playerAvatar: '0', + }); + } + }); + + it(`renders a maximum of 8 badges even if the array of badges it receives + contains more.`, () => { + const badges: Badge[] = []; + const playerName = 'test'; + + for (let i = 0; i < 30; i++) { + badges.push({ + playerName, + playerAvatar: '0', + }); + } + + render(); + const renderedBadges = screen.getAllByText(playerName); + expect(renderedBadges.length).toBe(8); }); }); diff --git a/src/__tests__/unit/services/server/user-record-parser/user-record-parser.test.ts b/src/__tests__/unit/services/server/user-record-parser/user-record-parser.test.ts index f0ecb951..eaacc68a 100644 --- a/src/__tests__/unit/services/server/user-record-parser/user-record-parser.test.ts +++ b/src/__tests__/unit/services/server/user-record-parser/user-record-parser.test.ts @@ -15,9 +15,9 @@ describe('UserRecordParser', () => { completed_challenge: false, invite_code: 'test-invite-code', completed_actions: { - election_reminders: false, - register_to_vote: false, - shared_challenge: false, + election_reminders: true, + register_to_vote: true, + shared_challenge: true, }, badges: [ { @@ -25,6 +25,16 @@ describe('UserRecordParser', () => { player_name: null, player_avatar: null, }, + { + action: Actions.VoterRegistration, + player_name: null, + player_avatar: null, + }, + { + action: Actions.ElectionReminders, + player_name: null, + player_avatar: null, + }, { player_name: 'user 2', player_avatar: '1', @@ -55,14 +65,20 @@ describe('UserRecordParser', () => { completedChallenge: false, inviteCode: 'test-invite-code', completedActions: { - electionReminders: false, - registerToVote: false, - sharedChallenge: false, + electionReminders: true, + registerToVote: true, + sharedChallenge: true, }, badges: [ { action: Actions.SharedChallenge, }, + { + action: Actions.VoterRegistration, + }, + { + action: Actions.ElectionReminders, + }, { playerName: 'user 2', playerAvatar: '1', diff --git a/src/components/progress/badges/action-badge.tsx b/src/components/progress/badges/action-badge.tsx index 5e05857e..0a1d844e 100644 --- a/src/components/progress/badges/action-badge.tsx +++ b/src/components/progress/badges/action-badge.tsx @@ -9,24 +9,31 @@ interface ActionBadgeProps { } /** - * Renders a component to display a action badge + * Renders a component to display an action badge. * - * @param badge - Component's badge data + * @param badge - Component's badge data. * - * @param index - Index number of badge + * @param index - Index number of badge. * - * @returns A React component that displays either a registered or shared action badge + * @returns A React component that displays either an action badge. */ export function ActionBadge({ badge, index }: ActionBadgeProps): JSX.Element { - let label: string | null = null; - let action: string | null = null; + let label: string; + let imagePrefix: string; - if (badge.action === Actions.VoterRegistration) { - label = 'You Registered'; - action = 'register-to-vote'; - } else if (badge.action === Actions.SharedChallenge) { - label = 'You Shared'; - action = 'shared-challenge'; + switch (badge.action) { + case Actions.VoterRegistration: + label = 'You Registered'; + imagePrefix = 'register-to-vote'; + break; + case Actions.SharedChallenge: + label = 'You Shared'; + imagePrefix = 'shared-challenge'; + break; + case Actions.ElectionReminders: + label = 'Got Alerts'; + imagePrefix = 'election-reminders'; + break; } return ( @@ -45,7 +52,7 @@ export function ActionBadge({ badge, index }: ActionBadgeProps): JSX.Element { alt="action badge icon" className={styles.icon} src={require( - `../../../../public/static/images/pages/progress/${action}-badge-icon.svg`, + `../../../../public/static/images/pages/progress/${imagePrefix}-badge-icon.svg`, )} />

{label}

diff --git a/src/components/progress/badges/player-badge.tsx b/src/components/progress/badges/player-badge.tsx index 6ad7138f..6c014bc1 100644 --- a/src/components/progress/badges/player-badge.tsx +++ b/src/components/progress/badges/player-badge.tsx @@ -1,5 +1,5 @@ import type { PlayerBadge } from '@/model/types/player-badge'; -import type { Avatar } from '@/model/types/avatar'; +import { AVATARS } from '@/constants/avatars'; import styles from './styles.module.scss'; import Image from 'next/image'; @@ -18,11 +18,7 @@ interface PlayerBadgeProps { * @returns A React component that displays a badge of a player's name and icon */ export function PlayerBadge({ badge, index }: PlayerBadgeProps): JSX.Element { - let name: string | null = null; - let icon: Avatar | null = null; - - name = badge.playerName; - icon = badge.playerAvatar; + const { playerName: name, playerAvatar: avatar } = badge; return (
@@ -37,11 +33,9 @@ export function PlayerBadge({ badge, index }: PlayerBadgeProps): JSX.Element {
player badge icon

{name}

diff --git a/src/model/types/action-badge.ts b/src/model/types/action-badge.ts index f0b34751..361ff003 100644 --- a/src/model/types/action-badge.ts +++ b/src/model/types/action-badge.ts @@ -1,8 +1,8 @@ import { Actions } from '../enums/actions'; /** - * Represents a badge awarded to the user either through their own actions + * Represents a badge awarded to the user through their own actions. */ export type ActionBadge = { - action: Actions.VoterRegistration | Actions.SharedChallenge; + action: Actions; }; diff --git a/src/model/types/badge.ts b/src/model/types/badge.ts index 48ec0ffe..b2a72c06 100644 --- a/src/model/types/badge.ts +++ b/src/model/types/badge.ts @@ -4,10 +4,5 @@ import type { PlayerBadge } from './player-badge'; /** * Represents a badge awarded to the user either through their own actions, or * due to an action taken by a player they invited. - * - * @remarks - * The two actions that grant the challenger themselves a badge are registering - * to vote and sharing the challenger. Signing up for election reminders does - * not grant the user a badge. */ export type Badge = ActionBadge | PlayerBadge; diff --git a/src/model/types/player-badge.ts b/src/model/types/player-badge.ts index 54bcf9cf..0202b836 100644 --- a/src/model/types/player-badge.ts +++ b/src/model/types/player-badge.ts @@ -1,7 +1,8 @@ import { Avatar } from './avatar'; /** - * Represents a badge awarded to the user due to an action taken by a player they invited. + * Represents a badge awarded to the user due to an action taken by a player + * they invited. */ export type PlayerBadge = { playerName: string; diff --git a/src/services/server/user-record-parser/user-record-parser.ts b/src/services/server/user-record-parser/user-record-parser.ts index f9320ae3..12b71a79 100644 --- a/src/services/server/user-record-parser/user-record-parser.ts +++ b/src/services/server/user-record-parser/user-record-parser.ts @@ -7,7 +7,7 @@ import type { IUserRecordParser } from './i-user-record-parser'; import type { User } from '@/model/types/user'; interface DBActionBadge { - action: Actions.VoterRegistration | Actions.SharedChallenge; + action: Actions; player_name: null; player_avatar: null; } @@ -26,7 +26,7 @@ export const UserRecordParser = inject( private dbBadgeSchema = z.union([ z.object({ - action: z.enum([Actions.VoterRegistration, Actions.SharedChallenge]), + action: z.nativeEnum(Actions), player_name: z.null(), player_avatar: z.null(), }), diff --git a/src/stories/components/progress/badges/badges.stories.tsx b/src/stories/components/progress/badges/badges.stories.tsx index cd2fe6e5..497d6698 100644 --- a/src/stories/components/progress/badges/badges.stories.tsx +++ b/src/stories/components/progress/badges/badges.stories.tsx @@ -47,11 +47,12 @@ export const SinglePlayerBadge: Story = { }, }; -export const AllBadges: Story = { +export const AllBadgeTypes: Story = { render: () => { const badges: Badge[] = [ { action: Actions.VoterRegistration }, { action: Actions.SharedChallenge }, + { action: Actions.ElectionReminders }, { playerName: 'Player', playerAvatar: '1' }, ]; return ( diff --git a/src/stories/pages/progress.stories.tsx b/src/stories/pages/progress.stories.tsx index eae51019..eb670076 100644 --- a/src/stories/pages/progress.stories.tsx +++ b/src/stories/pages/progress.stories.tsx @@ -101,12 +101,12 @@ export const CompletedChallenge: Story = { badges: [ { action: Actions.VoterRegistration }, { action: Actions.SharedChallenge }, + { action: Actions.ElectionReminders }, { playerName: 'Player1', playerAvatar: '0' }, { playerName: 'Player2', playerAvatar: '1' }, { playerName: 'Player3', playerAvatar: '2' }, { playerName: 'Player4', playerAvatar: '3' }, { playerName: 'Player5', playerAvatar: '0' }, - { playerName: 'Player6', playerAvatar: '1' }, ], challengeEndTimestamp: DateTime.now().toUnixInteger(), completedChallenge: true,