diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0a5f6a790044..369e3ec2dd04 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -14318,8 +14318,18 @@ function isWorkspaceMemberLeavingWorkspaceRoom(report: OnyxEntry, isPoli return (report.visibility === CONST.REPORT.VISIBILITY.RESTRICTED || hasAccessPolicyExpenseChat) && isPolicyEmployee; } +/** + * Checks whether a list report field has at least one enabled value. + * A value without a matching `disabledOptions` entry is treated as enabled, because fields created outside NewDot + * can arrive with an empty `disabledOptions` array even when `values` has entries. Iterate over `values` rather than + * calling `disabledOptions.some(...)`, which would return false for those fields and hide them. + */ +function hasEnabledListValue(reportField: PolicyReportField): boolean { + return reportField.values.some((_, index) => !reportField.disabledOptions.at(index)); +} + function shouldHideSingleReportField(reportField: PolicyReportField) { - const hasEnableOption = reportField.type !== CONST.REPORT_FIELD_TYPES.LIST || reportField.disabledOptions.some((option) => !option); + const hasEnableOption = reportField.type !== CONST.REPORT_FIELD_TYPES.LIST || hasEnabledListValue(reportField); return isReportFieldOfTypeTitle(reportField) || !hasEnableOption; } @@ -14783,6 +14793,7 @@ export { hasReportBeenForwardedSinceLastSubmit, hasAutomatedExpensifyAccountIDs, hasEmptyReportsForPolicy, + hasEnabledListValue, hasHeldExpenses, hasIOUWaitingOnCurrentUserBankAccount, hasOnlyHeldExpenses, diff --git a/src/pages/workspace/fields/FieldsInitialValuePage.tsx b/src/pages/workspace/fields/FieldsInitialValuePage.tsx index 9564a512f4cb..b5ab10c08958 100644 --- a/src/pages/workspace/fields/FieldsInitialValuePage.tsx +++ b/src/pages/workspace/fields/FieldsInitialValuePage.tsx @@ -12,7 +12,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import {hasCircularReferences} from '@libs/Formula'; import Navigation from '@libs/Navigation/Navigation'; -import {getReportFieldKey} from '@libs/ReportUtils'; +import {getReportFieldKey, hasEnabledListValue} from '@libs/ReportUtils'; import {isRequiredFulfilled} from '@libs/ValidationUtils'; import {getReportFieldInitialValue, getReportFieldsForTarget, getUnsupportedReportFieldFormulaParts, isReportFieldTargetValid} from '@libs/WorkspaceReportFieldUtils'; @@ -49,7 +49,7 @@ function FieldsInitialValuePage({policy, policyID, reportFieldID, featureName, e const reportField = policy?.fieldList?.[getReportFieldKey(reportFieldID)] ?? null; const isInvoiceField = reportField?.target === CONST.REPORT_FIELD_TARGETS.INVOICE; - const availableListValuesLength = (reportField?.disabledOptions ?? []).filter((disabledListValue) => !disabledListValue).length; + const hasAvailableListValues = !!reportField && hasEnabledListValue(reportField); const currentInitialValue = getReportFieldInitialValue(reportField, translate); const [initialValue, setInitialValue] = useState(currentInitialValue); @@ -91,7 +91,7 @@ function FieldsInitialValuePage({policy, policyID, reportFieldID, featureName, e } } - if (reportField?.type === CONST.REPORT_FIELD_TYPES.LIST && availableListValuesLength > 0 && !isRequiredFulfilled(formInitialValue)) { + if (reportField?.type === CONST.REPORT_FIELD_TYPES.LIST && hasAvailableListValues && !isRequiredFulfilled(formInitialValue)) { errors[INPUT_IDS.INITIAL_VALUE] = translate( isInvoiceField ? 'workspace.invoiceFields.invoiceFieldInitialValueRequiredError' : 'workspace.reportFields.reportFieldInitialValueRequiredError', ); @@ -99,7 +99,7 @@ function FieldsInitialValuePage({policy, policyID, reportFieldID, featureName, e return errors; }, - [availableListValuesLength, expectedTarget, isInvoiceField, reportField?.name, reportField?.type, policy?.fieldList, translate], + [hasAvailableListValues, expectedTarget, isInvoiceField, reportField?.name, reportField?.type, policy?.fieldList, translate], ); if (!reportField || !isReportFieldTargetValid(reportField, expectedTarget)) { diff --git a/src/pages/workspace/fields/FieldsSettingsPage.tsx b/src/pages/workspace/fields/FieldsSettingsPage.tsx index df78ada9a13a..d30eabdf8825 100644 --- a/src/pages/workspace/fields/FieldsSettingsPage.tsx +++ b/src/pages/workspace/fields/FieldsSettingsPage.tsx @@ -12,7 +12,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import type {PolicyFeature} from '@libs/PolicyUtils'; -import {getReportFieldKey} from '@libs/ReportUtils'; +import {getReportFieldKey, hasEnabledListValue} from '@libs/ReportUtils'; import {getReportFieldInitialValue, getReportFieldTypeTranslationKey, isReportFieldImportedFromIntegration, isReportFieldTargetValid} from '@libs/WorkspaceReportFieldUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; @@ -75,7 +75,7 @@ function FieldsSettingsPage({ const isDateFieldType = reportField.type === CONST.REPORT_FIELD_TYPES.DATE; const isListFieldType = reportField.type === CONST.REPORT_FIELD_TYPES.LIST; - const isListFieldEmpty = isListFieldType && reportField.disabledOptions.filter((disabledListValue) => !disabledListValue).length <= 0; + const isListFieldEmpty = isListFieldType && !hasEnabledListValue(reportField); const listValues = Object.values(policy?.fieldList?.[reportFieldKey]?.values ?? {})?.sort(localeCompare); const deleteReportFieldAndHideModal = () => { diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 68878937b51c..0269ddfb229d 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -20906,6 +20906,7 @@ describe('ReportUtils', () => { it('should return false if list report field has some options enabled', () => { const reportField = createMock({ type: CONST.REPORT_FIELD_TYPES.LIST, + values: ['A', 'B', 'C'], disabledOptions: [false, true, false], }); expect(shouldHideSingleReportField(reportField)).toBe(false); @@ -20913,10 +20914,47 @@ describe('ReportUtils', () => { it('should return false if all items in list report field are disabled', () => { const reportField = createMock({ type: CONST.REPORT_FIELD_TYPES.LIST, + values: ['A', 'B', 'C'], disabledOptions: [true, true, true], }); expect(shouldHideSingleReportField(reportField)).toBe(true); }); + it('should return false if list report field has values but an empty disabledOptions array', () => { + // Given a list field not created in NewDot, where the backend sends an empty disabledOptions array + const reportField = createMock({ + type: CONST.REPORT_FIELD_TYPES.LIST, + values: ['A', 'B'], + disabledOptions: [], + }); + + // When checking whether the field should be hidden + // Then it is visible, because values without a disabledOptions entry are treated as enabled + expect(shouldHideSingleReportField(reportField)).toBe(false); + }); + it('should return false if disabledOptions is shorter than values and the missing entries are enabled', () => { + // Given a list field where only the first value has a disabledOptions entry + const reportField = createMock({ + type: CONST.REPORT_FIELD_TYPES.LIST, + values: ['A', 'B', 'C'], + disabledOptions: [true], + }); + + // When checking whether the field should be hidden + // Then it is visible, because the values without an entry are treated as enabled + expect(shouldHideSingleReportField(reportField)).toBe(false); + }); + it('should return true if every value of a list report field is explicitly disabled', () => { + // Given a list field where every value is disabled + const reportField = createMock({ + type: CONST.REPORT_FIELD_TYPES.LIST, + values: ['A', 'B'], + disabledOptions: [true, true], + }); + + // When checking whether the field should be hidden + // Then it is hidden, because no value can be selected + expect(shouldHideSingleReportField(reportField)).toBe(true); + }); }); describe('shouldDisplayReportFields', () => { it('returns true for an invoice when the receiver policy does not include the invoice fields feature flag', () => {