This is an automated email from the ASF dual-hosted git repository. ahuber pushed a commit to branch 3971-layout.switching in repository https://gitbox.apache.org/repos/asf/causeway.git
commit 39b66b1e48ef76cb7565cb974552e027ca176f02 Author: andi-huber <[email protected]> AuthorDate: Tue Feb 24 08:14:49 2026 +0100 CAUSEWAY-3971: adds reproducer test Task-Url: https://issues.apache.org/jira/browse/CAUSEWAY-3971 --- .../applib/layout/grid/bootstrap/BSUtil.java | 121 +++++++++++++++++++++ .../metamodel/facets/object/grid/BSGridFacet.java | 9 +- .../metamodel/services/grid/Bar-simple.layout.xml | 37 +++++++ .../causeway/core/metamodel/services/grid/Bar.java | 33 ++++++ .../services/grid/LayoutSwitchingTest.java | 65 +++++++++++ .../components/layout/bs/BSGridPanelFactory.java | 5 +- 6 files changed, 262 insertions(+), 8 deletions(-) diff --git a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSUtil.java b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSUtil.java index 9d207ef9da7..9f7782757b4 100644 --- a/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSUtil.java +++ b/api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSUtil.java @@ -32,6 +32,7 @@ import org.apache.causeway.applib.layout.component.FieldSet; import org.apache.causeway.applib.layout.component.PropertyLayoutData; import org.apache.causeway.applib.layout.grid.bootstrap.BSElement.BSElementVisitor; + import lombok.experimental.UtilityClass; @UtilityClass @@ -83,6 +84,126 @@ public BSGrid resolveOwners(final BSGrid grid) { return grid; } + /** + * Useful for debugging or comparing grid instances (e.g. JUnit tests). + */ + public String toYaml(final BSGrid grid) { + class TinyWriter { + StringBuilder sb = new StringBuilder(); + int indent = 0; + void inc() { indent++; } + void dec() { indent--; } + void writeln(final String line) { + sb.append(" ".repeat(indent)).append(line).append("\n"); + } + } + var w = new TinyWriter(); + + grid.visit(new BSElementVisitor() { + @Override public void enter(final BSGrid bsGrid) { + w.writeln("bsGrid:"); + w.inc(); + w.writeln("class: %s".formatted(bsGrid.domainClass().getName())); + } + @Override public void exit(final BSGrid bsGrid) { + w.dec(); + } + @Override public void enter(final BSRow bsRow) { + w.writeln("row:"); + w.inc(); + } + @Override public void exit(final BSRow bsRow) { + w.dec(); + } + @Override public void enter(final BSCol bsCol) { + w.writeln("col:"); + w.inc(); + w.writeln("span: %s".formatted(bsCol.getSpan())); + if(bsCol.isUnreferencedActions()) { + w.writeln("unreferencedActions: true"); + } + if(bsCol.isUnreferencedCollections()) { + w.writeln("unreferencedCollections: true"); + } + } + @Override public void exit(final BSCol bsCol) { + w.dec(); + } + @Override public void enter(final BSTabGroup bsTabGroup) { + w.writeln("tabGroup:"); + w.inc(); + } + @Override public void exit(final BSTabGroup bsTabGroup) { + w.dec(); + } + @Override public void enter(final BSTab bsTab) { + w.writeln("tab:"); + w.inc(); + w.writeln("name: %s".formatted(bsTab.getName())); + } + @Override public void exit(final BSTab bsTab) { + w.dec(); + } + + @Override public void visit(final BSClearFix bsClearFix) {} + @Override public void visit(final DomainObjectLayoutData domainObjectLayoutData) { + w.inc(); + w.writeln("domainObject:"); + w.inc(); + w.writeln("named: %s".formatted(domainObjectLayoutData.getNamed())); + w.writeln("bookmarking: %s".formatted(domainObjectLayoutData.getBookmarking())); + w.dec(); + w.dec(); + } + @Override public void visit(final ActionLayoutData actionLayoutData) { + w.inc(); + w.writeln("action:"); + w.inc(); + w.writeln("id: %s".formatted(actionLayoutData.getId())); + w.writeln("named: %s".formatted(actionLayoutData.getNamed())); + w.writeln("position: %s".formatted(actionLayoutData.getPosition())); + w.writeln("promptStyle: %s".formatted(actionLayoutData.getPromptStyle())); + w.writeln("hidden: %s".formatted(actionLayoutData.getHidden())); + w.dec(); + w.dec(); + } + @Override public void visit(final PropertyLayoutData propertyLayoutData) { + w.inc(); + w.writeln("property:"); + w.inc(); + w.writeln("id: %s".formatted(propertyLayoutData.getId())); + w.writeln("named: %s".formatted(propertyLayoutData.getNamed())); + w.writeln("hidden: %s".formatted(propertyLayoutData.getHidden())); + w.dec(); + w.dec(); + } + @Override public void visit(final CollectionLayoutData collectionLayoutData) { + w.inc(); + w.writeln("collection:"); + w.inc(); + w.writeln("id: %s".formatted(collectionLayoutData.getId())); + w.writeln("named: %s".formatted(collectionLayoutData.getNamed())); + w.writeln("hidden: %s".formatted(collectionLayoutData.getHidden())); + w.dec(); + w.dec(); + } + @Override public void visit(final FieldSet fieldSet) { + w.writeln("fieldSet:"); + w.inc(); + w.writeln("id: %s".formatted(fieldSet.getId())); + w.writeln("name: %s".formatted(fieldSet.getName())); + if(fieldSet.isUnreferencedActions()) { + w.writeln("unreferencedActions: true"); + } + if(fieldSet.isUnreferencedProperties()) { + w.writeln("unreferencedProperties: true"); + } + w.dec(); + } + }); + return w.sb.toString(); + } + // -- REMOVERS /** removes the tab from its owner and returns the owner */ diff --git a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/grid/BSGridFacet.java b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/grid/BSGridFacet.java index 84866b2486c..e37944dbc06 100644 --- a/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/grid/BSGridFacet.java +++ b/core/metamodel/src/main/java/org/apache/causeway/core/metamodel/facets/object/grid/BSGridFacet.java @@ -62,6 +62,9 @@ public static GridFacet create( @Override public BSGrid getGrid(final @Nullable ManagedObject mo) { + if(mo!=null) { + System.err.println("get grid for %s: %s".formatted(layoutPrefixFor(mo), mo)); + } guardAgainstObjectOfDifferentType(mo); return normalized(mo); } @@ -87,21 +90,19 @@ private BSGrid normalized(final @Nullable ManagedObject mo) { private void guardAgainstObjectOfDifferentType(final @Nullable ManagedObject objectAdapter) { if(ManagedObjects.isNullOrUnspecifiedOrEmpty(objectAdapter)) return; // cannot introspect - if(!objSpec().equals(objectAdapter.objSpec())) { + if(!objSpec().equals(objectAdapter.objSpec())) throw _Exceptions.unrecoverable( "getGrid(adapter) was called passing an adapter (type: %s), " + "for which this GridFacet (type: %s) is not responsible; " + "indicates that some framework internals are wired up in a wrong way", objectAdapter.objSpec().getCorrespondingClass().getName(), objSpec().getCorrespondingClass().getName()); - } } private String layoutPrefixFor(final @Nullable ManagedObject objectAdapter) { if(ManagedObjects.isNullOrUnspecifiedOrEmpty(objectAdapter) - || !hasLayoutPrefixFacet()) { + || !hasLayoutPrefixFacet()) return ""; - } var layoutName = _Strings.nullToEmpty(layoutFacetLazy.get().layoutPrefix(objectAdapter)); return layoutName; } diff --git a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/Bar-simple.layout.xml b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/Bar-simple.layout.xml new file mode 100644 index 00000000000..ee709cc628e --- /dev/null +++ b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/Bar-simple.layout.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you 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. +--> +<bs:grid xsi:schemaLocation="https://causeway.apache.org/applib/layout/component https://causeway.apache.org/applib/layout/component/component.xsd https://causeway.apache.org/applib/layout/links https://causeway.apache.org/applib/layout/links/links.xsd https://causeway.apache.org/applib/layout/grid/bootstrap3 https://causeway.apache.org/applib/layout/grid/bootstrap3/bootstrap3.xsd" xmlns:bs="https://causeway.apache.org/applib/layout/grid/bootstrap3" xmlns:cpt="https://causeway.apache.org [...] + <bs:row> + <bs:col span="12" unreferencedActions="true"> + <cpt:domainObject/> + <cpt:action id="createSimpleObject" hidden="EVERYWHERE"/> + </bs:col> + </bs:row> + <bs:row> + <bs:col span="4"> + <cpt:fieldSet name="General" unreferencedProperties="true"> + <cpt:property id="name" hidden="EVERYWHERE"/> + </cpt:fieldSet> + </bs:col> + <bs:col span="12" unreferencedCollections="true"> + <cpt:collection id="sampleCollection" hidden="EVERYWHERE"/> + </bs:col> + </bs:row> +</bs:grid> diff --git a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/Bar.java b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/Bar.java index 881913d1812..27b8617046a 100644 --- a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/Bar.java +++ b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/Bar.java @@ -18,20 +18,53 @@ */ package org.apache.causeway.core.metamodel.services.grid; +import java.util.List; + import jakarta.inject.Named; import org.apache.causeway.applib.annotation.Action; +import org.apache.causeway.applib.annotation.Collection; import org.apache.causeway.applib.annotation.DomainObject; import org.apache.causeway.applib.annotation.Nature; +import org.apache.causeway.applib.annotation.ObjectSupport; +import org.apache.causeway.applib.annotation.Property; + +import lombok.RequiredArgsConstructor; @Named("simple.Bar") @DomainObject( nature = Nature.VIEW_MODEL) +@RequiredArgsConstructor public class Bar { + private final String name; + + public Bar() { + this.name = "Bar"; + } + + @ObjectSupport + public String layout() { + return name.toLowerCase().contains("simple") + ? "simple" + : name.toLowerCase().contains("full") + ? "full" + : null; + } + @Action public Object createSimpleObject() { return new Object(); } + @Property + public String getName() { + return name; + } + + @Collection + public List<String> getSampleCollection() { + return List.of("Foo", "Bar"); + } + } \ No newline at end of file diff --git a/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/LayoutSwitchingTest.java b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/LayoutSwitchingTest.java new file mode 100644 index 00000000000..ff69b9dd04f --- /dev/null +++ b/core/mmtest/src/test/java/org/apache/causeway/core/metamodel/services/grid/LayoutSwitchingTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.causeway.core.metamodel.services.grid; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import org.apache.causeway.applib.layout.grid.bootstrap.BSUtil; +import org.apache.causeway.applib.services.grid.GridService; +import org.apache.causeway.core.metamodel.MetaModelTestAbstract; +import org.apache.causeway.core.metamodel.facets.object.grid.GridFacet; +import org.apache.causeway.core.metamodel.object.ManagedObject; + +/** + * Switching between Layout Variants may result in Members staying hidden. + * https://issues.apache.org/jira/browse/CAUSEWAY-3971 + */ +class LayoutSwitchingTest extends MetaModelTestAbstract { + + private GridServiceDefault gridService; + + @Override + protected void afterSetUp() { + this.gridService = ((GridServiceDefault) getServiceRegistry() + .lookupServiceElseFail(GridService.class)); + assertFalse(gridService.supportsReloading()); + } + + @Test + void switchLayout() { + + var barSpec = getSpecificationLoader().specForTypeElseFail(Bar.class); + + var gridFacet = barSpec.getFacet(GridFacet.class); + assertNotNull(gridFacet); + + // triggers grid to be loaded (if initial or reloading supported) + var bsGrid = gridFacet.getGrid(ManagedObject.adaptSingular(barSpec, new Bar("simple"))); + assertNotNull(bsGrid); + + var bsGridDefault = gridFacet.getGrid(ManagedObject.adaptSingular(barSpec, new Bar())); + System.err.println(BSUtil.toYaml(bsGrid)); // we don't expect too see any hidden members but there are! + //TODO fail on seeing hidden members + } + + +} diff --git a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/BSGridPanelFactory.java b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/BSGridPanelFactory.java index 0b61457e266..6d093584e34 100644 --- a/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/BSGridPanelFactory.java +++ b/viewers/wicket/ui/src/main/java/org/apache/causeway/viewer/wicket/ui/components/layout/bs/BSGridPanelFactory.java @@ -54,12 +54,9 @@ public BSGridPanelFactory() { @Override public Component createComponent(final String id, final IModel<?> model) { final UiObjectWkt objectModel = (UiObjectWkt) model; - var mo = objectModel.getObject(); - var objectSpec = objectModel.getTypeOfSpecification(); - return Facets.bootstrapGrid(objectSpec, mo) - .map(UiGridLayout::new) // creates a deep copy of the underlying grid and applies rules + return UiGridLayout.forObject(mo) // creates a deep copy of the underlying grid and applies rules .map(ui->new BSGridPanel(id, objectModel, ui.bsGrid())) .orElseThrow(); // empty case guarded against by appliesTo(...) above }
