diff --git a/src/Apps/W1/Quality Management/Test Library/src/QltyInspectionUtility.Codeunit.al b/src/Apps/W1/Quality Management/Test Library/src/QltyInspectionUtility.Codeunit.al index 14c1d2a042..0d5dbc6542 100644 --- a/src/Apps/W1/Quality Management/Test Library/src/QltyInspectionUtility.Codeunit.al +++ b/src/Apps/W1/Quality Management/Test Library/src/QltyInspectionUtility.Codeunit.al @@ -894,6 +894,20 @@ codeunit 139940 "Qlty. Inspection Utility" exit(QltyResultEvaluation.CheckIfValueIsString(ValueToCheck, AcceptableValue, QltyCaseSensitivity)); end; + /// + /// Wrapper for internal procedure CheckIfValueIsInPredefinedList from Qlty. Result Evaluation codeunit. + /// + /// The value to check. + /// The acceptable value condition: a comma or pipe separated list of literal values. A blank value means no condition, and the default "anything except empty" tokens are also accepted. + /// The case sensitivity option. + /// True if the value matches one of the listed literal values, false otherwise. + internal procedure CheckIfValueIsInPredefinedList(ValueToCheck: Text; AcceptableValue: Text; QltyCaseSensitivity: Enum "Qlty. Case Sensitivity"): Boolean + var + QltyResultEvaluation: Codeunit "Qlty. Result Evaluation"; + begin + exit(QltyResultEvaluation.CheckIfValueIsInPredefinedList(ValueToCheck, AcceptableValue, QltyCaseSensitivity)); + end; + /// /// Wrapper for internal procedure ValidateQltyInspectionLine from Qlty. Result Evaluation codeunit. /// Validates an inspection line using the single-parameter internal signature. diff --git a/src/Apps/W1/Quality Management/app/src/Configuration/Result/QltyResultEvaluation.Codeunit.al b/src/Apps/W1/Quality Management/app/src/Configuration/Result/QltyResultEvaluation.Codeunit.al index c1b2942c55..3d9a296c17 100644 --- a/src/Apps/W1/Quality Management/app/src/Configuration/Result/QltyResultEvaluation.Codeunit.al +++ b/src/Apps/W1/Quality Management/app/src/Configuration/Result/QltyResultEvaluation.Codeunit.al @@ -104,7 +104,9 @@ codeunit 20410 "Qlty. Result Evaluation" LoopConditionMet := QltyBooleanParsing.GetBooleanFor(TestValue) = QltyBooleanParsing.GetBooleanFor(Condition) else LoopConditionMet := CheckIfValueIsString(TestValue, Condition, QltyCaseSensitivity); - QltyTestValueType::"Value Type Text", QltyTestValueType::"Value Type Option", QltyTestValueType::"Value Type Table Lookup", QltyTestValueType::"Value Type Text Expression": + QltyTestValueType::"Value Type Option", QltyTestValueType::"Value Type Table Lookup": + LoopConditionMet := CheckIfValueIsInPredefinedList(TestValue, Condition, QltyCaseSensitivity); + QltyTestValueType::"Value Type Text", QltyTestValueType::"Value Type Text Expression": LoopConditionMet := CheckIfValueIsString(TestValue, Condition, QltyCaseSensitivity); QltyTestValueType::"Value Type Date": begin @@ -700,6 +702,36 @@ codeunit 20410 "Qlty. Result Evaluation" exit(not TempTestStringValueQltyTest.IsEmpty()); end; + internal procedure CheckIfValueIsInPredefinedList(ValueToCheck: Text; AcceptableValue: Text; QltyCaseSensitivity: Enum "Qlty. Case Sensitivity"): Boolean + var + SingleAcceptableValue: Text; + begin + // Option and Table Lookup tests use a fixed set of predefined values. Their conditions are a + // comma or pipe separated list of literal values that may contain special filter characters such + // as parentheses, so they must be compared literally instead of being interpreted as a filter. + if IsAnythingExceptEmptyCondition(AcceptableValue) then + exit(ValueToCheck <> ''); + + if IsBlankOrEmptyCondition(AcceptableValue) then + exit(true); + + if QltyCaseSensitivity = QltyCaseSensitivity::Insensitive then begin + ValueToCheck := ValueToCheck.ToLower(); + AcceptableValue := AcceptableValue.ToLower(); + end; + + AcceptableValue := AcceptableValue.Replace('|', ','); + foreach SingleAcceptableValue in AcceptableValue.Split(',') do begin + SingleAcceptableValue := SingleAcceptableValue.Trim(); + if SingleAcceptableValue = '' then + continue; + if ValueToCheck = SingleAcceptableValue then + exit(true); + end; + + exit(false); + end; + /// /// OnBeforeEvaluateResult gives an opportunity to change how a result is evaluated. /// diff --git a/src/Apps/W1/Quality Management/test/src/QltyTestsResultEval.Codeunit.al b/src/Apps/W1/Quality Management/test/src/QltyTestsResultEval.Codeunit.al index 05f7d8a17a..05b6b27056 100644 --- a/src/Apps/W1/Quality Management/test/src/QltyTestsResultEval.Codeunit.al +++ b/src/Apps/W1/Quality Management/test/src/QltyTestsResultEval.Codeunit.al @@ -172,6 +172,42 @@ codeunit 139963 "Qlty. Tests - Result Eval." LibraryAssert.IsTrue(QltyInspectionUtility.CheckIfValueIsString('wildCardSearch', 'wild*'), 'String wildcard 2'); end; + [Test] + procedure ValueOptionWithSpecialCharacters() + var + QltyInspectionUtility: Codeunit "Qlty. Inspection Utility"; + CaseOption: Enum "Qlty. Case Sensitivity"; + begin + // [SCENARIO 639903] Option/Table Lookup conditions are matched literally, so predefined values containing + // special filter characters such as parentheses do not raise a filter error. + + // [GIVEN] An option test with values that contain parentheses 'Test(1),Test(2)' + // [WHEN] Evaluating a selected option value against a comma separated pass condition + // [THEN] The value matches its literal option without interpreting '(' as a filter operator + LibraryAssert.IsTrue(QltyInspectionUtility.CheckIfValueIsInPredefinedList('Test(1)', 'Test(1),Test(2)', CaseOption::Sensitive), 'Option special chars comma list first'); + LibraryAssert.IsTrue(QltyInspectionUtility.CheckIfValueIsInPredefinedList('Test(2)', 'Test(1),Test(2)', CaseOption::Sensitive), 'Option special chars comma list second'); + LibraryAssert.IsFalse(QltyInspectionUtility.CheckIfValueIsInPredefinedList('Test(3)', 'Test(1),Test(2)', CaseOption::Sensitive), 'Option special chars not in list'); + + // [THEN] A trailing space after the condition list is tolerated + LibraryAssert.IsTrue(QltyInspectionUtility.CheckIfValueIsInPredefinedList('Test(2)', 'Test(1),Test(2) ', CaseOption::Sensitive), 'Option special chars trailing space'); + + // [THEN] Pipe separated lists are also supported + LibraryAssert.IsTrue(QltyInspectionUtility.CheckIfValueIsInPredefinedList('C', 'C|D', CaseOption::Sensitive), 'Option pipe list match'); + LibraryAssert.IsFalse(QltyInspectionUtility.CheckIfValueIsInPredefinedList('A', 'C|D', CaseOption::Sensitive), 'Option pipe list no match'); + + // [THEN] Empty value does not match a non-empty condition and an empty condition matches anything + LibraryAssert.IsFalse(QltyInspectionUtility.CheckIfValueIsInPredefinedList('', 'C|D', CaseOption::Sensitive), 'Option empty value'); + LibraryAssert.IsTrue(QltyInspectionUtility.CheckIfValueIsInPredefinedList('A', '', CaseOption::Sensitive), 'Option empty condition'); + + // [THEN] Empty tokens from a malformed list (trailing or doubled delimiters) do not match an empty value + LibraryAssert.IsFalse(QltyInspectionUtility.CheckIfValueIsInPredefinedList('', 'A,', CaseOption::Sensitive), 'Option empty token trailing delimiter'); + LibraryAssert.IsFalse(QltyInspectionUtility.CheckIfValueIsInPredefinedList('', 'A||B', CaseOption::Sensitive), 'Option empty token doubled delimiter'); + LibraryAssert.IsTrue(QltyInspectionUtility.CheckIfValueIsInPredefinedList('B', 'A||B', CaseOption::Sensitive), 'Option doubled delimiter still matches real value'); + + // [THEN] Case insensitive comparison matches values that differ only by case + LibraryAssert.IsTrue(QltyInspectionUtility.CheckIfValueIsInPredefinedList('test(1)', 'Test(1),Test(2)', CaseOption::Insensitive), 'Option special chars case insensitive'); + end; + [TryFunction] procedure Try_TestValueDateIntentionallyBad() var