Skip to content

Commit

Permalink
Merge pull request Expensify#30068 from wlegolas/bugfix/issue-29614
Browse files Browse the repository at this point in the history
Fix bug to show the secondary avatar when the repost is is a policy expense chat
  • Loading branch information
techievivek authored Oct 25, 2023
2 parents 0c2578e + df0bc88 commit 2e36e8c
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 2 deletions.
5 changes: 4 additions & 1 deletion src/pages/home/report/ReportActionItemSingle.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,10 @@ function ReportActionItemSingle(props) {
id: secondaryAccountId,
};
} else if (!isWorkspaceActor) {
secondaryAvatar = ReportUtils.getIcons(props.report, {})[props.report.isOwnPolicyExpenseChat ? 0 : 1];
const avatarIconIndex = props.report.isOwnPolicyExpenseChat || ReportUtils.isPolicyExpenseChat(props.report) ? 0 : 1;
const reportIcons = ReportUtils.getIcons(props.report, {});

secondaryAvatar = reportIcons[avatarIconIndex];
}
const icon = {source: avatarSource, type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR, name: primaryDisplayName, id: isWorkspaceActor ? '' : actorAccountID};

Expand Down
86 changes: 86 additions & 0 deletions tests/unit/ReportActionItemSingleTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import Onyx from 'react-native-onyx';
import {cleanup, screen} from '@testing-library/react-native';
import * as LHNTestUtils from '../utils/LHNTestUtils';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';
import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates';

const ONYXKEYS = {
PERSONAL_DETAILS_LIST: 'personalDetailsList',
IS_LOADING_REPORT_DATA: 'isLoadingReportData',
COLLECTION: {
REPORT_ACTIONS: 'reportActions_',
POLICY: 'policy_',
},
NETWORK: 'network',
};

describe('ReportActionItemSingle', () => {
beforeAll(() =>
Onyx.init({
keys: ONYXKEYS,
registerStorageEventListener: () => {},
safeEvictionKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
}),
);

beforeEach(() => {
// Wrap Onyx each onyx action with waitForBatchedUpdates
wrapOnyxWithWaitForBatchedUpdates(Onyx);
// Initialize the network key for OfflineWithFeedback
return Onyx.merge(ONYXKEYS.NETWORK, {isOffline: false});
});

// Clear out Onyx after each test so that each test starts with a clean slate
afterEach(() => {
cleanup();
Onyx.clear();
});

describe('when the Report is a policy expense chat', () => {
describe('and the property "shouldShowSubscriptAvatar" is true', () => {
const shouldShowSubscriptAvatar = true;
const fakeReport = LHNTestUtils.getFakeReportWithPolicy([1, 2]);
const fakeReportAction = LHNTestUtils.getFakeAdvancedReportAction();
const fakePolicy = LHNTestUtils.getFakePolicy(fakeReport.policyID);
const fakePersonalDetails = {
[fakeReportAction.actorAccountID]: {
accountID: fakeReportAction.actorAccountID,
login: '[email protected]',
displayName: 'Email One',
avatar: 'https://example.com/avatar.png',
firstName: 'One',
},
};

beforeEach(() => {
LHNTestUtils.getDefaultRenderedReportActionItemSingle(shouldShowSubscriptAvatar, fakeReport, fakeReportAction);
});

function setup() {
return waitForBatchedUpdates().then(() =>
Onyx.multiSet({
[ONYXKEYS.PERSONAL_DETAILS_LIST]: fakePersonalDetails,
[ONYXKEYS.IS_LOADING_REPORT_DATA]: false,
[`${ONYXKEYS.COLLECTION.POLICY}${fakeReport.policyID}`]: fakePolicy,
}),
);
}

it('renders secondary Avatar properly', () => {
const expectedSecondaryIconTestId = 'SvgDefaultAvatar_w Icon';

return setup().then(() => {
expect(screen.getByTestId(expectedSecondaryIconTestId)).toBeDefined();
});
});

it('renders Person information', () => {
const [expectedPerson] = fakeReportAction.person;

return setup().then(() => {
expect(screen.getByText(expectedPerson.text)).toBeDefined();
});
});
});
});
});
155 changes: 154 additions & 1 deletion tests/utils/LHNTestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,13 @@ import ComposeProviders from '../../src/components/ComposeProviders';
import OnyxProvider from '../../src/components/OnyxProvider';
import {LocaleContextProvider} from '../../src/components/LocaleContextProvider';
import SidebarLinksData from '../../src/pages/home/sidebar/SidebarLinksData';
import ReportActionItemSingle from '../../src/pages/home/report/ReportActionItemSingle';
import {EnvironmentProvider} from '../../src/components/withEnvironment';
import {CurrentReportIDContextProvider} from '../../src/components/withCurrentReportID';
import CONST from '../../src/CONST';
import DateUtils from '../../src/libs/DateUtils';
import reportPropTypes from '../../src/pages/reportPropTypes';
import reportActionPropTypes from '../../src/pages/home/report/reportActionPropTypes';

// we have to mock `useIsFocused` because it's used in the SidebarLinks component
const mockedNavigate = jest.fn();
Expand Down Expand Up @@ -167,6 +170,63 @@ function getAdvancedFakeReport(isArchived, isUserCreatedPolicyRoom, hasAddWorksp
};
}

/**
* @param {Number[]} [participantAccountIDs]
* @param {Number} [millisecondsInThePast] the number of milliseconds in the past for the last message timestamp (to order reports by most recent messages)
* @param {boolean} [isUnread]
* @returns {Object}
*/
function getFakeReportWithPolicy(participantAccountIDs = [1, 2], millisecondsInThePast = 0, isUnread = false) {
return {
...getFakeReport(participantAccountIDs, millisecondsInThePast, isUnread),
type: CONST.REPORT.TYPE.CHAT,
chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT,
policyID: '08CE60F05A5D86E1',
oldPolicyName: '',
isOwnPolicyExpenseChat: false,
ownerAccountID: participantAccountIDs[0],
};
}

/**
* @param {Number} [id]
* @param {String} [name]
* @returns {Object}
*/
function getFakePolicy(id = 1, name = 'Workspace-Test-001') {
return {
id,
name,
isFromFullPolicy: false,
role: 'admin',
type: 'free',
owner: '[email protected]',
outputCurrency: 'BRL',
avatar: '',
employeeList: [],
isPolicyExpenseChatEnabled: true,
areChatRoomsEnabled: true,
lastModified: 1697323926777105,
autoReporting: true,
autoReportingFrequency: 'immediate',
defaultBillable: false,
disabledFields: {defaultBillable: true, reimbursable: false},
};
}

/**
* @param {String} actionName
* @param {String} actor
* @param {Number} millisecondsInThePast the number of milliseconds in the past for the last message timestamp (to order reports by most recent messages)
* @returns {Object}
*/
function getFakeAdvancedReportAction(actionName = 'IOU', actor = '[email protected]', millisecondsInThePast = 0) {
return {
...getFakeReportAction(actor, millisecondsInThePast),
actionName,
};
}

/**
* @param {String} [currentReportID]
*/
Expand Down Expand Up @@ -218,4 +278,97 @@ MockedSidebarLinks.defaultProps = {
currentReportID: '',
};

export {fakePersonalDetails, getDefaultRenderedSidebarLinks, getAdvancedFakeReport, getFakeReport, getFakeReportAction, MockedSidebarLinks};
/**
* @param {React.ReactElement} component
*/
function internalRender(component) {
// A try-catch block needs to be added to the rendering so that any errors that happen while the component
// renders are caught and logged to the console. Without the try-catch block, Jest might only report the error
// as "The above error occurred in your component", without providing specific details. By using a try-catch block,
// any errors are caught and logged, allowing you to identify the exact error that might be causing a rendering issue
// when developing tests.

try {
render(component);
} catch (error) {
console.error(error);
}
}

/**
* @param {Boolean} [shouldShowSubscriptAvatar]
* @param {Object} [report]
* @param {Object} [reportAction]
*/
function getDefaultRenderedReportActionItemSingle(shouldShowSubscriptAvatar = true, report = null, reportAction = null) {
const currentReport = report || getFakeReport();
const currentReportAction = reportAction || getFakeAdvancedReportAction();

internalRender(
<MockedReportActionItemSingle
shouldShowSubscriptAvatar={shouldShowSubscriptAvatar}
report={currentReport}
reportAction={currentReportAction}
/>,
);
}

/**
* @param {Boolean} shouldShowSubscriptAvatar
* @param {Object} report
* @param {Object} reportAction
* @returns {JSX.Element}
*/
function MockedReportActionItemSingle({shouldShowSubscriptAvatar, report, reportAction}) {
const personalDetailsList = {
[reportAction.actorAccountID]: {
accountID: reportAction.actorAccountID,
login: '[email protected]',
displayName: 'Email One',
avatar: 'https://example.com/avatar.png',
firstName: 'One',
},
};

return (
<ComposeProviders components={[OnyxProvider, LocaleContextProvider, EnvironmentProvider, CurrentReportIDContextProvider]}>
<ReportActionItemSingle
action={reportAction}
report={report}
personalDetailsList={personalDetailsList}
wrapperStyles={[{display: 'inline'}]}
showHeader
shouldShowSubscriptAvatar={shouldShowSubscriptAvatar}
hasBeenFlagged={false}
iouReport={undefined}
isHovered={false}
/>
</ComposeProviders>
);
}

MockedReportActionItemSingle.propTypes = {
shouldShowSubscriptAvatar: PropTypes.bool,
report: reportPropTypes,
reportAction: PropTypes.shape(reportActionPropTypes),
};

MockedReportActionItemSingle.defaultProps = {
shouldShowSubscriptAvatar: true,
report: null,
reportAction: null,
};

export {
fakePersonalDetails,
getDefaultRenderedSidebarLinks,
getAdvancedFakeReport,
getFakeReport,
getFakeReportAction,
MockedSidebarLinks,
getDefaultRenderedReportActionItemSingle,
MockedReportActionItemSingle,
getFakeReportWithPolicy,
getFakePolicy,
getFakeAdvancedReportAction,
};

0 comments on commit 2e36e8c

Please sign in to comment.