diff --git a/README.md b/README.md index c87cf9f1..fdf38d6e 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ This is the relevant dependency: org.vaadin.miki superfields - 0.14.0 + 0.14.1 ``` diff --git a/demo-v23/pom.xml b/demo-v23/pom.xml index fcf7aeaa..23b2bcbe 100644 --- a/demo-v23/pom.xml +++ b/demo-v23/pom.xml @@ -4,11 +4,11 @@ superfields-parent org.vaadin.miki - 0.14.0 + 0.14.1 superfields-demo-v23 - 0.14.0 + 0.14.1 V23+ demo app for SuperFields Showcase application for V23+ and SuperFields. war @@ -23,7 +23,7 @@ org.vaadin.miki superfields - 0.14.0 + 0.14.1 javax.servlet diff --git a/demo-v23/src/main/java/org/vaadin/miki/demo/builders/ObjectFieldBuilder.java b/demo-v23/src/main/java/org/vaadin/miki/demo/builders/ObjectFieldBuilder.java index 556f9cee..293364b6 100644 --- a/demo-v23/src/main/java/org/vaadin/miki/demo/builders/ObjectFieldBuilder.java +++ b/demo-v23/src/main/java/org/vaadin/miki/demo/builders/ObjectFieldBuilder.java @@ -5,6 +5,7 @@ import org.vaadin.miki.demo.ContentBuilder; import org.vaadin.miki.demo.Order; import org.vaadin.miki.demo.data.Book; +import org.vaadin.miki.demo.data.Format; import org.vaadin.miki.demo.data.Person; import org.vaadin.miki.superfields.object.ObjectField; @@ -16,9 +17,9 @@ public class ObjectFieldBuilder implements ContentBuilder> { @Override public void buildContent(ObjectField component, Consumer callback) { final ComboBox values = new ComboBox<>("Select a value: ", - Book.of("1984", 1948, "English", Person.of("George Orwell", LocalDate.of(1903, 6, 25), false)), - Book.of("The God Delusion", 2006, "English", Person.of("Richard Dawkins", LocalDate.of(1941, 3, 26), false)), - Book.of("Dolina Issy", 1955, "polski", Person.of("Czesław Miłosz", LocalDate.of(1911, 6, 30), true)) + Book.of("1984", 1948, "English", Format.SOFT_COVER, Person.of("George Orwell", LocalDate.of(1903, 6, 25), false)), + Book.of("The God Delusion", 2006, "English", Format.HARD_COVER, Person.of("Richard Dawkins", LocalDate.of(1941, 3, 26), false)), + Book.of("Dolina Issy", 1955, "polski", null, Person.of("Czesław Miłosz", LocalDate.of(1911, 6, 30), true)) ); values.addValueChangeListener(event -> component.setValue(event.getValue())); values.setAllowCustomValue(false); diff --git a/demo-v23/src/main/java/org/vaadin/miki/demo/data/Book.java b/demo-v23/src/main/java/org/vaadin/miki/demo/data/Book.java index ea26ca79..b9c9cef8 100644 --- a/demo-v23/src/main/java/org/vaadin/miki/demo/data/Book.java +++ b/demo-v23/src/main/java/org/vaadin/miki/demo/data/Book.java @@ -16,12 +16,13 @@ */ public class Book { - public static Book of(String title, int firstPublished, String language, Person... authors) { + public static Book of(String title, int firstPublished, String language, Format ownedFormat, Person... authors) { final Book result = new Book(); result.setAuthors(Arrays.asList(authors)); result.setTitle(title); result.setFirstPublished(firstPublished); result.setLanguage(language); + result.setOwnedFormat(ownedFormat); return result; } @@ -40,6 +41,10 @@ public static Book of(String title, int firstPublished, String language, Person. @FieldCaption("Original language") private String language; + @FieldOrder(6) + @FieldGroup("publication") + private Format ownedFormat; + @BigField @FieldOrder(5) private String summary = ""; @@ -84,17 +89,25 @@ public void setSummary(String summary) { this.summary = summary; } + public Format getOwnedFormat() { + return ownedFormat; + } + + public void setOwnedFormat(Format ownedFormat) { + this.ownedFormat = ownedFormat; + } + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Book book = (Book) o; - return getFirstPublished() == book.getFirstPublished() && Objects.equals(getTitle(), book.getTitle()) && Objects.equals(getAuthors(), book.getAuthors()) && Objects.equals(getLanguage(), book.getLanguage()) && Objects.equals(getSummary(), book.getSummary()); + return getFirstPublished() == book.getFirstPublished() && Objects.equals(getTitle(), book.getTitle()) && Objects.equals(getAuthors(), book.getAuthors()) && Objects.equals(getLanguage(), book.getLanguage()) && getOwnedFormat() == book.getOwnedFormat() && Objects.equals(getSummary(), book.getSummary()); } @Override public int hashCode() { - return Objects.hash(getTitle(), getAuthors(), getFirstPublished(), getLanguage(), getSummary()); + return Objects.hash(getTitle(), getAuthors(), getFirstPublished(), getLanguage(), getOwnedFormat(), getSummary()); } @Override @@ -104,6 +117,7 @@ public String toString() { ", authors=" + authors + ", firstPublished=" + firstPublished + ", language='" + language + '\'' + + ", ownedFormat=" + ownedFormat + ", summary='" + summary + '\'' + '}'; } diff --git a/demo-v23/src/main/java/org/vaadin/miki/demo/data/Format.java b/demo-v23/src/main/java/org/vaadin/miki/demo/data/Format.java new file mode 100644 index 00000000..789da27e --- /dev/null +++ b/demo-v23/src/main/java/org/vaadin/miki/demo/data/Format.java @@ -0,0 +1,7 @@ +package org.vaadin.miki.demo.data; + +public enum Format { + + HARD_COVER, SOFT_COVER, AUDIO_BOOK + +} diff --git a/pom.xml b/pom.xml index e7afff3d..b481c6c1 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.vaadin.miki superfields-parent - 0.14.0 + 0.14.1 superfields demo-v23 diff --git a/superfields/README.md b/superfields/README.md index ebe05635..d456a11a 100644 --- a/superfields/README.md +++ b/superfields/README.md @@ -110,7 +110,11 @@ A `CustomField`. It checks the type of the value passed to it and attemp A `CustomField` capable of building components and matching them with object's properties. Once configured it is basically an automated form generator. This component is highly configurable and details on how to use it are present in [the project's wiki](https://github.com/vaadin-miki/super-fields/wiki). -In most common cases one would use `ObjectFieldFactory` to create and configure `ObjectField`, and then annotate data model class with the additional information. Please consult the demo application or the tests (`ObjectFieldTest` and `NestedObjectFieldTest`) for details. +In most common cases one would use `ObjectFieldFactory` to create and configure `ObjectField`, and then annotate data model class with the additional information. Please consult the demo application or the tests (`ObjectFieldTest`, `NestedObjectFieldTest` and `EnumObjectTest`) for details. Also please note that [`ObjectFieldFactory` will become a separate library](https://github.com/vaadin-miki/super-fields/issues/401) at some point in the future. + +### `SuperCheckbox` + +It is known that [`Checkbox` does not support read-only mode](https://github.com/vaadin/web-components/issues/688). This component exists as a workaround and binds `enabled` and `readOnly` as one: setting the checkbox to read-only will disable it. ## Select fields diff --git a/superfields/pom.xml b/superfields/pom.xml index 7eedbd99..2b08f9aa 100644 --- a/superfields/pom.xml +++ b/superfields/pom.xml @@ -8,7 +8,7 @@ superfields SuperFields Code for various V14+ fields and other components. - 0.14.0 + 0.14.1 11 diff --git a/superfields/release-notes.md b/superfields/release-notes.md index 5d5074a1..2984c935 100644 --- a/superfields/release-notes.md +++ b/superfields/release-notes.md @@ -1,3 +1,13 @@ +# 0.14.1 - SuperCheckbox and bugfixes +## New features and enhancements +* \#400 - [ObjectField should support enums and other values presentable with ComboBox](https://github.com/vaadin-miki/super-fields/issues/400) +* \#404 - [Checkbox that actually can be made read-only](https://github.com/vaadin-miki/super-fields/issues/404) +## Changes to API +* \#406 - [ObjectFieldFactory should return interface types](https://github.com/vaadin-miki/super-fields/issues/406) +## Bug fixes +* \#402 - [HasReadOnly disables layouts](https://github.com/vaadin-miki/super-fields/issues/402) +* \#403 - [Component added to read-only layout is not read-only](https://github.com/vaadin-miki/super-fields/issues/403) +* \#406 - [ObjectFieldFactory should return interface types](https://github.com/vaadin-miki/super-fields/issues/406) # 0.14.0 - ObjectField ## New features and enhancements * \#380 - [ObjectField](https://github.com/vaadin-miki/super-fields/issues/380) diff --git a/superfields/src/main/java/org/vaadin/miki/markers/HasReadOnly.java b/superfields/src/main/java/org/vaadin/miki/markers/HasReadOnly.java index d2a2e910..7cd218ca 100644 --- a/superfields/src/main/java/org/vaadin/miki/markers/HasReadOnly.java +++ b/superfields/src/main/java/org/vaadin/miki/markers/HasReadOnly.java @@ -1,6 +1,7 @@ package org.vaadin.miki.markers; import com.vaadin.flow.component.Component; +import com.vaadin.flow.component.HasComponents; import com.vaadin.flow.component.HasEnabled; import com.vaadin.flow.component.HasValue; @@ -29,7 +30,7 @@ public interface HasReadOnly { * @param readOnly New state. * @param component Component. * If it implements {@link HasReadOnly} or {@link HasValue}, the state will be updated. - * If it implements {@link HasEnabled}, read-only means disabled. + * If it implements {@link HasEnabled} and does not implement {@link HasComponents}, read-only means disabled. * Otherwise, nothing happens. */ static void setReadOnly(boolean readOnly, Component component) { @@ -37,7 +38,7 @@ static void setReadOnly(boolean readOnly, Component component) { ((HasReadOnly) component).setReadOnly(readOnly); else if(component instanceof HasValue) ((HasValue) component).setReadOnly(readOnly); - else if(component instanceof HasEnabled) + else if(component instanceof HasEnabled && !(component instanceof HasComponents)) // HasComponents implements HasEnabled, that caused #402 ((HasEnabled) component).setEnabled(!readOnly); // delegate to children component.getChildren().forEach(child -> setReadOnly(readOnly, child)); diff --git a/superfields/src/main/java/org/vaadin/miki/superfields/checkbox/SuperCheckbox.java b/superfields/src/main/java/org/vaadin/miki/superfields/checkbox/SuperCheckbox.java new file mode 100644 index 00000000..b4194a20 --- /dev/null +++ b/superfields/src/main/java/org/vaadin/miki/superfields/checkbox/SuperCheckbox.java @@ -0,0 +1,26 @@ +package org.vaadin.miki.superfields.checkbox; + +import com.vaadin.flow.component.checkbox.Checkbox; + +/** + * A regular {@link Checkbox} that has its read-only state synchronised with enabledness. + * This exists purely as a workaround for a known issue of Vaadin. + * + * @author miki + * @since 2022-09-14 + */ +@SuppressWarnings("squid:S110") // no way around big number of parent classes +public class SuperCheckbox extends Checkbox { + + @Override + public void setReadOnly(boolean readOnly) { + super.setReadOnly(readOnly); + super.setEnabled(!readOnly); + } + + @Override + public void setEnabled(boolean enabled) { + super.setReadOnly(!enabled); + super.setEnabled(enabled); + } +} diff --git a/superfields/src/main/java/org/vaadin/miki/superfields/layouts/HeaderFooterLayoutWrapper.java b/superfields/src/main/java/org/vaadin/miki/superfields/layouts/HeaderFooterLayoutWrapper.java index c92fffdd..dbc62f54 100644 --- a/superfields/src/main/java/org/vaadin/miki/superfields/layouts/HeaderFooterLayoutWrapper.java +++ b/superfields/src/main/java/org/vaadin/miki/superfields/layouts/HeaderFooterLayoutWrapper.java @@ -14,7 +14,6 @@ /** * A wrapper for a typical header-body-footer layout that exposes header and footer, and delegates all * methods from {@link HasComponents} to the body. In other words, it allows using predefined layouts. - * * This component is {@link Iterable} over the {@link Component}s contained in the body. * Similarly, {@link #getComponents()} ()} returns {@link Component}s things put in the body. * @@ -64,13 +63,21 @@ protected R initContent() { return this.root; } + private void ensureReadOnly(Component... components) { + if(this.isReadOnly()) + for (Component component : components) + HasReadOnly.setReadOnly(true, component); + } + @Override public void add(Component... components) { + this.ensureReadOnly(components); this.body.add(components); } @Override public void addComponentAtIndex(int index, Component component) { + this.ensureReadOnly(component); this.body.addComponentAtIndex(index, component); } diff --git a/superfields/src/main/java/org/vaadin/miki/superfields/util/factory/MetadataProperties.java b/superfields/src/main/java/org/vaadin/miki/superfields/util/factory/MetadataProperties.java index 4994a1b6..e1999e24 100644 --- a/superfields/src/main/java/org/vaadin/miki/superfields/util/factory/MetadataProperties.java +++ b/superfields/src/main/java/org/vaadin/miki/superfields/util/factory/MetadataProperties.java @@ -21,6 +21,7 @@ public class MetadataProperties { public static final String COMPONENT_BUILDER_METADATA_PROPERTY = "build-with"; public static final String COMPONENT_STYLE_METADATA_PROPERTY = "component-style-name"; public static final String COMPONENT_ID_METADATA_PROPERTY = "component-id"; + public static final String AVAILABLE_ITEMS_METADATA_PROPERTY = "available-items"; private MetadataProperties() { // no instances allowed diff --git a/superfields/src/main/java/org/vaadin/miki/superfields/util/factory/ObjectFieldFactory.java b/superfields/src/main/java/org/vaadin/miki/superfields/util/factory/ObjectFieldFactory.java index 31e0824d..3f5d1841 100644 --- a/superfields/src/main/java/org/vaadin/miki/superfields/util/factory/ObjectFieldFactory.java +++ b/superfields/src/main/java/org/vaadin/miki/superfields/util/factory/ObjectFieldFactory.java @@ -5,10 +5,12 @@ import com.vaadin.flow.component.HasLabel; import com.vaadin.flow.component.HasStyle; import com.vaadin.flow.component.HasValue; -import com.vaadin.flow.component.checkbox.Checkbox; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.combobox.MultiSelectComboBox; import com.vaadin.flow.function.SerializableSupplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.vaadin.miki.superfields.checkbox.SuperCheckbox; import org.vaadin.miki.superfields.collections.CollectionController; import org.vaadin.miki.superfields.collections.CollectionField; import org.vaadin.miki.superfields.collections.CollectionLayoutProvider; @@ -155,7 +157,7 @@ public MapEntryField provideComponent(int index, CollectionController cont /** * Builds a {@link SimplePropertyComponentBuilder} and configures it for building most default components:
    *
  • properties marked with {@link MetadataProperties#SHOW_AS_COMPONENT_METADATA_PROPERTY} as requested
  • - *
  • boolean properties as {@link Checkbox}
  • + *
  • boolean properties as {@link SuperCheckbox}
  • *
  • integer properties as {@link SuperIntegerField}
  • *
  • long properties as {@link SuperLongField}
  • *
  • double properties as {@link SuperDoubleField}
  • @@ -170,7 +172,7 @@ public MapEntryField provideComponent(int index, CollectionController cont * @return A {@link SimplePropertyComponentBuilder}. */ @SuppressWarnings("unchecked") - protected SimplePropertyComponentBuilder buildAndConfigureComponentBuilder() { + protected PropertyComponentBuilder buildAndConfigureComponentBuilder() { final SimplePropertyComponentBuilder result = new SimplePropertyComponentBuilder() .withoutDefaultLabel() .withRegisteredBuilder( @@ -184,8 +186,22 @@ protected SimplePropertyComponentBuilder buildAndConfigureComponentBuilder() { // here is a builder that delegates to another builder property -> ((FieldBuilder)ReflectTools.newInstance((Class) property.getMetadata().get(MetadataProperties.COMPONENT_BUILDER_METADATA_PROPERTY).getValue())).buildPropertyField(property) ) - .withRegisteredType(Boolean.class, Checkbox::new) - .withRegisteredType(boolean.class, Checkbox::new) + .withRegisteredBuilder( + // multi-selection combobox only works for enums + property -> property.getMetadata().containsKey(MetadataProperties.AVAILABLE_ITEMS_METADATA_PROPERTY) + && property.getMetadata().get(MetadataProperties.AVAILABLE_ITEMS_METADATA_PROPERTY).hasValueOfType(Collection.class) + && property.getMetadata().containsKey(MetadataProperties.COLLECTION_ELEMENT_TYPE_METADATA_PROPERTY) + && property.getMetadata().get(MetadataProperties.COLLECTION_ELEMENT_TYPE_METADATA_PROPERTY).hasValueOfType(Property.class) + && ((Property) property.getMetadata().get(MetadataProperties.COLLECTION_ELEMENT_TYPE_METADATA_PROPERTY).getValue()).getType().isEnum(), + property -> ((HasValue) (HasValue) new MultiSelectComboBox<>("", (List)property.getMetadata().get(MetadataProperties.AVAILABLE_ITEMS_METADATA_PROPERTY).getValue())) + ) + .withRegisteredBuilder( + // single-selection combobox may be rendered before any other type (allowing to have a drop-down for String items, for example) + property -> property.getMetadata().containsKey(MetadataProperties.AVAILABLE_ITEMS_METADATA_PROPERTY) && property.getMetadata().get(MetadataProperties.AVAILABLE_ITEMS_METADATA_PROPERTY).hasValueOfType(Collection.class) && !property.getMetadata().containsKey(MetadataProperties.COLLECTION_ELEMENT_TYPE_METADATA_PROPERTY), + property -> new ComboBox<>("", (List)property.getMetadata().get(MetadataProperties.AVAILABLE_ITEMS_METADATA_PROPERTY).getValue()) + ) + .withRegisteredType(Boolean.class, SuperCheckbox::new) + .withRegisteredType(boolean.class, SuperCheckbox::new) .withRegisteredType(Integer.class, SuperIntegerField::new) .withRegisteredType(int.class, SuperIntegerField::new) .withRegisteredType(Long.class, SuperLongField::new) @@ -226,6 +242,10 @@ protected SimplePropertyComponentBuilder buildAndConfigureComponentBuilder() { return result; } + private Set getEnumMetadata(Class type) { + return type.isEnum() ? Collections.singleton(new PropertyMetadata(MetadataProperties.AVAILABLE_ITEMS_METADATA_PROPERTY, List.class, Arrays.asList(type.getEnumConstants()))) : Collections.emptySet(); + } + /** * Builds a {@link ReflectivePropertyProvider} and configures it to a typical use case based on annotations:
      *
    • {@link FieldGroup} is mapped to {@link MetadataProperties#GROUP_METADATA_PROPERTY}
    • @@ -238,7 +258,7 @@ protected SimplePropertyComponentBuilder buildAndConfigureComponentBuilder() { * In addition fields without a setter are marked with {@link MetadataProperties#READ_ONLY_METADATA_PROPERTY}, and collections and maps using {@link MetadataProperties#COLLECTION_ELEMENT_TYPE_METADATA_PROPERTY}, {@link MetadataProperties#MAP_KEY_TYPE_METADATA_PROPERTY} and {@link MetadataProperties#MAP_VALUE_TYPE_METADATA_PROPERTY}. * @return A {@link ReflectivePropertyProvider}. */ - protected ReflectivePropertyProvider buildAndConfigurePropertyProvider() { + protected PropertyProvider buildAndConfigurePropertyProvider() { return new ReflectivePropertyProvider().withMetadataProvider(new AnnotationMetadataProvider() .withRegisteredAnnotation(MetadataProperties.GROUP_METADATA_PROPERTY, FieldGroup.class, String.class, FieldGroup::value) .withRegisteredAnnotation(MetadataProperties.ORDER_METADATA_PROPERTY, FieldOrder.class, int.class, FieldOrder::value) @@ -251,18 +271,26 @@ protected ReflectivePropertyProvider buildAndConfigurePropertyProvider() { , // mark fields as read-only when there is no setter (name, field, setter, getter) -> setter == null ? Collections.singleton(new PropertyMetadata(MetadataProperties.READ_ONLY_METADATA_PROPERTY, boolean.class, true)) : Collections.emptySet(), + // metadata for enums + (name, field, setter, getter) -> this.getEnumMetadata(field.getType()), // metadata for collections (name, field, setter, getter) -> { if(Collection.class.isAssignableFrom(field.getType())) - return ReflectTools.extractGenericType(field, 0).map(type -> new PropertyMetadata(MetadataProperties.COLLECTION_ELEMENT_TYPE_METADATA_PROPERTY, Property.class, new Property<>(field.getDeclaringClass(), name, type, null, null))).map(Collections::singleton).orElse(Collections.emptySet()); + return ReflectTools.extractGenericType(field, 0).map(type -> { + final Set result = new LinkedHashSet<>(); + result.add(new PropertyMetadata(MetadataProperties.COLLECTION_ELEMENT_TYPE_METADATA_PROPERTY, Property.class, new Property<>(field.getDeclaringClass(), name, type, null, null))); + // if the type of the collection is an enum, prepare enum data as well + result.addAll(this.getEnumMetadata(type)); + return result; + }).orElse(Collections.emptySet()); else return Collections.emptySet(); }, // metadata for maps (name, field, setter, getter) -> { if(Map.class.isAssignableFrom(field.getType())) { final List metadata = new ArrayList<>(); - ReflectTools.extractGenericType(field, 0).map(type -> new PropertyMetadata(MetadataProperties.MAP_KEY_TYPE_METADATA_PROPERTY, Property.class, new Property<>(field.getDeclaringClass(), name, type, null, null))).ifPresent(metadata::add); - ReflectTools.extractGenericType(field, 1).map(type -> new PropertyMetadata(MetadataProperties.MAP_VALUE_TYPE_METADATA_PROPERTY, Property.class, new Property<>(field.getDeclaringClass(), name, type, null, null))).ifPresent(metadata::add); + ReflectTools.extractGenericType(field, 0).map(type -> new PropertyMetadata(MetadataProperties.MAP_KEY_TYPE_METADATA_PROPERTY, Property.class, new Property<>(field.getDeclaringClass(), name, type, null, null, this.getEnumMetadata(type)))).ifPresent(metadata::add); + ReflectTools.extractGenericType(field, 1).map(type -> new PropertyMetadata(MetadataProperties.MAP_VALUE_TYPE_METADATA_PROPERTY, Property.class, new Property<>(field.getDeclaringClass(), name, type, null, null, this.getEnumMetadata(type)))).ifPresent(metadata::add); return metadata.size() == 2 ? metadata : Collections.emptySet(); } else return Collections.emptySet(); @@ -274,7 +302,7 @@ protected ReflectivePropertyProvider buildAndConfigurePropertyProvider() { * Builds a {@link PropertyGroupingProvider} based on presence of {@link MetadataProperties#GROUP_METADATA_PROPERTY} and {@link MetadataProperties#ORDER_METADATA_PROPERTY}. * @return A {@link MetadataBasedGroupingProvider}. */ - protected MetadataBasedGroupingProvider buildAndConfigureGroupingProvider() { + protected PropertyGroupingProvider buildAndConfigureGroupingProvider() { return new MetadataBasedGroupingProvider() .withGroupingMetadataName(MetadataProperties.GROUP_METADATA_PROPERTY) .withSortingMetadataName(MetadataProperties.ORDER_METADATA_PROPERTY); diff --git a/superfields/src/test/java/org/vaadin/miki/markers/HasReadOnlyTest.java b/superfields/src/test/java/org/vaadin/miki/markers/HasReadOnlyTest.java new file mode 100644 index 00000000..f572823f --- /dev/null +++ b/superfields/src/test/java/org/vaadin/miki/markers/HasReadOnlyTest.java @@ -0,0 +1,54 @@ +package org.vaadin.miki.markers; + +import com.github.mvysny.kaributesting.v10.MockVaadin; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.orderedlayout.FlexLayout; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.vaadin.miki.superfields.layouts.FlexLayoutHelpers; +import org.vaadin.miki.superfields.text.SuperTextArea; + +// tests for #402 +public class HasReadOnlyTest { + + @Before + public void setup() { + MockVaadin.setup(); + } + + @After + public void tearDown() { + MockVaadin.tearDown(); + } + + @Test + public void testComponentReadOnlyInLayout() { + final FlexLayout layout = FlexLayoutHelpers.row(); + final SuperTextArea area = new SuperTextArea(); + layout.add(area); + Assert.assertTrue(area.isEnabled()); + HasReadOnly.setReadOnly(true, layout); + // must be read-only + Assert.assertTrue(area.isReadOnly()); + // but must also be enabled + Assert.assertTrue(area.isEnabled()); + // layout itself also should be enabled + Assert.assertTrue(layout.isEnabled()); + } + + @Test + public void testComponentDisabledInLayout() { + final FlexLayout layout = FlexLayoutHelpers.row(); + final Button area = new Button(); + layout.add(area); + Assert.assertTrue(area.isEnabled()); + HasReadOnly.setReadOnly(true, layout); + // cannot be read-only, so must be disabled + Assert.assertFalse(area.isEnabled()); + // layout itself also should be enabled + Assert.assertTrue(layout.isEnabled()); + } + +} \ No newline at end of file diff --git a/superfields/src/test/java/org/vaadin/miki/superfields/checkbox/SuperCheckboxTest.java b/superfields/src/test/java/org/vaadin/miki/superfields/checkbox/SuperCheckboxTest.java new file mode 100644 index 00000000..ca592b62 --- /dev/null +++ b/superfields/src/test/java/org/vaadin/miki/superfields/checkbox/SuperCheckboxTest.java @@ -0,0 +1,34 @@ +package org.vaadin.miki.superfields.checkbox; + +import com.github.mvysny.kaributesting.v10.MockVaadin; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class SuperCheckboxTest { + + private SuperCheckbox checkbox; + + @Before + public void setup() { + MockVaadin.setup(); + this.checkbox = new SuperCheckbox(); + } + + @After + public void teardown() { + MockVaadin.tearDown(); + } + + @Test + public void testReadOnlyAndEnabled() { + this.checkbox.setReadOnly(true); + Assert.assertTrue(this.checkbox.isReadOnly()); + Assert.assertFalse(this.checkbox.isEnabled()); + this.checkbox.setEnabled(true); + Assert.assertTrue(this.checkbox.isEnabled()); + Assert.assertFalse(this.checkbox.isReadOnly()); + } + +} \ No newline at end of file diff --git a/superfields/src/test/java/org/vaadin/miki/superfields/layouts/HeaderFooterLayoutWrapperTest.java b/superfields/src/test/java/org/vaadin/miki/superfields/layouts/HeaderFooterLayoutWrapperTest.java new file mode 100644 index 00000000..b74f6a18 --- /dev/null +++ b/superfields/src/test/java/org/vaadin/miki/superfields/layouts/HeaderFooterLayoutWrapperTest.java @@ -0,0 +1,78 @@ +package org.vaadin.miki.superfields.layouts; + +import com.github.mvysny.kaributesting.v10.MockVaadin; +import com.vaadin.flow.component.HasValue; +import com.vaadin.flow.component.button.Button; +import com.vaadin.flow.component.orderedlayout.FlexLayout; +import com.vaadin.flow.component.textfield.TextArea; +import com.vaadin.flow.component.textfield.TextField; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.vaadin.miki.superfields.numbers.SuperIntegerField; + +import java.util.Arrays; +import java.util.stream.Stream; + +// tests for #403 +public class HeaderFooterLayoutWrapperTest { + + @Before + public void setup() { + MockVaadin.setup(); + } + + @After + public void tearDown() { + MockVaadin.tearDown(); + } + + @Test + public void addingComponentToReadOnlyMakesReadOnly() { + final HeaderFooterLayoutWrapper layout = FlexLayoutHelpers.columnWithHeaderRowAndFooterRow(); + final Button button = new Button("this should be disabled"); + final TextArea area = new TextArea("this should be readonly, but enabled"); + layout.add(button, area); + layout.setReadOnly(true); + Assert.assertFalse(button.isEnabled()); + Assert.assertTrue(area.isEnabled()); + Assert.assertTrue(area.isReadOnly()); + // adding more things + final Button newButton = new Button("this should be turned into disabled"); + final TextField textField = new TextField("this should be turned into readonly"); + layout.add(newButton, textField); + Assert.assertFalse(button.isEnabled()); + Assert.assertTrue(textField.isReadOnly()); + Assert.assertTrue(textField.isEnabled()); + // now a similar trick, but with a layout + final FlexLayout nested = FlexLayoutHelpers.column(); + final Button another = new Button("yet another"); + final SuperIntegerField integerField = new SuperIntegerField(); + nested.add(another, integerField); + layout.add(nested); + Assert.assertTrue(nested.isEnabled()); + Assert.assertFalse(another.isEnabled()); + Assert.assertTrue(integerField.isEnabled()); + Assert.assertTrue(integerField.isReadOnly()); + // now switch off read-only + layout.setReadOnly(false); + Stream.of(button, newButton, another, nested).forEach(b -> Assert.assertTrue(b.isEnabled())); + for(HasValue f: Arrays.asList(area, textField, integerField)) + Assert.assertFalse(f.isReadOnly()); + } + + @Test + public void addingReadOnlyMakesNoDifference() { + final HeaderFooterLayoutWrapper layout = FlexLayoutHelpers.columnWithHeaderRowAndFooterRow(); + final Button button = new Button("this should be disabled"); + button.setEnabled(false); + final TextArea area = new TextArea("this should be readonly, but enabled"); + area.setReadOnly(true); + layout.add(button, area); + Assert.assertFalse(layout.isReadOnly()); + Assert.assertFalse(button.isEnabled()); + Assert.assertTrue(area.isReadOnly()); + } + +} \ No newline at end of file diff --git a/superfields/src/test/java/org/vaadin/miki/superfields/object/EnumObject.java b/superfields/src/test/java/org/vaadin/miki/superfields/object/EnumObject.java new file mode 100644 index 00000000..86eb3070 --- /dev/null +++ b/superfields/src/test/java/org/vaadin/miki/superfields/object/EnumObject.java @@ -0,0 +1,48 @@ +package org.vaadin.miki.superfields.object; + +import java.util.Objects; +import java.util.Set; + +public class EnumObject { + + private TestingMode mode; + + private Set modes; + + public TestingMode getMode() { + return mode; + } + + public void setMode(TestingMode mode) { + this.mode = mode; + } + + public Set getModes() { + return modes; + } + + public void setModes(Set modes) { + this.modes = modes; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + EnumObject that = (EnumObject) o; + return getMode() == that.getMode() && Objects.equals(getModes(), that.getModes()); + } + + @Override + public int hashCode() { + return Objects.hash(getMode(), getModes()); + } + + @Override + public String toString() { + return "EnumObject{" + + "mode=" + mode + + ", modes=" + modes + + '}'; + } +} diff --git a/superfields/src/test/java/org/vaadin/miki/superfields/object/EnumObjectTest.java b/superfields/src/test/java/org/vaadin/miki/superfields/object/EnumObjectTest.java new file mode 100644 index 00000000..ca0bf6af --- /dev/null +++ b/superfields/src/test/java/org/vaadin/miki/superfields/object/EnumObjectTest.java @@ -0,0 +1,84 @@ +package org.vaadin.miki.superfields.object; + +import com.github.mvysny.kaributesting.v10.MockVaadin; +import com.vaadin.flow.component.HasValue; +import com.vaadin.flow.component.combobox.ComboBox; +import com.vaadin.flow.component.combobox.MultiSelectComboBox; +import com.vaadin.flow.data.provider.Query; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.vaadin.miki.superfields.util.factory.ObjectFieldFactory; + +import java.util.Map; +import java.util.Set; + +public class EnumObjectTest { + + private static final ObjectFieldFactory FACTORY = new ObjectFieldFactory(); + + @BeforeClass + public static void setupFactory() { + FACTORY.registerInstanceProvider(EnumObject.class, EnumObject::new); + } + + private ObjectField field; + private int eventCounter = 0; + + @Before + public void setup() { + MockVaadin.setup(); + this.eventCounter = 0; + this.field = FACTORY.buildAndConfigureObjectField(EnumObject.class, EnumObject::new); + } + + @After + public void tearDown() { + MockVaadin.tearDown(); + } + + @Test + @SuppressWarnings("unchecked") + public void testEnumFieldIsCombobox() { + final Map, HasValue> map = this.field.getPropertiesAndComponents(); + final HasValue comboBox = map.keySet().stream().filter(def -> "mode".equals(def.getName())).map(map::get).findFirst().orElse(null); + Assert.assertNotNull(comboBox); + Assert.assertTrue(String.format("field should be a combobox, not a %s", comboBox.getClass().getSimpleName()), comboBox instanceof ComboBox); + Assert.assertArrayEquals(TestingMode.values(), ((ComboBox) comboBox).getDataProvider().fetch(new Query<>()).toArray(TestingMode[]::new)); + this.field.addValueChangeListener(event -> eventCounter++); + // set a value + final EnumObject data = new EnumObject(); + data.setMode(TestingMode.MANUAL); + this.field.setValue(data); + Assert.assertEquals(1, this.eventCounter); + Assert.assertEquals(TestingMode.MANUAL, comboBox.getValue()); + // select a value + ((ComboBox)comboBox).setValue(TestingMode.AUTOMATIC); + Assert.assertEquals(2, this.eventCounter); + Assert.assertEquals(TestingMode.AUTOMATIC, field.getValue().getMode()); + } + + @Test + public void testEnumListIsMultiselectComboBox() { + final Map, HasValue> map = this.field.getPropertiesAndComponents(); + final HasValue comboBox = map.keySet().stream().filter(def -> "modes".equals(def.getName())).map(map::get).findFirst().orElse(null); + Assert.assertNotNull(comboBox); + Assert.assertTrue(String.format("field should be a multi-select combobox, not a %s", comboBox.getClass().getSimpleName()), comboBox instanceof MultiSelectComboBox); + Assert.assertArrayEquals(TestingMode.values(), ((MultiSelectComboBox) comboBox).getDataProvider().fetch(new Query<>()).toArray(TestingMode[]::new)); + this.field.addValueChangeListener(event -> eventCounter++); + // set a value + final EnumObject data = new EnumObject(); + final Set testingModes = Set.of(TestingMode.MANUAL, TestingMode.AUTOMATIC); + data.setModes(testingModes); + this.field.setValue(data); + Assert.assertEquals(1, this.eventCounter); + Assert.assertEquals(testingModes, comboBox.getValue()); + // select a value + ((MultiSelectComboBox)comboBox).setValue(TestingMode.NONE); + Assert.assertEquals(2, this.eventCounter); + Assert.assertEquals(Set.of(TestingMode.NONE), field.getValue().getModes()); + } + +} diff --git a/superfields/src/test/java/org/vaadin/miki/superfields/object/TestingMode.java b/superfields/src/test/java/org/vaadin/miki/superfields/object/TestingMode.java new file mode 100644 index 00000000..d17a517e --- /dev/null +++ b/superfields/src/test/java/org/vaadin/miki/superfields/object/TestingMode.java @@ -0,0 +1,7 @@ +package org.vaadin.miki.superfields.object; + +public enum TestingMode { + + AUTOMATIC, MANUAL, NONE + +}