Skip to content

Commit

Permalink
CHAL-53 #done - Implement election reminders page (#36)
Browse files Browse the repository at this point in the history
* added election reminders page

* added tests

* added tests

* added pledge-to-vote-iframe to coveragePathIgnorePatterns array
  • Loading branch information
dvorakjt committed Sep 22, 2024
1 parent 1cb031c commit ee862e3
Show file tree
Hide file tree
Showing 27 changed files with 959 additions and 5 deletions.
7 changes: 7 additions & 0 deletions jest-setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,10 @@ console.warn = (message, ...optionalParams) => {

actualWarn(message, ...optionalParams);
};

/* Mock the ResizeObserver */
global.ResizeObserver = jest.fn().mockImplementation(() => ({
observe: jest.fn(),
unobserve: jest.fn(),
disconnect: jest.fn(),
}));
3 changes: 3 additions & 0 deletions jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ const config = {
'<rootDir>/src/app/register/eligibility/page.tsx',
'<rootDir>/src/app/register/names/page.tsx',
'<rootDir>/src/app/register/other-details/page.tsx',
// pledge to vote iframe is heavily reliant on the ResizeObserver and
// content dimensions
'<rootDir>/src/app/reminders/pledge-to-vote-iframe/pledge-to-vote-iframe.tsx',
],
//require 100% code coverage for the tests to pass
coverageThreshold: {
Expand Down
165 changes: 165 additions & 0 deletions src/__tests__/unit/app/reminders/completed/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import CompletedRemindersPage from '@/app/reminders/completed/page';
import { render, screen, cleanup } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
import { UserContext, type UserContextType } from '@/contexts/user-context';
import { AlertsContextProvider } from '@/contexts/alerts-context';
import { Builder } from 'builder-pattern';
import navigation from 'next/navigation';
import { UserType } from '@/model/enums/user-type';
import type { User } from '@/model/types/user';
import type { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';

jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
}));

describe('CompletedRemindersPage', () => {
let router: AppRouterInstance;

beforeEach(() => {
router = Builder<AppRouterInstance>().push(jest.fn()).build();
jest.spyOn(navigation, 'useRouter').mockImplementation(() => router);
});

afterEach(cleanup);

it('renders.', () => {
const user = Builder<User>()
.completedActions({
electionReminders: true,
registerToVote: false,
sharedChallenge: false,
})
.build();

const userContextValue = Builder<UserContextType>()
.user(user)
.gotElectionReminders(() => Promise.resolve())
.build();

render(
<AlertsContextProvider>
<UserContext.Provider value={userContextValue}>
<CompletedRemindersPage searchParams={{}} />
</UserContext.Provider>
</AlertsContextProvider>,
);
});

it('displays an alert if its searchParams include hasError=true.', () => {
const user = Builder<User>()
.completedActions({
electionReminders: true,
registerToVote: false,
sharedChallenge: false,
})
.build();

const userContextValue = Builder<UserContextType>()
.user(user)
.gotElectionReminders(() => Promise.resolve())
.build();

render(
<AlertsContextProvider>
<UserContext.Provider value={userContextValue}>
<CompletedRemindersPage searchParams={{ hasError: 'true' }} />
</UserContext.Provider>
</AlertsContextProvider>,
);

const alert = screen.getByRole('alert');
expect(alert.classList).not.toContain('hidden');
expect(alert.textContent).toBe(
"Oops! We couldn't award you a badge. Please try again later.",
);
});

it(`does not display an alert if its searchParams do not include
hasError=true.`, () => {
const user = Builder<User>()
.completedActions({
electionReminders: true,
registerToVote: false,
sharedChallenge: false,
})
.build();

const userContextValue = Builder<UserContextType>()
.user(user)
.gotElectionReminders(() => Promise.resolve())
.build();

render(
<AlertsContextProvider>
<UserContext.Provider value={userContextValue}>
<CompletedRemindersPage searchParams={{}} />
</UserContext.Provider>
</AlertsContextProvider>,
);

const alert = screen.getByRole('alert');
expect(alert.classList).toContain('hidden');
});

it(`redirects the user to /progress if they click 'Continue' and are a
challenger.`, async () => {
const userContextValue = Builder<UserContextType>()
.user(
Builder<User>()
.type(UserType.Challenger)
.completedActions({
electionReminders: true,
registerToVote: false,
sharedChallenge: false,
})
.build(),
)
.gotElectionReminders(() => Promise.resolve())
.build();

const user = userEvent.setup();

render(
<AlertsContextProvider>
<UserContext.Provider value={userContextValue}>
<CompletedRemindersPage searchParams={{}} />
</UserContext.Provider>
</AlertsContextProvider>,
);

await user.click(screen.getByText(/continue/i));
expect(router.push).toHaveBeenCalledWith('/progress');
});

it(`redirects the user to /actions if they click 'Continue' and are not a
challenger.`, async () => {
const userContextValue = Builder<UserContextType>()
.user(
Builder<User>()
.type(UserType.Player)
.completedActions({
electionReminders: true,
registerToVote: false,
sharedChallenge: false,
})
.build(),
)
.gotElectionReminders(() => Promise.resolve())
.build();

const user = userEvent.setup();

render(
<AlertsContextProvider>
<UserContext.Provider value={userContextValue}>
<CompletedRemindersPage searchParams={{}} />
</UserContext.Provider>
</AlertsContextProvider>,
);

await user.click(screen.getByText(/continue/i));
expect(router.push).toHaveBeenCalledWith('/actions');
});
});
36 changes: 36 additions & 0 deletions src/__tests__/unit/app/reminders/page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import RemindersPage from '@/app/reminders/page';
import { render, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom';
import { UserContext, type UserContextType } from '@/contexts/user-context';
import { AlertsContextProvider } from '@/contexts/alerts-context';
import { Builder } from 'builder-pattern';
import type { User } from '@/model/types/user';

jest.mock('next/navigation', () => require('next-router-mock'));

describe('RemindersPage', () => {
afterEach(cleanup);

it('renders.', () => {
const user = Builder<User>()
.completedActions({
electionReminders: false,
registerToVote: false,
sharedChallenge: false,
})
.build();

const userContextValue = Builder<UserContextType>()
.user(user)
.gotElectionReminders(() => Promise.resolve())
.build();

render(
<AlertsContextProvider>
<UserContext.Provider value={userContextValue}>
<RemindersPage />
</UserContext.Provider>
</AlertsContextProvider>,
);
});
});
137 changes: 137 additions & 0 deletions src/__tests__/unit/components/guards/has-not-completed-action.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import { hasNotCompletedAction } from '@/components/guards/has-not-completed-action';
import { render, screen, cleanup } from '@testing-library/react';
import '@testing-library/jest-dom';
import navigation from 'next/navigation';
import { Builder } from 'builder-pattern';
import { UserContext, type UserContextType } from '@/contexts/user-context';
import { Actions } from '@/model/enums/actions';
import type { User } from '@/model/types/user';
import type { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.shared-runtime';

jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
}));

describe('hasNotCompletedAction', () => {
let router: AppRouterInstance;

beforeEach(() => {
router = Builder<AppRouterInstance>().push(jest.fn()).build();
jest.spyOn(navigation, 'useRouter').mockImplementation(() => router);
});

afterEach(cleanup);

it('returns a component that can be rendered.', () => {
const user = Builder<User>()
.completedActions({
electionReminders: false,
registerToVote: false,
sharedChallenge: false,
})
.build();

const userContextValue = Builder<UserContextType>().user(user).build();

const TestComponent = hasNotCompletedAction(
function () {
return <div data-testid="test"></div>;
},
{ action: Actions.ElectionReminders, redirectTo: '/' },
);

render(
<UserContext.Provider value={userContextValue}>
<TestComponent />
</UserContext.Provider>,
);
expect(screen.queryByTestId('test')).toBeInTheDocument();
});

it('returns a component that can accept props.', () => {
const user = Builder<User>()
.completedActions({
electionReminders: false,
registerToVote: false,
sharedChallenge: false,
})
.build();

const userContextValue = Builder<UserContextType>().user(user).build();

interface TestComponentProps {
message: string;
}

const TestComponent = hasNotCompletedAction(
function ({ message }: TestComponentProps) {
return <div>{message}</div>;
},
{ action: Actions.ElectionReminders, redirectTo: '/' },
);

const message = 'test';

render(
<UserContext.Provider value={userContextValue}>
<TestComponent message={message} />
</UserContext.Provider>,
);
expect(screen.queryByText(message)).toBeInTheDocument();
});

it('blocks access to a page if the user has completed the provided action.', () => {
const user = Builder<User>()
.completedActions({
electionReminders: true,
registerToVote: false,
sharedChallenge: false,
})
.build();

const userContextValue = Builder<UserContextType>().user(user).build();

const redirectTo = '/';

const TestComponent = hasNotCompletedAction(
function () {
return null;
},
{ action: Actions.ElectionReminders, redirectTo },
);

render(
<UserContext.Provider value={userContextValue}>
<TestComponent />
</UserContext.Provider>,
);

expect(router.push).toHaveBeenCalledWith(redirectTo);
});

it('allows access to a page if the user has not completed the provided action.', () => {
const user = Builder<User>()
.completedActions({
electionReminders: true,
registerToVote: true,
sharedChallenge: false,
})
.build();

const userContextValue = Builder<UserContextType>().user(user).build();

const TestComponent = hasNotCompletedAction(
function () {
return null;
},
{ action: Actions.SharedChallenge, redirectTo: '/' },
);

render(
<UserContext.Provider value={userContextValue}>
<TestComponent />
</UserContext.Provider>,
);
expect(router.push).not.toHaveBeenCalled();
});
});
Loading

0 comments on commit ee862e3

Please sign in to comment.