From 7564686933e3027a482d717404c6202d70401c4d Mon Sep 17 00:00:00 2001 From: aleksandrovpv Date: Mon, 1 Jun 2026 15:31:43 +0400 Subject: [PATCH] GenericFilter: introduce MutableConfiguration, add addAndSetCurrentConfiguration() and refreshCurrentConfiguration() #5083 - MutableConfiguration: new interface extending Configuration with all mutating methods. RunTimeConfiguration now implements MutableConfiguration instead of Configuration, giving compile-time safety against calling mutating methods on a DesignTimeConfiguration. Mutating methods on Configuration are deprecated in favour of MutableConfiguration. - addAndSetCurrentConfiguration(): atomic alternative to calling addConfiguration() followed by setCurrentConfiguration(). The two-step sequence silently does nothing when the configuration has not yet been registered at the time setCurrentConfiguration is invoked. - refreshCurrentConfiguration(): stable public method to re-render the filter UI after programmatic modification of the current configuration. Was previously only accessible as the protected refreshCurrentConfigurationLayout(). - MutableConfiguration.protectedFromUserDeletion: flag that prevents the user from deleting a programmatic configuration through the UI. When true, GenericFilterRemoveAction hides the Remove button for that configuration, and GenericFilter.removeConfiguration() silently skips it. --- .../GenericFilterRemoveAction.java | 3 + .../genericfilter/Configuration.java | 64 +++++++- .../genericfilter/GenericFilter.java | 32 +++- .../genericfilter/MutableConfiguration.java | 141 ++++++++++++++++++ .../DesignTimeConfiguration.java | 17 ++- .../configuration/RunTimeConfiguration.java | 24 ++- .../genericfilter/GenericFilterApiTest.groovy | 134 +++++++++++++++++ .../view/GenericFilterApiTestView.java | 33 ++++ .../view/generic-filter-api-test-view.xml | 32 ++++ 9 files changed, 469 insertions(+), 11 deletions(-) create mode 100644 jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/MutableConfiguration.java create mode 100644 jmix-flowui/flowui/src/test/groovy/component/genericfilter/GenericFilterApiTest.groovy create mode 100644 jmix-flowui/flowui/src/test/java/component/genericfilter/view/GenericFilterApiTestView.java create mode 100644 jmix-flowui/flowui/src/test/resources/component/genericfilter/view/generic-filter-api-test-view.xml diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterRemoveAction.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterRemoveAction.java index 30fb8e99bf..ca212e4d4a 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterRemoveAction.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/action/genericfilter/GenericFilterRemoveAction.java @@ -25,6 +25,7 @@ import io.jmix.flowui.action.DialogAction; import io.jmix.flowui.component.genericfilter.Configuration; import io.jmix.flowui.component.genericfilter.GenericFilterSupport; +import io.jmix.flowui.component.genericfilter.MutableConfiguration; import io.jmix.flowui.component.genericfilter.configuration.DesignTimeConfiguration; import io.jmix.flowui.component.genericfilter.model.FilterConfigurationModel; import io.jmix.flowui.icon.Icons; @@ -91,6 +92,8 @@ protected boolean isApplicable() { return super.isApplicable() && target.getCurrentConfiguration() != target.getEmptyConfiguration() && !(target.getCurrentConfiguration() instanceof DesignTimeConfiguration) + && !(target.getCurrentConfiguration() instanceof MutableConfiguration mc + && mc.isProtectedFromUserDeletion()) && (globalConfigurationModificationPermitted || !isCurrentConfigurationAvailableForAll()); } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/Configuration.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/Configuration.java index 04a922ee03..ba3857a7da 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/Configuration.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/Configuration.java @@ -18,13 +18,18 @@ import io.jmix.core.querycondition.LogicalCondition; import io.jmix.flowui.component.filter.FilterComponent; +import io.jmix.flowui.component.genericfilter.configuration.DesignTimeConfiguration; import io.jmix.flowui.component.genericfilter.configuration.RunTimeConfiguration; import io.jmix.flowui.component.logicalfilter.LogicalFilterComponent; -import org.jspecify.annotations.Nullable; +import org.springframework.lang.Nullable; /** * A configuration is a set of filter components. + * + *

Note: several mutating methods declared in this interface throw + * {@link UnsupportedOperationException} when called on a {@link DesignTimeConfiguration}. + * Those methods are deprecated — use {@link MutableConfiguration} when you need to call them. */ public interface Configuration extends Comparable { @@ -45,12 +50,15 @@ public interface Configuration extends Comparable { String getName(); /** - * Sets the name of configuration. This method is only available for - * the {@link RunTimeConfiguration}. + * Sets the name of configuration. * * @param name a configuration name + * @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration} + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration * @see RunTimeConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) void setName(@Nullable String name); /** @@ -60,13 +68,16 @@ public interface Configuration extends Comparable { LogicalFilterComponent getRootLogicalFilterComponent(); /** - * Sets the root element of configuration. This method is only available for - * the {@link RunTimeConfiguration}. + * Sets the root element of configuration. * * @param rootLogicalFilterComponent a root element of configuration + * @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration} + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration * @see LogicalFilterComponent * @see RunTimeConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) void setRootLogicalFilterComponent(LogicalFilterComponent rootLogicalFilterComponent); /** @@ -76,15 +87,22 @@ public interface Configuration extends Comparable { /** * @return true if the configuration is modified + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) boolean isModified(); /** * Sets whether configuration is modified. If a filter component is modified, * then a remove button appears next to it. * - * @param modified whether configuration is modified. + * @param modified whether configuration is modified + * @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration} + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) void setModified(boolean modified); /** @@ -93,7 +111,10 @@ public interface Configuration extends Comparable { * * @param filterComponent the filter component to check * @return whether the filter component of configuration is modified + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) boolean isFilterComponentModified(FilterComponent filterComponent); /** @@ -102,16 +123,28 @@ public interface Configuration extends Comparable { * * @param filterComponent a filter component * @param modified whether the filter component of configuration is modified + * @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration} + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) void setFilterComponentModified(FilterComponent filterComponent, boolean modified); /** * Sets a default value of {@link FilterComponent} for the configuration by the parameter name. * This allows the default values to be saved and displayed in the configuration editor. * + *

On {@link io.jmix.flowui.component.genericfilter.configuration.DesignTimeConfiguration} + * this method is intended for framework-internal initialization only (e.g. XML loader). + * For {@link io.jmix.flowui.component.genericfilter.configuration.RunTimeConfiguration}, + * use {@link MutableConfiguration#setFilterComponentDefaultValue} instead. + * * @param parameterName a parameter name of filter component * @param defaultValue a default value + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) void setFilterComponentDefaultValue(String parameterName, @Nullable Object defaultValue); /** @@ -119,7 +152,11 @@ public interface Configuration extends Comparable { * component becomes null. * * @param parameterName a parameter name of filter component + * @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration} + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) void resetFilterComponentDefaultValue(String parameterName); /** @@ -133,20 +170,33 @@ public interface Configuration extends Comparable { /** * Sets null as the default value for all configuration filter components. + * + * @throws UnsupportedOperationException when called on {@link DesignTimeConfiguration} + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) void resetAllDefaultValues(); /** - * Returns whether the configuration is available for all users + * Returns whether the configuration is available for all users. + * * @return true if the configuration is available for all users, otherwise false. + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) default boolean isAvailableForAllUsers() { return false; } /** * Sets whether the configuration is available for all users or not + * + * @deprecated use {@link MutableConfiguration} instead + * @see MutableConfiguration */ + @Deprecated(since = "3.0", forRemoval = true) default void setAvailableForAllUsers(boolean availableForAllUsers) { } } diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/GenericFilter.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/GenericFilter.java index c18771466a..f97679c657 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/GenericFilter.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/GenericFilter.java @@ -603,6 +603,35 @@ public void setCurrentConfiguration(Configuration currentConfiguration) { setCurrentConfigurationInternal(currentConfiguration, false); } + /** + * Registers the given configuration with this filter and immediately sets it as the current + * configuration, in the correct order and in a single call. + *

+ * This is a convenience alternative to calling {@link #addConfiguration(Configuration)} + * followed by {@link #setCurrentConfiguration(Configuration)}, which fails silently when + * the configuration is not yet registered at the time {@code setCurrentConfiguration} is + * invoked. + * + * @param configuration the configuration to register and activate + */ + public void addAndSetCurrentConfiguration(Configuration configuration) { + addConfiguration(configuration); + setCurrentConfiguration(configuration); + } + + /** + * Refreshes the layout of the current configuration. + *

+ * Call this method after programmatically modifying the current configuration's filter + * components (e.g. adding a component to the root {@link LogicalFilterComponent}) to force + * the filter UI to re-render remove buttons and update the data-loader condition. + *

+ * This is a stable public equivalent of the internal {@code refreshCurrentConfigurationLayout()}. + */ + public void refreshCurrentConfiguration() { + refreshCurrentConfigurationLayout(); + } + protected void setCurrentConfigurationInternal(Configuration currentConfiguration, boolean fromClient) { if (configurations.contains(currentConfiguration) || getEmptyConfiguration().equals(currentConfiguration)) { @@ -891,7 +920,8 @@ public void addConfiguration(Configuration configuration) { */ public void removeConfiguration(Configuration configuration) { if (configuration != getEmptyConfiguration() - && !(configuration instanceof DesignTimeConfiguration)) { + && !(configuration instanceof DesignTimeConfiguration) + && !(configuration instanceof MutableConfiguration mc && mc.isProtectedFromUserDeletion())) { configurations.remove(configuration); configuration.getRootLogicalFilterComponent().getElement().removeFromParent(); diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/MutableConfiguration.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/MutableConfiguration.java new file mode 100644 index 0000000000..b202bf8edd --- /dev/null +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/MutableConfiguration.java @@ -0,0 +1,141 @@ +/* + * Copyright 2024 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.jmix.flowui.component.genericfilter; + +import io.jmix.flowui.component.filter.FilterComponent; +import io.jmix.flowui.component.genericfilter.configuration.DesignTimeConfiguration; +import io.jmix.flowui.component.genericfilter.configuration.RunTimeConfiguration; +import io.jmix.flowui.component.logicalfilter.LogicalFilterComponent; +import org.springframework.lang.Nullable; + +/** + * A mutable extension of {@link Configuration}. + *

+ * {@link RunTimeConfiguration} implements this interface. {@link DesignTimeConfiguration} + * only implements the read-only {@link Configuration}. + *

+ * Use this interface as the declared type when you need to call mutating methods, + * so the compiler catches misuse of {@link DesignTimeConfiguration} at compile time: + *

{@code
+ * MutableConfiguration config = filter.runtimeConfigurationBuilder()
+ *         .id("myConfig")
+ *         .buildAndRegister();
+ * config.setModified(true);
+ * }
+ * + * @see Configuration + * @see RunTimeConfiguration + */ +@SuppressWarnings({"deprecation", "removal"}) +public interface MutableConfiguration extends Configuration { + + /** + * Returns whether the configuration is modified. + * If a filter component is modified, a remove button appears next to it. + */ + boolean isModified(); + + /** + * Returns whether the given filter component is modified. + * If a filter component is modified, a remove button appears next to it. + * + * @param filterComponent the filter component to check + */ + boolean isFilterComponentModified(FilterComponent filterComponent); + + /** + * Sets the name of configuration. + * + * @param name a configuration name + */ + void setName(@Nullable String name); + + /** + * Sets the root logical filter component of configuration. + * + * @param rootLogicalFilterComponent a root element of configuration + */ + void setRootLogicalFilterComponent(LogicalFilterComponent rootLogicalFilterComponent); + + /** + * Sets whether the configuration is modified. + * If a filter component is modified, a remove button appears next to it. + *

+ * Note: this method iterates over components already added to the root at the time of the call. + * + * @param modified whether configuration is modified + */ + void setModified(boolean modified); + + /** + * Sets whether the given filter component of configuration is modified. + * If a filter component is modified, a remove button appears next to it. + * + * @param filterComponent a filter component + * @param modified whether the filter component of configuration is modified + */ + void setFilterComponentModified(FilterComponent filterComponent, boolean modified); + + /** + * Sets a default value of the given filter component for the configuration. + * This allows the default values to be saved and displayed in the configuration editor. + * + * @param parameterName a parameter name of filter component + * @param defaultValue a default value + */ + void setFilterComponentDefaultValue(String parameterName, @Nullable Object defaultValue); + + /** + * Resets the default value of the filter component identified by the parameter name. + * + * @param parameterName a parameter name of filter component + */ + void resetFilterComponentDefaultValue(String parameterName); + + /** + * Sets null as the default value for all configuration filter components. + */ + void resetAllDefaultValues(); + + /** + * Returns whether the configuration is available for all users. + */ + boolean isAvailableForAllUsers(); + + /** + * Sets whether the configuration is available for all users. + * + * @param availableForAllUsers whether the configuration is available for all users + */ + void setAvailableForAllUsers(boolean availableForAllUsers); + + /** + * Returns whether this configuration is protected from deletion by the user through the UI. + *

+ * When {@code true}, the Remove action in the filter toolbar is hidden for this configuration, + * and {@link GenericFilter#removeConfiguration} will not remove it even if called directly. + */ + boolean isProtectedFromUserDeletion(); + + /** + * Sets whether this configuration is protected from deletion by the user through the UI. + * + * @param value {@code true} to prevent the user from deleting this configuration + */ + void setProtectedFromUserDeletion(boolean value); + +} diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/configuration/DesignTimeConfiguration.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/configuration/DesignTimeConfiguration.java index 7509f6e354..08fc2b4402 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/configuration/DesignTimeConfiguration.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/configuration/DesignTimeConfiguration.java @@ -24,10 +24,14 @@ import io.jmix.flowui.component.genericfilter.GenericFilter; import io.jmix.flowui.component.logicalfilter.LogicalFilterComponent; -import org.jspecify.annotations.Nullable; +import org.springframework.lang.Nullable; import java.util.HashMap; import java.util.Map; +/** + * A read-only configuration defined at design time (e.g. via XML). + * Mutating methods inherited from {@link Configuration} throw {@link UnsupportedOperationException}. + */ public class DesignTimeConfiguration implements Configuration { protected final String id; @@ -107,7 +111,18 @@ public void setFilterComponentModified(FilterComponent filterComponent, boolean "Use FilterCopyAction to create a modifiable copy of configuration"); } + /** + * Records the default value for the given filter component parameter. + *

+ * Intended for framework-internal initialization only — called by the XML loader + * ({@code GenericFilterLoader}) and {@code DesignTimeConfigurationBuilder} when setting up + * the configuration. Not deprecated here intentionally: the deprecation on + * {@link Configuration#setFilterComponentDefaultValue} discourages calling this method + * through a {@link Configuration} reference, but direct calls on this class are valid + * during initialization. + */ @Override + @SuppressWarnings({"deprecation", "removal"}) public void setFilterComponentDefaultValue(String parameterName, @Nullable Object defaultValue) { Preconditions.checkNotNullArgument(parameterName); if (isFilterComponentExist(parameterName)) { diff --git a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/configuration/RunTimeConfiguration.java b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/configuration/RunTimeConfiguration.java index c187c1f6ad..ea4d11b7f8 100644 --- a/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/configuration/RunTimeConfiguration.java +++ b/jmix-flowui/flowui/src/main/java/io/jmix/flowui/component/genericfilter/configuration/RunTimeConfiguration.java @@ -22,15 +22,24 @@ import io.jmix.flowui.component.filter.SingleFilterComponentBase; import io.jmix.flowui.component.genericfilter.Configuration; import io.jmix.flowui.component.genericfilter.GenericFilter; +import io.jmix.flowui.component.genericfilter.MutableConfiguration; import io.jmix.flowui.component.logicalfilter.LogicalFilterComponent; -import org.jspecify.annotations.Nullable; +import org.springframework.lang.Nullable; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; -public class RunTimeConfiguration implements Configuration { +/** + * A runtime (user-created) filter configuration that supports dynamic modification of + * its filter components. + *

+ * Implements {@link MutableConfiguration} — use that interface as the declared type when + * you need compile-time safety against calling mutating methods on a read-only + * {@link DesignTimeConfiguration}. + */ +public class RunTimeConfiguration implements MutableConfiguration { protected final String id; protected final GenericFilter owner; @@ -40,6 +49,7 @@ public class RunTimeConfiguration implements Configuration { protected LogicalFilterComponent rootLogicalFilterComponent; protected Set modifiedFilterComponents = new HashSet<>(); protected Map defaultValuesMap = new HashMap<>(); + protected boolean protectedFromUserDeletion = false; public RunTimeConfiguration(String id, LogicalFilterComponent rootLogicalFilterComponent, GenericFilter owner) { this.id = id; @@ -186,4 +196,14 @@ public boolean equals(Object obj) { public int hashCode() { return id.hashCode(); } + + @Override + public boolean isProtectedFromUserDeletion() { + return protectedFromUserDeletion; + } + + @Override + public void setProtectedFromUserDeletion(boolean value) { + this.protectedFromUserDeletion = value; + } } diff --git a/jmix-flowui/flowui/src/test/groovy/component/genericfilter/GenericFilterApiTest.groovy b/jmix-flowui/flowui/src/test/groovy/component/genericfilter/GenericFilterApiTest.groovy new file mode 100644 index 0000000000..df163c130a --- /dev/null +++ b/jmix-flowui/flowui/src/test/groovy/component/genericfilter/GenericFilterApiTest.groovy @@ -0,0 +1,134 @@ +/* + * Copyright 2026 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package component.genericfilter + +import component.genericfilter.view.GenericFilterApiTestView +import io.jmix.flowui.Actions +import io.jmix.flowui.UiComponents +import io.jmix.flowui.action.genericfilter.GenericFilterRemoveAction +import io.jmix.flowui.component.genericfilter.MutableConfiguration +import io.jmix.flowui.component.genericfilter.configuration.RunTimeConfiguration +import io.jmix.flowui.component.logicalfilter.GroupFilter +import io.jmix.flowui.component.logicalfilter.LogicalFilterComponent +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.boot.test.context.SpringBootTest +import test_support.spec.FlowuiTestSpecification + +@SpringBootTest +class GenericFilterApiTest extends FlowuiTestSpecification { + + @Autowired + UiComponents uiComponents + + @Autowired + Actions actions + + void setup() { + registerViewBasePackages("component.genericfilter.view") + } + + def "addAndSetCurrentConfiguration() registers and activates in one call"() { + given: + def view = navigateToView(GenericFilterApiTestView) + def filter = view.genericFilter + def config = newRuntimeConfig("my-config", filter) + + when: + filter.addAndSetCurrentConfiguration(config) + + then: + filter.getConfigurations().contains(config) + filter.getCurrentConfiguration() == config + } + + def "refreshCurrentConfiguration() can be called on the public API without error"() { + given: + def view = navigateToView(GenericFilterApiTestView) + + when: + view.genericFilter.refreshCurrentConfiguration() + + then: + noExceptionThrown() + } + + def "protectedFromUserDeletion defaults to false and can be set to true"() { + given: + def view = navigateToView(GenericFilterApiTestView) + def config = newRuntimeConfig("config", view.genericFilter) + + expect: + !config.isProtectedFromUserDeletion() + + when: + config.setProtectedFromUserDeletion(true) + + then: + config.isProtectedFromUserDeletion() + } + + def "removeConfiguration() skips a configuration protected from user deletion"() { + given: + def view = navigateToView(GenericFilterApiTestView) + def filter = view.genericFilter + def config = newRuntimeConfig("protected", filter) + config.setProtectedFromUserDeletion(true) + filter.addConfiguration(config) + + when: + filter.removeConfiguration(config) + + then: + filter.getConfigurations().contains(config) + } + + def "GenericFilterRemoveAction is not applicable when current configuration is protected from user deletion"() { + given: + def view = navigateToView(GenericFilterApiTestView) + def filter = view.genericFilter + def config = newRuntimeConfig("protected", filter) + config.setProtectedFromUserDeletion(true) + filter.addAndSetCurrentConfiguration(config) + + and: + def removeAction = actions.create(GenericFilterRemoveAction.ID) as GenericFilterRemoveAction + removeAction.setTarget(filter) + + expect: + !removeAction.isApplicable() + } + + def "RunTimeConfiguration implements MutableConfiguration; DesignTimeConfiguration does not"() { + given: + def view = navigateToView(GenericFilterApiTestView) + def filter = view.genericFilter + def runTimeConfig = newRuntimeConfig("rt", filter) + def designTimeConfig = filter.addConfiguration("dt", "Design-time") + + expect: + runTimeConfig instanceof MutableConfiguration + !(designTimeConfig instanceof MutableConfiguration) + } + + private RunTimeConfiguration newRuntimeConfig(String id, def filter) { + def root = uiComponents.create(GroupFilter) + root.setConditionModificationDelegated(true) + root.setOperation(LogicalFilterComponent.Operation.AND) + root.setOperationTextVisible(false) + return new RunTimeConfiguration(id, root, filter) + } +} diff --git a/jmix-flowui/flowui/src/test/java/component/genericfilter/view/GenericFilterApiTestView.java b/jmix-flowui/flowui/src/test/java/component/genericfilter/view/GenericFilterApiTestView.java new file mode 100644 index 0000000000..43a2b6be13 --- /dev/null +++ b/jmix-flowui/flowui/src/test/java/component/genericfilter/view/GenericFilterApiTestView.java @@ -0,0 +1,33 @@ +/* + * Copyright 2026 Haulmont. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package component.genericfilter.view; + +import com.vaadin.flow.router.Route; +import io.jmix.flowui.component.genericfilter.GenericFilter; +import io.jmix.flowui.view.StandardView; +import io.jmix.flowui.view.ViewComponent; +import io.jmix.flowui.view.ViewController; +import io.jmix.flowui.view.ViewDescriptor; + +@Route(value = "generic-filter-api-test-view") +@ViewController("GenericFilterApiTestView") +@ViewDescriptor("generic-filter-api-test-view.xml") +public class GenericFilterApiTestView extends StandardView { + + @ViewComponent + public GenericFilter genericFilter; +} diff --git a/jmix-flowui/flowui/src/test/resources/component/genericfilter/view/generic-filter-api-test-view.xml b/jmix-flowui/flowui/src/test/resources/component/genericfilter/view/generic-filter-api-test-view.xml new file mode 100644 index 0000000000..05b212b193 --- /dev/null +++ b/jmix-flowui/flowui/src/test/resources/component/genericfilter/view/generic-filter-api-test-view.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + +