Skip to content

Commit

Permalink
feat: disable edit button if rejected (#176)
Browse files Browse the repository at this point in the history
* feat: disable edit button if rejected

* fix: jest types and unit tests

* fix: page navigation delay removed

* fix: restore nextjs headers
  • Loading branch information
NithinKuruba authored Aug 21, 2024
1 parent 23d7597 commit ad6f829
Show file tree
Hide file tree
Showing 10 changed files with 181 additions and 79 deletions.
12 changes: 12 additions & 0 deletions app/__tests__/api/update-realm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,16 @@ describe('Profile Validations', () => {
const updatedFields = Object.keys(profileUpdate.mock.calls[0][0].data);
adminAllowedFields.forEach((field) => expect(updatedFields.includes(field)).toBeTruthy());
});

it('does not allow to update rejected realms', async () => {
(prisma.roster.findUnique as jest.Mock).mockImplementation(() => {
return Promise.resolve({ ...CustomRealmProfiles[0], approved: false });
});
const { req, res } = createMocks({
method: 'PUT',
query: { id: 1 },
});
await handler(req, res);
expect(res.statusCode).toBe(400);
});
});
24 changes: 16 additions & 8 deletions app/__tests__/custom-realm-dashboard.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import { getRealmEvents } from 'services/events';
import { CustomRealmFormData } from 'types/realm-profile';
import Router from 'next/router';
import { CustomRealms } from './fixtures';
import { debug } from 'jest-preview';

jest.mock('services/realm', () => {
return {
deleteRealmRequest: jest.fn((realmInfo: CustomRealmFormData) => Promise.resolve([true, null])),
updateRealmProfile: jest.fn((id: number, status: string) => Promise.resolve([true, null])),
getRealmProfiles: jest.fn((excludeArchived: boolean) => Promise.resolve([CustomRealms, null])),
};
});

Expand Down Expand Up @@ -107,10 +107,10 @@ jest.mock('../pages/api/auth/[...nextauth]', () => {
});

describe('Table', () => {
it('Loads in table data from serverside props', () => {
render(<CustomRealmDashboard defaultRealmRequests={CustomRealms} />);
expect(screen.getByText('realm 1'));
expect(screen.getByText('realm 2'));
it('Loads in table data from serverside props', async () => {
render(<CustomRealmDashboard />);
await waitFor(() => screen.getByText('realm 1'));
await waitFor(() => screen.getByText('realm 2'));
});
});

Expand All @@ -127,6 +127,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
await waitFor(() => screen.getByText('realm 1'));
fireEvent.click(screen.getByText('realm 1'));
screen.getByText('Access Request').click();
await waitFor(() => screen.getByText('Approve Custom Realm', { selector: 'button' }).click());
Expand All @@ -141,6 +142,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
await waitFor(() => screen.getByText('realm 1'));
fireEvent.click(screen.getByText('realm 1'));
screen.getByText('Access Request').click();
await waitFor(() => screen.getByText('Decline Custom Realm', { selector: 'button' }).click());
Expand All @@ -155,6 +157,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
await waitFor(() => screen.getByText('realm 1'));
fireEvent.click(screen.getByText('realm 1'));
screen.getByText('Access Request').click();
await waitFor(() => screen.getByText('Approve Custom Realm', { selector: 'button' }).click());
Expand All @@ -173,6 +176,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
await waitFor(() => screen.getByText('realm 1'));
fireEvent.click(screen.getByText('realm 1'));
screen.getByText('Access Request').click();
await waitFor(() => screen.getByText('Decline Custom Realm', { selector: 'button' }).click());
Expand All @@ -191,6 +195,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
await waitFor(() => screen.getByText('realm 1'));
(updateRealmProfile as jest.MockedFunction<any>).mockImplementationOnce(() =>
Promise.resolve([null, { message: 'failure' }]),
);
Expand Down Expand Up @@ -227,6 +232,7 @@ describe('Status update', () => {
router={Router as any}
/>,
);
await waitFor(() => screen.getByText('realm 1'));
(updateRealmProfile as jest.MockedFunction<any>).mockImplementationOnce(() =>
Promise.resolve([null, { message: 'failure' }]),
);
Expand Down Expand Up @@ -263,7 +269,8 @@ describe('Events table', () => {
});

it('fetches correct events when selected row changes', async () => {
render(<CustomRealmDashboard defaultRealmRequests={CustomRealms} />);
render(<CustomRealmDashboard />);
await waitFor(() => screen.getByText('realm 1'));
const row1 = screen.getByText('realm 1');
fireEvent.click(row1);
expect(getRealmEvents).toHaveBeenCalledTimes(1);
Expand All @@ -279,8 +286,8 @@ describe('Events table', () => {
});

it('displays events for the selected realm and updates when changing rows', async () => {
render(<CustomRealmDashboard defaultRealmRequests={CustomRealms} />);

render(<CustomRealmDashboard />);
await waitFor(() => screen.getByText('realm 1'));
const firstRealmRow = screen.getByText('realm 1');
fireEvent.click(firstRealmRow);
const eventTab = screen.getByText('Events');
Expand All @@ -306,6 +313,7 @@ describe('Events table', () => {
router={Router as any}
/>,
);
await waitFor(() => screen.getByText('realm 1'));
fireEvent.click(screen.getByText('realm 1'));
await waitFor(() => screen.getByText('Network error when fetching realm events.'));
});
Expand Down
26 changes: 26 additions & 0 deletions app/__tests__/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export const CustomRealms: CustomRealmFormData[] = [
rcChannelOwnedBy: '',
materialToSend: '',
status: 'pending',
idps: [],
protocol: [],
},
{
id: 2,
Expand All @@ -43,6 +45,30 @@ export const CustomRealms: CustomRealmFormData[] = [
division: 'division',
approved: null,
status: 'pending',
idps: [],
protocol: [],
},
{
id: 3,
realm: 'realm 3',
productName: 'name',
purpose: 'purpose',
primaryEndUsers: ['livingInBC', 'businessInBC', 'govEmployees', 'details'],
environments: ['dev', 'test', 'prod'],
preferredAdminLoginMethod: 'idir',
productOwnerEmail: '[email protected]',
productOwnerIdirUserId: 'po',
technicalContactEmail: '[email protected]',
technicalContactIdirUserId: '[email protected]',
secondTechnicalContactIdirUserId: 'dmsd',
secondTechnicalContactEmail: '[email protected]',
ministry: 'ministry',
branch: 'branch',
division: 'division',
approved: false,
status: 'pending',
idps: [],
protocol: [],
},
];

Expand Down
85 changes: 85 additions & 0 deletions app/__tests__/my-dashboard.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import React from 'react';
import { render, screen, within, waitFor, fireEvent } from '@testing-library/react';
import App from 'pages/_app';
import CustomRealmDashboard from 'pages/custom-realm-dashboard';
import { updateRealmProfile } from 'services/realm';
import { getRealmEvents } from 'services/events';
import { CustomRealmFormData } from 'types/realm-profile';
import Router from 'next/router';
import { CustomRealms } from './fixtures';
import RealmLeftPanel from 'page-partials/my-dashboard/RealmLeftPanel';
import noop from 'lodash.noop';
import { debug } from 'jest-preview';

const editFunction = jest.fn();

jest.mock('next/router', () => ({
useRouter() {
return {
route: '/',
pathname: '',
query: '',
asPath: '',
push: jest.fn(() => Promise.resolve(true)),
events: {
on: jest.fn(),
off: jest.fn(),
},
beforePopState: jest.fn(() => null),
prefetch: jest.fn(() => null),
};
},
}));

// Mock authentication
const mockSession = {
expires: new Date(Date.now() + 2 * 86400).toISOString(),
user: { username: 'admin' },
};
jest.mock('next-auth/react', () => {
const originalModule = jest.requireActual('next-auth/react');
return {
__esModule: true,
...originalModule,
useSession: jest.fn(() => {
return { data: mockSession, status: 'authenticated' }; // return type is [] in v3 but changed to {} in v4
}),
};
});

jest.mock('next-auth/next', () => {
return {
__esModule: true,
getServerSession: jest.fn(() => {
return { data: mockSession, status: 'authenticated' };
}),
};
});

jest.mock('../pages/api/realms', () => {
return {
__esModule: true,
getAllRealms: jest.fn(() => Promise.resolve([CustomRealms, null])),
authOptions: {},
};
});

describe('realm table', () => {
it('loads all realms', () => {
render(<RealmLeftPanel realms={CustomRealms as any} onCancel={noop} onEditClick={noop} onViewClick={noop} />);
});

it('edit button disabled if realm is not approved', async () => {
render(
<RealmLeftPanel realms={CustomRealms as any} onCancel={noop} onEditClick={editFunction} onViewClick={noop} />,
);
const table = screen.getByRole('table');
const thirdRow = table.querySelector('tbody tr:nth-child(3)') as HTMLTableRowElement;
expect(thirdRow).toBeInTheDocument();
const actionCell = thirdRow.querySelector('td:nth-child(10)') as HTMLTableCellElement;
expect(actionCell).toBeInTheDocument();
const editButton = within(actionCell).getByRole('img', { name: 'Edit' });
fireEvent.click(editButton);
expect(editFunction).toHaveBeenCalledTimes(0);
});
});
4 changes: 2 additions & 2 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,12 @@
},
"devDependencies": {
"@octokit/types": "^12.1.1",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
"@types/deep-diff": "^1.0.5",
"@types/easy-soap-request": "^4.1.1",
"@types/jest": "^29.5.7",
"@types/jest": "^29.5.12",
"@types/jsonwebtoken": "^8.5.8",
"@types/jwk-to-pem": "^2.0.1",
"@types/jws": "^3.2.4",
Expand Down
16 changes: 8 additions & 8 deletions app/page-partials/my-dashboard/RealmTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,20 +94,19 @@ function RealmTable({ realms, onEditClick, onViewClick }: Props) {
title="Edit"
icon={faEdit}
onClick={() => {
if (props.row.original.approved === false) return;
onEditClick(String(props.row.getValue('id')));
}}
disabled={props.row.original.approved === false}
/>
</div>
),
},
];
const getStatus = (status?: string) => {
switch (status) {
case StatusEnum.APPLIED:
return 'Ready';
default:
return 'In Progress';
}
const getStatus = (status?: string, approved?: boolean) => {
if (status === StatusEnum.APPLIED) return 'Ready';
else if (approved === false) return 'Rejected';
else return 'In Progress';
};

return (
Expand All @@ -127,9 +126,10 @@ function RealmTable({ realms, onEditClick, onViewClick }: Props) {
secondTechnicalContact: r.secondTechnicalContactEmail
? `${r.secondTechnicalContactEmail} (${r.secondTechnicalContactIdirUserId})`
: '',
status: getStatus(r.status),
status: getStatus(r.status, r.approved),
rcChannel: r.rcChannel,
rcChannelOwnedBy: r.rcChannelOwnedBy,
approved: r.approved,
};
})}
columns={columns}
Expand Down
2 changes: 1 addition & 1 deletion app/pages/api/realms/[id].ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse<
},
});

if (!currentRequest) {
if (!currentRequest || currentRequest.approved === false) {
return res.status(400).json({ success: false, error: 'Invalid request' });
}

Expand Down
51 changes: 8 additions & 43 deletions app/pages/custom-realm-dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,16 @@ interface SelectOption {
label: string;
}

function CustomRealmDashboard({ defaultRealmRequests, alert }: Props) {
const [realmRequests, setRealmRequests] = useState<CustomRealmFormData[]>(defaultRealmRequests || []);
function CustomRealmDashboard({ alert }: Props) {
const [realmRequests, setRealmRequests] = useState<CustomRealmFormData[]>([]);
const [selectedRow, setSelectedRow] = useState<CustomRealmFormData | undefined>();
const [lastUpdateTime, setLastUpdateTime] = useState(new Date());
const { setModalConfig } = useContext(ModalContext);

useEffect(() => {
fetchRealms();
}, []);

const handleDeleteRequest = (id: number) => {
const handleConfirm = async () => {
const [, err] = await deleteRealmRequest(id);
Expand Down Expand Up @@ -118,11 +122,11 @@ function CustomRealmDashboard({ defaultRealmRequests, alert }: Props) {
content: `Realm request for ${realm?.realm} ${approval}.`,
});
const updatedRealms = realmRequests.map((realm) => {
if (realm.id === realmId) return { ...realm, approved: approving } as CustomRealmFormData;
if (realm.id === realmId) return { ...realm, approved: approving } as RealmProfile;
return realm;
});
setRealmRequests(updatedRealms);
setSelectedRow({ ...selectedRow, approved: approving } as CustomRealmFormData);
setSelectedRow({ ...selectedRow, approved: approving } as RealmProfile);
};
const statusVerb = approval === 'approved' ? 'Approve' : 'Decline';
setModalConfig({
Expand Down Expand Up @@ -268,42 +272,3 @@ function CustomRealmDashboard({ defaultRealmRequests, alert }: Props) {
}

export default withBottomAlert(CustomRealmDashboard);

interface ExtendedForm extends CustomRealmFormData {
createdAt: object;
updatedAt: object;
}

/**Fetch realm data with first page load */
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const session = await getServerSession(context.req, context.res, authOptions);
if (!session)
return {
props: { defaultRealmRequests: [] },
};

const username = session?.user?.idir_username || '';
const isAdmin = checkAdminRole(session?.user);

try {
const realms = await getAllRealms(username, isAdmin);
// Strip non-serializable dates
const formattedRealms = realms.map((realm: ExtendedForm) => {
const { createdAt, updatedAt, ...rest } = realm;
return rest;
});

return {
props: {
defaultRealmRequests: formattedRealms,
},
};
} catch (err) {
console.error(err);
return {
props: {
defaltRealmRequests: [],
},
};
}
};
3 changes: 2 additions & 1 deletion app/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"isolatedModules": true,
"jsx": "preserve",
"baseUrl": ".",
"incremental": true
"incremental": true,
"types": ["node", "jest", "@testing-library/jest-dom"]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "pages/**/*", "src/**/*"],
"exclude": ["node_modules"]
Expand Down
Loading

0 comments on commit ad6f829

Please sign in to comment.