Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CHAL-53 #done - Implement election reminders page #36

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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