Skip to content
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/* eslint-disable import/no-extraneous-dependencies, ordered-imports/ordered-imports */
import {
render,
screen,
} from '@testing-library/react'

import type { Assignment } from '../../models'
Expand Down Expand Up @@ -52,7 +53,7 @@ jest.mock('../../utils', () => ({
}))

jest.mock('../../constants', () => ({
BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED: false,
BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED: true,
}))

describe('PaymentFormModal', () => {
Expand Down Expand Up @@ -92,4 +93,24 @@ describe('PaymentFormModal', () => {
preventOpenOnFocus: true,
}))
})

it('shows billing account id when enabled', () => {
render(
<PaymentFormModal
billingAccountId={80001063}
billingAccountMarkup={0.25}
engagementName='Engagement'
member={member}
onCancel={jest.fn()}
onConfirm={jest.fn()}
open
projectName='Project'
/>,
)

expect(screen.getByText('Billing Account'))
.toBeTruthy()
expect(screen.getByText('80001063'))
.toBeTruthy()
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jest.mock('../../hooks', () => ({
}))

jest.mock('../../constants', () => ({
BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED: false,
BILLING_ACCOUNT_MEMBER_PAYMENT_DETAILS_ENABLED: true,
}))

jest.mock('~/libs/ui', () => ({
Expand Down Expand Up @@ -48,7 +48,7 @@ describe('PaymentHistoryModal', () => {
mockUseFetchAssignmentPayments.mockReset()
})

it('renders clickable remarks links and the payment creator handle', async () => {
it('renders clickable remarks links, the payment creator handle, and fee details', async () => {
mockUseFetchAssignmentPayments.mockReturnValue({
error: undefined,
isLoading: false,
Expand Down Expand Up @@ -96,9 +96,9 @@ describe('PaymentHistoryModal', () => {
.toBeTruthy()
expect(screen.getByText('payment.manager'))
.toBeTruthy()
expect(screen.queryByText('Fee:'))
.toBeNull()
expect(screen.queryByText('$18.60'))
.toBeNull()
expect(screen.getByText('Fee:'))
.toBeTruthy()
expect(screen.getByText('$18.60'))
.toBeTruthy()
})
})
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable import/no-extraneous-dependencies, ordered-imports/ordered-imports */
import {
fireEvent,
render,
screen,
} from '@testing-library/react'
Expand Down Expand Up @@ -34,10 +35,16 @@ jest.mock('../BillingAccountLineItemsModal', () => ({
),
}))

jest.mock('../../constants', () => ({
BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED: false,
BILLING_ACCOUNT_DETAILS_MODAL_ENABLED: false,
}))
jest.mock('../../constants', () => {
const constants = {
BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED: true,
BILLING_ACCOUNT_DETAILS_MODAL_ENABLED: true,
}

Object.assign(globalThis, { mockWorkConstants: constants })

return constants
})

jest.mock('~/libs/ui', () => ({
IconOutline: {
Expand All @@ -55,6 +62,22 @@ const mockedUseFetchProjectBillingAccount = useFetchProjectBillingAccount as jes
typeof useFetchProjectBillingAccount
>

interface MockWorkConstants {
BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED: boolean
BILLING_ACCOUNT_DETAILS_MODAL_ENABLED: boolean
}

/**
* Returns the mutable constants mock installed for this spec.
*
* @returns Work app billing feature flag state used by mocked constants.
*/
function getMockWorkConstants(): MockWorkConstants {
return (globalThis as unknown as {
mockWorkConstants: MockWorkConstants
}).mockWorkConstants
}

const billingAccountDetails: BillingAccountDetails = {
budget: 1000,
consumedAmounts: [],
Expand Down Expand Up @@ -99,6 +122,9 @@ describe('ProjectBillingAccountExpiredNotice', () => {
beforeEach(() => {
jest.clearAllMocks()

getMockWorkConstants().BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED = true
getMockWorkConstants().BILLING_ACCOUNT_DETAILS_MODAL_ENABLED = true

mockedUseFetchBillingAccounts.mockReturnValue({
billingAccounts: [],
error: undefined,
Expand All @@ -124,6 +150,9 @@ describe('ProjectBillingAccountExpiredNotice', () => {
})

it('hides billing account budget and line-item details while billing details are disabled', () => {
getMockWorkConstants().BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED = false
getMockWorkConstants().BILLING_ACCOUNT_DETAILS_MODAL_ENABLED = false

renderNotice()

expect(screen.getByText(/Billing account:/))
Expand All @@ -147,18 +176,19 @@ describe('ProjectBillingAccountExpiredNotice', () => {
.toBeTruthy()
expect(screen.getByText(/80001063/))
.toBeTruthy()
expect(screen.queryByText('$1,025 / $1,000 spent'))
.toBeNull()
expect(screen.queryByText(/The billing account for this project has insufficient remaining funds,/))
.toBeNull()
expect(screen.queryByRole('link', { name: 'click here to update' }))
.toBeNull()
expect(screen.queryByRole('button', {
expect(screen.getByText('$1,025 / $1,000 spent'))
.toBeTruthy()
expect(screen.getByText(/The billing account for this project has insufficient remaining funds,/))
.toBeTruthy()
expect(screen.getByRole('link', { name: 'click here to update' }))
.toBeTruthy()
fireEvent.click(screen.getByRole('button', {
name: 'View billing account details',
}))
.toBeNull()
expect(screen.queryByRole('dialog'))
.toBeNull()

expect(screen.getByRole('dialog')
.textContent)
.toContain('Billing account details for 80001063')
})

it('shows member payments remaining instead of spent and total budget for copilots', () => {
Expand All @@ -185,6 +215,8 @@ describe('ProjectBillingAccountExpiredNotice', () => {
isLoading: false,
})

getMockWorkConstants().BILLING_ACCOUNT_DETAILS_MODAL_ENABLED = true

renderNotice({
...defaultContextValue,
isCopilot: true,
Expand All @@ -197,7 +229,8 @@ describe('ProjectBillingAccountExpiredNotice', () => {
.toBeNull()
})

it('hides member payment details and billing account modal access from copilots when disabled', () => {
it('hides the inline member payment balance but keeps billing account modal access for copilots '
+ 'when disabled', () => {
mockedUseFetchBillingAccountDetails.mockReturnValue({
billingAccountDetails: {
...billingAccountDetails,
Expand All @@ -210,6 +243,8 @@ describe('ProjectBillingAccountExpiredNotice', () => {
isLoading: false,
})

getMockWorkConstants().BILLING_ACCOUNT_DETAILS_MODAL_ENABLED = true

renderNotice({
...defaultContextValue,
isCopilot: true,
Expand All @@ -220,11 +255,14 @@ describe('ProjectBillingAccountExpiredNotice', () => {
.toBeNull()
expect(screen.queryByText('$750 / $1,000 spent'))
.toBeNull()
expect(screen.queryByRole('button', {
fireEvent.click(screen.getByRole('button', {
name: 'View billing account details',
}))
.toBeNull()

expect(screen.getByRole('dialog')
.textContent)
.toContain('Billing account details for 80001063')
expect(mockedUseFetchBillingAccountDetails)
.toHaveBeenCalledWith(undefined)
.toHaveBeenCalledWith('80001063')
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,13 @@ function isRestrictedCopilot(workAppContext: WorkAppContextModel): boolean {
}

/**
* Resolves whether the current user may see project payment details.
* Resolves whether the current user may see inline project payment amounts.
*
* @param workAppContext Current work app user context.
* @param displayMemberPaymentDetailsToCopilots Project-level copilot display flag.
* @returns `true` when payment amounts and the line-item modal may be shown.
* @returns `true` when payment amounts may be shown in the notice summary.
*/
function canShowProjectPaymentDetails(
function canShowProjectPaymentAmounts(
workAppContext: WorkAppContextModel,
displayMemberPaymentDetailsToCopilots: boolean | undefined,
): boolean {
Expand All @@ -124,20 +124,20 @@ function canShowProjectPaymentDetails(
* Resolves whether the copilot-safe member-payment balance should be shown.
*
* @param workAppContext Current work app user context.
* @param showPaymentDetails Whether payment details are enabled for this project.
* @param showPaymentAmounts Whether payment amounts are enabled for this project.
* @returns `true` when the user should see member payments remaining.
*/
function canShowMemberPaymentsRemaining(
workAppContext: WorkAppContextModel,
showPaymentDetails: boolean,
showPaymentAmounts: boolean,
): boolean {
return showPaymentDetails && isRestrictedCopilot(workAppContext)
return showPaymentAmounts && isRestrictedCopilot(workAppContext)
}

interface VisibleBudgetInfoParams {
copilotBudgetInfo: CopilotMemberPaymentsBudgetInfo | undefined
showMemberPaymentsRemaining: boolean
showPaymentDetails: boolean
showPaymentAmounts: boolean
standardBudgetInfo: BillingAccountBudgetInfo | undefined
}

Expand All @@ -150,7 +150,7 @@ interface VisibleBudgetInfoParams {
function getVisibleBudgetInfo(
params: VisibleBudgetInfoParams,
): BillingAccountBudgetInfo | undefined {
if (!params.showPaymentDetails) {
if (!params.showPaymentAmounts) {
return undefined
}

Expand Down Expand Up @@ -198,10 +198,10 @@ function renderBudgetDisplayContent(
}

/**
* Hides budget-derived billing account notices while budget display is disabled.
* Hides budget-derived billing account notices when budget display is disabled.
*
* @param billingAccountIssue The billing account issue resolved for the project.
* @returns The issue to display, or `undefined` when the temporary hide applies.
* @returns The issue to display, or `undefined` when budget display is disabled.
*/
function getVisibleBillingAccountIssue(
billingAccountIssue: BillingAccountIssue,
Expand Down Expand Up @@ -260,7 +260,7 @@ const BillingAccountDetailsContent: FC<BillingAccountDetailsContentProps> = (
}

/**
* Renders the temporarily enabled/disabled line-item modal.
* Renders the gated billing-account line-item modal.
*
* @param billingAccountDetails Billing account detail payload, if loaded.
* @param isModalOpen Whether the details modal has been requested.
Expand Down Expand Up @@ -340,14 +340,15 @@ export const ProjectBillingAccountExpiredNotice: FC<ProjectBillingAccountExpired
) => {
const [isModalOpen, setIsModalOpen] = useState<boolean>(false)
const workAppContext: WorkAppContextModel = useContext(WorkAppContext)
const showPaymentDetails: boolean = canShowProjectPaymentDetails(
const showPaymentAmounts: boolean = canShowProjectPaymentAmounts(
workAppContext,
props.displayMemberPaymentDetailsToCopilots,
)
const showMemberPaymentsRemaining: boolean = canShowMemberPaymentsRemaining(
workAppContext,
showPaymentDetails,
showPaymentAmounts,
)
const showMemberPaymentsRemainingInModal: boolean = isRestrictedCopilot(workAppContext)

const projectBillingAccountResult: UseFetchProjectBillingAccountResult = useFetchProjectBillingAccount(
props.projectId,
Expand All @@ -356,9 +357,9 @@ export const ProjectBillingAccountExpiredNotice: FC<ProjectBillingAccountExpired
const billingAccount = projectBillingAccountResult.billingAccount
const normalizedBillingAccountId = normalizeOptionalString(props.billingAccountId)
|| normalizeOptionalString(billingAccount?.id)
const shouldFetchBillingAccountDetails = (BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED && showPaymentDetails)
const shouldFetchBillingAccountDetails = (BILLING_ACCOUNT_BUDGET_DISPLAY_ENABLED && showPaymentAmounts)
|| showMemberPaymentsRemaining
|| (BILLING_ACCOUNT_DETAILS_MODAL_ENABLED && isModalOpen && showPaymentDetails)
|| (BILLING_ACCOUNT_DETAILS_MODAL_ENABLED && isModalOpen)
const billingAccountDetailsResult: UseFetchBillingAccountDetailsResult = useFetchBillingAccountDetails(
shouldFetchBillingAccountDetails
? normalizedBillingAccountId
Expand Down Expand Up @@ -408,7 +409,7 @@ export const ProjectBillingAccountExpiredNotice: FC<ProjectBillingAccountExpired
const budgetInfo = getVisibleBudgetInfo({
copilotBudgetInfo,
showMemberPaymentsRemaining,
showPaymentDetails,
showPaymentAmounts,
standardBudgetInfo,
})
const budgetDisplayContent = renderBudgetDisplayContent(
Expand All @@ -433,7 +434,7 @@ export const ProjectBillingAccountExpiredNotice: FC<ProjectBillingAccountExpired
budgetDisplayContent={budgetDisplayContent}
budgetInfo={budgetInfo}
onOpenModal={handleOpenModal}
showDetailsButton={BILLING_ACCOUNT_DETAILS_MODAL_ENABLED && showPaymentDetails}
showDetailsButton={BILLING_ACCOUNT_DETAILS_MODAL_ENABLED}
/>
)
: undefined
Expand All @@ -442,7 +443,7 @@ export const ProjectBillingAccountExpiredNotice: FC<ProjectBillingAccountExpired
isModalOpen,
handleCloseModal,
props.projectId,
showMemberPaymentsRemaining,
showMemberPaymentsRemainingInModal,
)

return renderBillingAccountContent({
Expand Down
Loading
Loading