You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/06/06 08:21:27 UTC

(causeway) 35/39: CAUSEWAY-4012: allows selector string partial matching

This is an automated email from the ASF dual-hosted git repository.

danhaywood pushed a commit to branch CAUSEWAY-4012
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 32da54a23f2fe8b214629f34255fa6c2d27fc992
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Sat Jun 6 06:44:34 2026 +0100

    CAUSEWAY-4012: allows selector string partial matching
---
 .../ParentedCollectionSelectorMatchingUtil.java    | 10 +++++-
 .../ParentedCollectionSelectorActionUtilTest.java  | 41 ++++++++++++++++++++++
 .../allow-selector-string-partial-match/tasks.md   | 16 ++++-----
 3 files changed, 58 insertions(+), 9 deletions(-)

diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/synthetic/ParentedCollectionSelectorMatchingUtil.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/synthetic/ParentedCollectionSelectorMatchingUtil.java
index 687f36b3569..ee380928df0 100644
--- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/synthetic/ParentedCollectionSelectorMatchingUtil.java
+++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/actions/synthetic/ParentedCollectionSelectorMatchingUtil.java
@@ -93,13 +93,21 @@ private boolean matches(
             }
             val property = scalarProperties.getElseFail(i);
             val propertyValue = property.get(childAdapter, interactionInitiatedBy);
-            if(!Objects.equals(MmUnwrapUtils.single(propertyValue), MmUnwrapUtils.single(argumentAdapter))) {
+            if(!matches(MmUnwrapUtils.single(propertyValue), MmUnwrapUtils.single(argumentAdapter))) {
                 return false;
             }
         }
         return true;
     }
 
+    private boolean matches(final Object propertyValue, final Object argumentValue) {
+        if(propertyValue instanceof String
+                && argumentValue instanceof String) {
+            return ((String) propertyValue).contains((String) argumentValue);
+        }
+        return Objects.equals(propertyValue, argumentValue);
+    }
+
     @Value(staticConstructor = "of")
     static class MatchResult {
         @NonNull Status status;
diff --git a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java
index 2ed85ad5c06..9febf4f174b 100644
--- a/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java
+++ b/core/metamodel/src/test/java/org/apache/causeway/core/metamodel/specloader/specimpl/ParentedCollectionSelectorActionUtilTest.java
@@ -324,6 +324,31 @@ void validates_and_invokes_selector_action_returning_single_matching_child() {
         assertThat(result.getPojo(), is(matchingItem));
     }
 
+    @Test
+    void partial_string_filter_validates_and_invokes_when_it_identifies_one_child() {
+        val lease = new Lease();
+        val matchingItem = new LeaseItem("first child", 1, null);
+        lease.getItems().add(matchingItem);
+        lease.getItems().add(new LeaseItem("second child", 2, null));
+
+        assertThat(validate(lease, "irs", null).isAllowed(), is(true));
+
+        val result = invoke(lease, "irs", null);
+
+        assertThat(result.getPojo(), is(matchingItem));
+    }
+
+    @Test
+    void non_string_scalar_filter_still_uses_exact_equality() {
+        val lease = new Lease();
+        lease.getItems().add(new LeaseItem("first", 12, null));
+
+        val consent = validate(lease, "first", 1);
+
+        assertThat(consent.isVetoed(), is(true));
+        assertThat(consent.getReasonAsString().orElseThrow(), containsString("0 items match. Use parameters to match just one item."));
+    }
+
     @Test
     void selector_action_validation_rejects_when_no_child_matches() {
         val lease = new Lease();
@@ -355,6 +380,22 @@ void selector_action_validation_rejects_when_multiple_children_match() {
         assertThat(ex.getMessage(), containsString("2 items match. Use parameters to match just one item."));
     }
 
+    @Test
+    void partial_string_filter_is_ambiguous_when_it_matches_multiple_children() {
+        val lease = new Lease();
+        lease.getItems().add(new LeaseItem("first child", 1, null));
+        lease.getItems().add(new LeaseItem("second child", 2, null));
+
+        val consent = validate(lease, "child", null);
+
+        assertThat(consent.isVetoed(), is(true));
+        assertThat(consent.getReasonAsString().orElseThrow(), containsString("2 items match. Use parameters to match just one item."));
+
+        val ex = assertThrows(RecoverableException.class, () -> executeWithRuleChecking(lease, "child", null));
+
+        assertThat(ex.getMessage(), containsString("2 items match. Use parameters to match just one item."));
+    }
+
     @Test
     void direct_invocation_still_fails_when_no_child_matches() {
         val lease = new Lease();
diff --git a/openspec/changes/allow-selector-string-partial-match/tasks.md b/openspec/changes/allow-selector-string-partial-match/tasks.md
index b158a19c52d..775b17392cb 100644
--- a/openspec/changes/allow-selector-string-partial-match/tasks.md
+++ b/openspec/changes/allow-selector-string-partial-match/tasks.md
@@ -1,16 +1,16 @@
 ## 1. Matching Implementation
 
-- [ ] 1.1 Update `ParentedCollectionSelectorMatchingUtil` so supplied string filter values match child string property values using containment rather than exact equality.
-- [ ] 1.2 Keep non-string scalar filter values using exact equality and keep null or absent optional filter values ignored.
-- [ ] 1.3 Verify validation and invocation both use the same updated matching behavior.
+- [x] 1.1 Update `ParentedCollectionSelectorMatchingUtil` so supplied string filter values match child string property values using containment rather than exact equality.
+- [x] 1.2 Keep non-string scalar filter values using exact equality and keep null or absent optional filter values ignored.
+- [x] 1.3 Verify validation and invocation both use the same updated matching behavior.
 
 ## 2. Test Coverage
 
-- [ ] 2.1 Add or update selector action tests for a partial string filter that uniquely selects one child.
-- [ ] 2.2 Add or update selector action tests for a partial string filter that matches multiple children and remains ambiguous.
-- [ ] 2.3 Add or update selector action tests confirming non-string scalar filters still require exact equality.
+- [x] 2.1 Add or update selector action tests for a partial string filter that uniquely selects one child.
+- [x] 2.2 Add or update selector action tests for a partial string filter that matches multiple children and remains ambiguous.
+- [x] 2.3 Add or update selector action tests confirming non-string scalar filters still require exact equality.
 
 ## 3. Validation
 
-- [ ] 3.1 Run the focused selector action test suite for `ParentedCollectionSelectorActionUtilTest`.
-- [ ] 3.2 Run `openspec validate allow-selector-string-partial-match --strict`.
+- [x] 3.1 Run the focused selector action test suite for `ParentedCollectionSelectorActionUtilTest`.
+- [x] 3.2 Run `openspec validate allow-selector-string-partial-match --strict`.