Skip to content

Commit

Permalink
Merge branch 'integrationTesting' into B-22559-Add-Filtering-To-Rejec…
Browse files Browse the repository at this point in the history
…ted-Tab-INT
  • Loading branch information
KonstanceH authored Mar 4, 2025
2 parents 8f4f9d6 + 5a3dac0 commit db4619b
Show file tree
Hide file tree
Showing 4 changed files with 265 additions and 20 deletions.
23 changes: 23 additions & 0 deletions src/constants/adminApp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* eslint-disable import/prefer-default-export */

// Defines Export headers for Office User use to match UI columns
export const OFFICE_USER_EXPORT_HEADERS = [
{ header: 'Id', key: 'id' },
{ header: 'Email', key: 'email' },
{ header: 'Telephone', key: 'telephone' },
{ header: 'First Name', key: 'firstName' },
{ header: 'Last Name', key: 'lastName' },
{ header: 'Middle Initials', key: 'middleInitials' },
{ header: 'Primary Transportation Office', key: 'primaryTransportationOffice' },
{ header: 'Transportation Office Assignments', key: 'transportationOfficeAssignments' },
{ header: 'User Id', key: 'userId' },
{ header: 'Active', key: 'active' },
{ header: 'EDIPI', key: 'edipi' },
{ header: 'Other Unique ID', key: 'otherUniqueId' },
{ header: 'Rejection Reason', key: 'rejectionReason' },
{ header: 'Status', key: 'status' },
{ header: 'Roles', key: 'roles' },
{ header: 'Privileges', key: 'privileges' },
{ header: 'Created At', key: 'createdAt' },
{ header: 'Updated At', key: 'updatedAt' },
];
69 changes: 49 additions & 20 deletions src/pages/Admin/OfficeUsers/OfficeUserList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,65 @@ import {
TopToolbar,
useListController,
downloadCSV,
useDataProvider,
} from 'react-admin';
import * as jsonexport from 'jsonexport/dist';

import { OFFICE_USER_EXPORT_HEADERS } from 'constants/adminApp';
import ImportOfficeUserButton from 'components/Admin/ImportOfficeUserButton';
import AdminPagination from 'scenes/SystemAdmin/shared/AdminPagination';

// Custom exporter to flatten out role and privilege types
const exporter = (data) => {
const usersForExport = data.map((rowData) => {
const { roles, privileges, ...otherRowData } = rowData;

const flattenedRoles = roles ? roles.map((role) => role.roleType).join(',') : '';
const flattenedPrivileges = privileges ? privileges.map((privilege) => privilege.privilegeType).join(',') : '';

return {
...otherRowData,
roles: flattenedRoles,
privileges: flattenedPrivileges,
};
});

// convert data to csv and download
jsonexport(usersForExport, {}, (err, csv) => {
if (err) throw err;
downloadCSV(csv, 'office-users');
// Function to transform rowData based on headers
const transformRowData = (rowData, officeObjects) => {
const transformedData = {};
OFFICE_USER_EXPORT_HEADERS.forEach(({ key, header }) => {
switch (key) {
case 'roles':
transformedData[header] = rowData[key] ? rowData[key].map((role) => role.roleType).join(',') : '';
break;
case 'privileges':
transformedData[header] = rowData[key]
? rowData[key].map((privilege) => privilege.privilegeType).join(',')
: '';
break;
case 'primaryTransportationOffice':
transformedData[header] = officeObjects[rowData.transportationOfficeId]?.name || '';
break;
default:
transformedData[header] = rowData[key] !== undefined ? rowData[key] : '';
break;
}
});
return transformedData;
};

// Overriding the default toolbar to add import button
// Overriding the default toolbar for customizations
const ListActions = () => {
const { total, resource, sort, filterValues } = useListController();
const dataProvider = useDataProvider();
const exporter = async (data) => {
// Fetch the offices asynchronously
const officesResponse = await dataProvider.getMany('offices');
const officeObjects = {};

// Map office data into officeObjects using the office id as the key
officesResponse.data.forEach((office) => {
if (!officeObjects[`${office.id}`]) {
officeObjects[`${office.id}`] = office;
}
});

// Process the user data using the transformation function
const usersForExport = data.map((rowData) => transformRowData(rowData, officeObjects));

// Extract header names for jsonexport
const headersMap = OFFICE_USER_EXPORT_HEADERS.map((h) => h.header);
// Convert the data to CSV and trigger the download
jsonexport(usersForExport, { headersMap }, (err, csv) => {
if (err) throw err;
downloadCSV(csv, 'office-users');
});
};

return (
<TopToolbar>
Expand Down Expand Up @@ -79,6 +107,7 @@ const OfficeUserList = () => (
<Datagrid bulkActionButtons={false} rowClick="show">
<TextField source="id" />
<TextField source="email" />
<TextField source="telephone" />
<TextField source="firstName" />
<TextField source="lastName" />
<ReferenceField
Expand Down
101 changes: 101 additions & 0 deletions src/shared/Inputs/YesNoBoolean.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import YesNoBoolean from './YesNoBoolean';
import { renderWithProviders } from 'testUtils';

describe('YesNoBoolean Component', () => {
const defaultProps = {
onChange: jest.fn(),
value: false,
};

beforeEach(() => {
jest.clearAllMocks();
});

it('renders yes and no radio buttons', () => {
renderWithProviders(<YesNoBoolean {...defaultProps} />);

expect(screen.getByLabelText('Yes')).toBeInTheDocument();
expect(screen.getByLabelText('No')).toBeInTheDocument();
});

it('should reflect initial value prop correctly when true', () => {
renderWithProviders(<YesNoBoolean {...defaultProps} value={true} />);

expect(screen.getByLabelText('Yes')).toBeChecked();
expect(screen.getByLabelText('No')).not.toBeChecked();
});

it('should reflects initial value prop correctly when false', () => {
renderWithProviders(<YesNoBoolean {...defaultProps} value={false} />);

expect(screen.getByLabelText('Yes')).not.toBeChecked();
expect(screen.getByLabelText('No')).toBeChecked();
});

it('handles onChange when selecting Yes', () => {
renderWithProviders(<YesNoBoolean {...defaultProps} value={false} />);

const yesRadio = screen.getByLabelText('Yes');
fireEvent.click(yesRadio);

expect(defaultProps.onChange).toHaveBeenCalledWith(true);
expect(defaultProps.onChange).toHaveBeenCalledTimes(1);
});

it('handles onChange when selecting No', () => {
render(<YesNoBoolean {...defaultProps} value={true} />);

const noRadio = screen.getByLabelText('No');
fireEvent.click(noRadio);

expect(defaultProps.onChange).toHaveBeenCalledWith(false);
expect(defaultProps.onChange).toHaveBeenCalledTimes(1);
});

it('works with input prop object', () => {
const inputProps = {
input: {
value: 'true', // String value should be converted to boolean
onChange: jest.fn(),
},
};

renderWithProviders(<YesNoBoolean {...inputProps} />);

expect(screen.getByLabelText('Yes')).toBeChecked();
expect(screen.getByLabelText('No')).not.toBeChecked();

fireEvent.click(screen.getByLabelText('No'));
expect(inputProps.input.onChange).toHaveBeenCalledWith(false);
});

it('applies correct classes to inputs and labels', () => {
renderWithProviders(<YesNoBoolean {...defaultProps} />);

const radioInputs = screen.getAllByRole('radio');
const labels = screen.getAllByText(/Yes|No/);

radioInputs.forEach((input) => {
expect(input).toHaveClass('usa-radio__input', 'inline_radio');
});

labels.forEach((label) => {
expect(label).toHaveClass('usa-radio__label', 'inline_radio');
});
});

it('has unique IDs for each radio button', () => {
renderWithProviders(<YesNoBoolean {...defaultProps} />);
const yesRadio = screen.getByLabelText('Yes');
const noRadio = screen.getByLabelText('No');

const yesId = yesRadio.id;
const noId = noRadio.id;

expect(yesId).not.toBe(noId);
expect(yesId).toMatch(/^yes_no_/);
expect(noId).toMatch(/^yes_no_/);
});
});
92 changes: 92 additions & 0 deletions src/shared/RadioButton/index.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import RadioButton from '.';
import { renderWithProviders } from 'testUtils';

describe('RadioButton Component', () => {
const defaultProps = {
name: 'test-radio-btn',
label: 'Test Label',
onChange: jest.fn(),
value: 'test-value',
checked: false,
};

// Reset mocks before each test
beforeEach(() => {
jest.clearAllMocks();
});

it('renders radio button with label', () => {
renderWithProviders(<RadioButton {...defaultProps} />);

const radioInput = screen.getByRole('radio');
const label = screen.getByText('Test Label');

expect(radioInput).toBeInTheDocument();
expect(label).toBeInTheDocument();
});

it('applies correct props to input element', () => {
renderWithProviders(<RadioButton {...defaultProps} />);

const radioInput = screen.getByRole('radio');

expect(radioInput).toHaveAttribute('name', 'test-radio-btn');
expect(radioInput).toHaveAttribute('value', 'test-value');
expect(radioInput).not.toBeChecked();
});

it('reflects checked state when true', () => {
renderWithProviders(<RadioButton {...defaultProps} checked={true} />);

const radioInput = screen.getByRole('radio');
expect(radioInput).toBeChecked();
});

it('calls onChange handler when clicked', () => {
renderWithProviders(<RadioButton {...defaultProps} />);

const radioInput = screen.getByRole('radio');
fireEvent.click(radioInput);

expect(defaultProps.onChange).toHaveBeenCalledTimes(1);
});

it('applies custom input className', () => {
const inputClassName = 'custom-input-class';
renderWithProviders(<RadioButton {...defaultProps} inputClassName={inputClassName} />);

const radioInput = screen.getByRole('radio');
expect(radioInput).toHaveClass(inputClassName);
});

it('applies custom label className', () => {
const labelClassName = 'custom-label-class';
renderWithProviders(<RadioButton {...defaultProps} labelClassName={labelClassName} />);

const label = screen.getByText('Test Label');
expect(label).toHaveClass(labelClassName);
});

it('uses provided testId for testing', () => {
const testId = 'custom-test-id';
render(<RadioButton {...defaultProps} testId={testId} />);

const radioInput = screen.getByTestId(testId);
expect(radioInput).toBeInTheDocument();
});

it('associates label with input via matching ids', () => {
renderWithProviders(<RadioButton {...defaultProps} />);

const radioInput = screen.getByRole('radio');
const label = screen.getByText('Test Label');

const inputId = radioInput.getAttribute('id');
const labelFor = label.getAttribute('for');

expect(inputId).toBe(labelFor);
expect(inputId).toBeTruthy();
});
});

0 comments on commit db4619b

Please sign in to comment.