diff --git a/src/languages/de.ts b/src/languages/de.ts index 8f411c70e681..0c4ac086349b 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -56,6 +56,7 @@ import type { StepCounterParams, SyncStageNameConnectionsParams, UnshareParams, + UnsupportedFormulaValueErrorParams, UpdatedBudgetParams, UpdatedPolicyApprovalRuleParams, UpdatedPolicyCategoryMaxAmountNoReceiptParams, @@ -5724,6 +5725,7 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU reportFieldNameRequiredError: 'Bitte gib einen Berichtsfeldnamen ein', reportFieldTypeRequiredError: 'Bitte wähle einen Berichtsfeldtyp aus', circularReferenceError: 'Dieses Feld kann nicht auf sich selbst verweisen. Bitte aktualisieren.', + unsupportedFormulaValueError: ({value}: UnsupportedFormulaValueErrorParams) => `Formelfeld ${value} nicht erkannt`, reportFieldInitialValueRequiredError: 'Bitte wähle einen Anfangswert für ein Berichtsfeld aus', genericFailureMessage: 'Beim Aktualisieren des Berichtfelds ist ein Fehler aufgetreten. Bitte versuche es erneut.', }, diff --git a/src/languages/en.ts b/src/languages/en.ts index a5867646e848..bb998a1c73f1 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -44,6 +44,7 @@ import type { StepCounterParams, SyncStageNameConnectionsParams, UnshareParams, + UnsupportedFormulaValueErrorParams, UpdatedBudgetParams, UpdatedPolicyApprovalRuleParams, UpdatedPolicyCategoryMaxAmountNoReceiptParams, @@ -5708,6 +5709,7 @@ const translations = { reportFieldNameRequiredError: 'Please enter a report field name', reportFieldTypeRequiredError: 'Please choose a report field type', circularReferenceError: "This field can't refer to itself. Please update.", + unsupportedFormulaValueError: ({value}: UnsupportedFormulaValueErrorParams) => `Formula field ${value} not recognized`, reportFieldInitialValueRequiredError: 'Please choose a report field initial value', genericFailureMessage: 'An error occurred while updating the report field. Please try again.', }, diff --git a/src/languages/es.ts b/src/languages/es.ts index ae51e8001082..17f1f7db28f7 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -3,7 +3,7 @@ import dedent from '@libs/StringUtils/dedent'; import CONST from '@src/CONST'; import type {OriginalMessageSettlementAccountLocked, PersonalRulesModifiedFields, PolicyRulesModifiedFields} from '@src/types/onyx/OriginalMessage'; import type en from './en'; -import type {ConciergeBrokenCardConnectionParams, CreatedReportForUnapprovedTransactionsParams, PaidElsewhereParams} from './params'; +import type {ConciergeBrokenCardConnectionParams, CreatedReportForUnapprovedTransactionsParams, PaidElsewhereParams, UnsupportedFormulaValueErrorParams} from './params'; import type {TranslationDeepObject} from './types'; /* eslint-disable max-len */ @@ -5557,6 +5557,7 @@ ${amount} para ${merchant} - ${date}`, reportFieldNameRequiredError: 'Ingresa un nombre de campo de informe', reportFieldTypeRequiredError: 'Elige un tipo de campo de informe', circularReferenceError: 'Este campo no puede hacer referencia a sí mismo. Por favor, actualizar.', + unsupportedFormulaValueError: ({value}: UnsupportedFormulaValueErrorParams) => `El campo de fórmula ${value} no se reconoce`, reportFieldInitialValueRequiredError: 'Elige un valor inicial de campo de informe', genericFailureMessage: 'Se ha producido un error al actualizar el campo de informe. Por favor, inténtalo de nuevo.', }, diff --git a/src/languages/fr.ts b/src/languages/fr.ts index ae6196763821..f9ef5eec0998 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -56,6 +56,7 @@ import type { StepCounterParams, SyncStageNameConnectionsParams, UnshareParams, + UnsupportedFormulaValueErrorParams, UpdatedBudgetParams, UpdatedPolicyApprovalRuleParams, UpdatedPolicyCategoryMaxAmountNoReceiptParams, @@ -5745,6 +5746,7 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST. reportFieldNameRequiredError: 'Veuillez saisir un nom de champ de note de frais', reportFieldTypeRequiredError: 'Veuillez choisir un type de champ de note de frais', circularReferenceError: 'Ce champ ne peut pas faire référence à lui-même. Veuillez le mettre à jour.', + unsupportedFormulaValueError: ({value}: UnsupportedFormulaValueErrorParams) => `Champ de formule ${value} non reconnu`, reportFieldInitialValueRequiredError: 'Veuillez choisir une valeur initiale pour le champ de note de frais', genericFailureMessage: 'Une erreur s’est produite lors de la mise à jour du champ de note de frais. Veuillez réessayer.', }, diff --git a/src/languages/it.ts b/src/languages/it.ts index 21b0d587e97e..66acd8bb32e2 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -56,6 +56,7 @@ import type { StepCounterParams, SyncStageNameConnectionsParams, UnshareParams, + UnsupportedFormulaValueErrorParams, UpdatedBudgetParams, UpdatedPolicyApprovalRuleParams, UpdatedPolicyCategoryMaxAmountNoReceiptParams, @@ -5717,6 +5718,7 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST. reportFieldNameRequiredError: 'Inserisci un nome per il campo del report', reportFieldTypeRequiredError: 'Scegli un tipo di campo del report', circularReferenceError: 'Questo campo non può fare riferimento a se stesso. Aggiorna per favore.', + unsupportedFormulaValueError: ({value}: UnsupportedFormulaValueErrorParams) => `Campo formula ${value} non riconosciuto`, reportFieldInitialValueRequiredError: 'Scegli un valore iniziale per il campo del resoconto', genericFailureMessage: 'Si è verificato un errore durante l’aggiornamento del campo del report. Riprova.', }, diff --git a/src/languages/ja.ts b/src/languages/ja.ts index eccda8fc9a4f..bb55bd54a0dd 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -56,6 +56,7 @@ import type { StepCounterParams, SyncStageNameConnectionsParams, UnshareParams, + UnsupportedFormulaValueErrorParams, UpdatedBudgetParams, UpdatedPolicyApprovalRuleParams, UpdatedPolicyCategoryMaxAmountNoReceiptParams, @@ -5651,6 +5652,7 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO reportFieldNameRequiredError: 'レポート項目名を入力してください', reportFieldTypeRequiredError: 'レポートフィールドの種類を選択してください', circularReferenceError: 'このフィールドを自分自身に参照することはできません。更新してください。', + unsupportedFormulaValueError: ({value}: UnsupportedFormulaValueErrorParams) => `数式フィールド ${value} が認識されません`, reportFieldInitialValueRequiredError: 'レポート項目の初期値を選択してください', genericFailureMessage: 'レポートフィールドの更新中にエラーが発生しました。もう一度お試しください。', }, diff --git a/src/languages/nl.ts b/src/languages/nl.ts index 7336d6d4a873..406129b68ec9 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -56,6 +56,7 @@ import type { StepCounterParams, SyncStageNameConnectionsParams, UnshareParams, + UnsupportedFormulaValueErrorParams, UpdatedBudgetParams, UpdatedPolicyApprovalRuleParams, UpdatedPolicyCategoryMaxAmountNoReceiptParams, @@ -5699,6 +5700,7 @@ _Voor meer gedetailleerde instructies, [bezoek onze help-site](${CONST.NETSUITE_ reportFieldNameRequiredError: 'Voer een naam voor een rapportveld in', reportFieldTypeRequiredError: 'Kies een veldtype voor het rapport', circularReferenceError: 'Dit veld kan niet naar zichzelf verwijzen. Werk het alsjeblieft bij.', + unsupportedFormulaValueError: ({value}: UnsupportedFormulaValueErrorParams) => `Formuleveld ${value} niet herkend`, reportFieldInitialValueRequiredError: 'Kies een beginwaarde voor een rapportveld', genericFailureMessage: 'Er is een fout opgetreden bij het bijwerken van het rapportveld. Probeer het opnieuw.', }, diff --git a/src/languages/params.ts b/src/languages/params.ts index bc6fd400fbc4..017f608dbd41 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -136,6 +136,10 @@ type MarkReimbursedFromIntegrationParams = {amount: string; currency: string}; type ShareParams = {to: string}; +type UnsupportedFormulaValueErrorParams = { + value: string; +}; + type UnshareParams = {to: string}; type ConnectionNameParams = { @@ -236,6 +240,7 @@ export type { MarkReimbursedFromIntegrationParams, ShareParams, UnshareParams, + UnsupportedFormulaValueErrorParams, ConnectionNameParams, ExportAgainModalDescriptionParams, UpdateRoleParams, diff --git a/src/languages/pl.ts b/src/languages/pl.ts index c9b6fed1f72a..0df3ebe034af 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -56,6 +56,7 @@ import type { StepCounterParams, SyncStageNameConnectionsParams, UnshareParams, + UnsupportedFormulaValueErrorParams, UpdatedBudgetParams, UpdatedPolicyApprovalRuleParams, UpdatedPolicyCategoryMaxAmountNoReceiptParams, @@ -5691,6 +5692,7 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy reportFieldNameRequiredError: 'Wprowadź nazwę pola raportu', reportFieldTypeRequiredError: 'Wybierz typ pola raportu', circularReferenceError: 'To pole nie może odnosić się do siebie. Zaktualizuj je.', + unsupportedFormulaValueError: ({value}: UnsupportedFormulaValueErrorParams) => `Pole formuły ${value} nie zostało rozpoznane`, reportFieldInitialValueRequiredError: 'Wybierz początkową wartość pola raportu', genericFailureMessage: 'Wystąpił błąd podczas aktualizowania pola raportu. Spróbuj ponownie.', }, diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index f43fe751cfe5..c9b7a2cda4ac 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -56,6 +56,7 @@ import type { StepCounterParams, SyncStageNameConnectionsParams, UnshareParams, + UnsupportedFormulaValueErrorParams, UpdatedBudgetParams, UpdatedPolicyApprovalRuleParams, UpdatedPolicyCategoryMaxAmountNoReceiptParams, @@ -5698,6 +5699,7 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS reportFieldNameRequiredError: 'Insira um nome de campo de relatório', reportFieldTypeRequiredError: 'Escolha um tipo de campo de relatório', circularReferenceError: 'Este campo não pode fazer referência a si mesmo. Atualize, por favor.', + unsupportedFormulaValueError: ({value}: UnsupportedFormulaValueErrorParams) => `Campo de fórmula ${value} não reconhecido`, reportFieldInitialValueRequiredError: 'Escolha um valor inicial para o campo de relatório', genericFailureMessage: 'Ocorreu um erro ao atualizar o campo do relatório. Tente novamente.', }, diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index 91244efc29fe..6682831f041e 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -56,6 +56,7 @@ import type { StepCounterParams, SyncStageNameConnectionsParams, UnshareParams, + UnsupportedFormulaValueErrorParams, UpdatedBudgetParams, UpdatedPolicyApprovalRuleParams, UpdatedPolicyCategoryMaxAmountNoReceiptParams, @@ -5547,6 +5548,7 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM reportFieldNameRequiredError: '请输入报表字段名称', reportFieldTypeRequiredError: '请选择报表字段类型', circularReferenceError: '此字段不能引用自身。请更新。', + unsupportedFormulaValueError: ({value}: UnsupportedFormulaValueErrorParams) => `无法识别公式字段 ${value}`, reportFieldInitialValueRequiredError: '请选择报表字段的初始值', genericFailureMessage: '更新报表字段时出错。请重试。', }, diff --git a/src/libs/Formula.ts b/src/libs/Formula.ts index bcae1e52494e..c74489efd8e1 100644 --- a/src/libs/Formula.ts +++ b/src/libs/Formula.ts @@ -973,4 +973,4 @@ function resolveReportFieldValue( export {FORMULA_PART_TYPES, compute, parse, hasCircularReferences, resolveReportFieldValue}; -export type {FormulaContext, FieldList, MinimalTransaction}; +export type {FormulaContext, FieldList, FormulaPart, MinimalTransaction}; diff --git a/src/libs/WorkspaceReportFieldUtils.ts b/src/libs/WorkspaceReportFieldUtils.ts index 250d98f0d9e0..a7d127e9d728 100644 --- a/src/libs/WorkspaceReportFieldUtils.ts +++ b/src/libs/WorkspaceReportFieldUtils.ts @@ -6,6 +6,7 @@ import type ONYXKEYS from '@src/ONYXKEYS'; import type {InputID} from '@src/types/form/WorkspaceReportFieldForm'; import type {PolicyReportField, PolicyReportFieldType} from '@src/types/onyx/Policy'; import {addErrorMessage} from './ErrorUtils'; +import type {FormulaPart} from './Formula'; import {isRequiredFulfilled} from './ValidationUtils'; /** @@ -96,7 +97,7 @@ function hasFormulaPartsInInitialValue(initialValue?: string): boolean { // Dynamically require to avoid circular dependency with ReportActionsUtils const {parse, FORMULA_PART_TYPES} = require('./Formula') as { - parse: (formula?: string) => Array<{type: string}>; + parse: (formula?: string) => FormulaPart[]; FORMULA_PART_TYPES: {FREETEXT: string}; }; return parse(initialValue).some((part) => part.type !== FORMULA_PART_TYPES.FREETEXT); @@ -109,12 +110,112 @@ function isReportFieldNameExisting(fieldList: Record return Object.values(fieldList ?? {}).some((reportField) => reportField.name.toLowerCase() === fieldName.toLowerCase()); } +/** + * Returns the list of unsupported {report:*} formula parts in the initial value. + * Used to validate formula report fields so unsupported tokens (e.g. {report:i}) are rejected with a clear error. + */ +function getUnsupportedReportFieldFormulaParts(initialValue?: string): string[] { + if (!initialValue || typeof initialValue !== 'string') { + return []; + } + + // Dynamically require to avoid circular dependency with ReportActionsUtils + const {parse, FORMULA_PART_TYPES} = require('./Formula') as { + parse: (formula?: string) => FormulaPart[]; + FORMULA_PART_TYPES: {REPORT: string}; + }; + + // cspell:ignore oldid + const supportedReportFields = new Set([ + 'id', + 'oldid', + 'title', + 'status', + 'displaystatus', + 'expensescount', + 'type', + 'startdate', + 'enddate', + 'total', + 'reimbursable', + 'currency', + 'policyname', + 'workspacename', + 'created', + 'approve', + 'submit', + 'autoreporting', + ]); + + const supportedSubmitDirections = new Set(['from', 'to', 'date']); + const supportedSubmitPersonFields = new Set(['firstname', 'lastname', 'fullname', 'email', 'userid', 'customfield1', 'payrollid', 'customfield2']); + const supportedAutoReportingFields = new Set(['start', 'end']); + + const unsupported: string[] = []; + const parts = parse(initialValue); + + for (const part of parts) { + if (part.type !== FORMULA_PART_TYPES.REPORT) { + continue; + } + + const [field, ...rest] = part.fieldPath ?? []; + const normalizedField = field?.trim().toLowerCase(); + + if (!normalizedField || !supportedReportFields.has(normalizedField)) { + unsupported.push(part.definition); + continue; + } + + if (normalizedField === 'submit') { + const direction = rest.at(0)?.trim().toLowerCase(); + if (!direction || !supportedSubmitDirections.has(direction)) { + unsupported.push(part.definition); + continue; + } + + // report:submit:from and report:submit:to are valid without subfield. + if ((direction === 'from' || direction === 'to') && rest.length === 1) { + continue; + } + + // report:submit:date is valid (format is optional and validated elsewhere) + if (direction === 'date') { + continue; + } + + // report:submit:from:* and report:submit:to:* need a subfield + const submitField = rest.at(1)?.trim().toLowerCase(); + if (!submitField || !supportedSubmitPersonFields.has(submitField)) { + unsupported.push(part.definition); + } + continue; + } + + if (normalizedField === 'autoreporting') { + const subField = rest.at(0)?.trim().toLowerCase(); + if (!subField || !supportedAutoReportingFields.has(subField)) { + unsupported.push(part.definition); + } + } + + if (normalizedField === 'approve') { + if (rest.at(0)?.trim().toLowerCase() !== 'date') { + unsupported.push(part.definition); + } + } + } + + return unsupported; +} + export { getReportFieldTypeTranslationKey, getReportFieldAlternativeTextTranslationKey, validateReportFieldListValueName, generateFieldID, getReportFieldInitialValue, + getUnsupportedReportFieldFormulaParts, hasFormulaPartsInInitialValue, isReportFieldNameExisting, }; diff --git a/src/pages/workspace/reports/CreateReportFieldsPage.tsx b/src/pages/workspace/reports/CreateReportFieldsPage.tsx index ce16948bf18e..aaaec9b44d6c 100644 --- a/src/pages/workspace/reports/CreateReportFieldsPage.tsx +++ b/src/pages/workspace/reports/CreateReportFieldsPage.tsx @@ -18,7 +18,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {hasAccountingConnections} from '@libs/PolicyUtils'; import {isRequiredFulfilled} from '@libs/ValidationUtils'; -import {hasFormulaPartsInInitialValue, isReportFieldNameExisting} from '@libs/WorkspaceReportFieldUtils'; +import {getUnsupportedReportFieldFormulaParts, hasFormulaPartsInInitialValue, isReportFieldNameExisting} from '@libs/WorkspaceReportFieldUtils'; import type {SettingsNavigatorParamList} from '@navigation/types'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -106,6 +106,15 @@ function WorkspaceCreateReportFieldsPage({ errors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.circularReferenceError'); } + if ((type === CONST.REPORT_FIELD_TYPES.TEXT || type === CONST.REPORT_FIELD_TYPES.FORMULA) && !!formInitialValue && !errors[INPUT_IDS.INITIAL_VALUE]) { + const unsupportedFormulaParts = getUnsupportedReportFieldFormulaParts(formInitialValue); + if (unsupportedFormulaParts.length > 0) { + errors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.unsupportedFormulaValueError', { + value: unsupportedFormulaParts.join(', '), + }); + } + } + if (type === CONST.REPORT_FIELD_TYPES.LIST && availableListValuesLength > 0 && !isRequiredFulfilled(formInitialValue)) { errors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.reportFieldInitialValueRequiredError'); } diff --git a/src/pages/workspace/reports/ReportFieldsInitialValuePage.tsx b/src/pages/workspace/reports/ReportFieldsInitialValuePage.tsx index 96ccdb309700..00e5f16fc426 100644 --- a/src/pages/workspace/reports/ReportFieldsInitialValuePage.tsx +++ b/src/pages/workspace/reports/ReportFieldsInitialValuePage.tsx @@ -16,7 +16,7 @@ import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavig import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import {getReportFieldKey} from '@libs/ReportUtils'; import {isRequiredFulfilled} from '@libs/ValidationUtils'; -import {getReportFieldInitialValue} from '@libs/WorkspaceReportFieldUtils'; +import {getReportFieldInitialValue, getUnsupportedReportFieldFormulaParts} from '@libs/WorkspaceReportFieldUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import AccessOrNotFoundWrapper from '@pages/workspace/AccessOrNotFoundWrapper'; import type {WithPolicyAndFullscreenLoadingProps} from '@pages/workspace/withPolicyAndFullscreenLoading'; @@ -76,6 +76,15 @@ function ReportFieldsInitialValuePage({ errors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.circularReferenceError'); } + if ((reportField?.type === CONST.REPORT_FIELD_TYPES.TEXT || reportField?.type === CONST.REPORT_FIELD_TYPES.FORMULA) && !!formInitialValue && !errors[INPUT_IDS.INITIAL_VALUE]) { + const unsupportedFormulaParts = getUnsupportedReportFieldFormulaParts(formInitialValue); + if (unsupportedFormulaParts.length > 0) { + errors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.unsupportedFormulaValueError', { + value: unsupportedFormulaParts.join(', '), + }); + } + } + if (reportField?.type === CONST.REPORT_FIELD_TYPES.LIST && availableListValuesLength > 0 && !isRequiredFulfilled(formInitialValue)) { errors[INPUT_IDS.INITIAL_VALUE] = translate('workspace.reportFields.reportFieldInitialValueRequiredError'); } diff --git a/tests/unit/WorkspaceReportFieldUtilsTest.ts b/tests/unit/WorkspaceReportFieldUtilsTest.ts index 95a288c8602e..6a02ca2f75aa 100644 --- a/tests/unit/WorkspaceReportFieldUtilsTest.ts +++ b/tests/unit/WorkspaceReportFieldUtilsTest.ts @@ -1,4 +1,4 @@ -import {hasFormulaPartsInInitialValue, isReportFieldNameExisting} from '@libs/WorkspaceReportFieldUtils'; +import {getUnsupportedReportFieldFormulaParts, hasFormulaPartsInInitialValue, isReportFieldNameExisting} from '@libs/WorkspaceReportFieldUtils'; import type {PolicyReportField} from '@src/types/onyx/Policy'; describe('WorkspaceReportFieldUtils.hasFormulaPartsInInitialValue', () => { @@ -41,6 +41,47 @@ describe('WorkspaceReportFieldUtils.hasFormulaPartsInInitialValue', () => { }); }); +describe('WorkspaceReportFieldUtils.getUnsupportedReportFieldFormulaParts', () => { + it('returns empty array for empty or non-string input', () => { + expect(getUnsupportedReportFieldFormulaParts()).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('')).toEqual([]); + }); + + it('returns unsupported report tokens like {report:i}', () => { + expect(getUnsupportedReportFieldFormulaParts('{report:i}')).toEqual(['{report:i}']); + expect(getUnsupportedReportFieldFormulaParts('prefix {report:i} suffix')).toEqual(['{report:i}']); + }); + + it('returns empty array for supported report tokens', () => { + expect(getUnsupportedReportFieldFormulaParts('{report:id}')).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('{report:oldID}')).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('{report:title}')).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('{report:status}')).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('{report:displaystatus}')).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('{report:approve:date}')).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('{report:submit:from}')).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('{report:submit:to}')).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('{report:submit:from:firstname}')).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('{report:autoreporting:start}')).toEqual([]); + }); + + it('returns only unsupported parts in mixed formulas', () => { + expect(getUnsupportedReportFieldFormulaParts('Hello {report:id} and {report:invalid}')).toEqual(['{report:invalid}']); + expect(getUnsupportedReportFieldFormulaParts('{report:i} {report:unknown}')).toEqual(['{report:i}', '{report:unknown}']); + }); + + it('ignores non-report parts (field, user, freetext)', () => { + expect(getUnsupportedReportFieldFormulaParts('{field:myField}')).toEqual([]); + expect(getUnsupportedReportFieldFormulaParts('plain text')).toEqual([]); + }); + + it('rejects invalid submit and autoreporting subfields', () => { + expect(getUnsupportedReportFieldFormulaParts('{report:submit:invalid}')).toEqual(['{report:submit:invalid}']); + expect(getUnsupportedReportFieldFormulaParts('{report:autoreporting:invalid}')).toEqual(['{report:autoreporting:invalid}']); + expect(getUnsupportedReportFieldFormulaParts('{report:approve:invalid}')).toEqual(['{report:approve:invalid}']); + }); +}); + describe('WorkspaceReportFieldUtils.isReportFieldNameExisting', () => { const fieldList: Record = { field1: {name: 'Field1', type: 'text'} as PolicyReportField,