From 0acd74d6f37157738b7d6b0545a5247e73e300f5 Mon Sep 17 00:00:00 2001 From: Youri Bonnaffe Date: Tue, 26 Oct 2021 13:59:48 +0200 Subject: [PATCH 1/6] Reformat code --- .../symphony/messageml/elements/Select.java | 11 +- .../elements/form/SelectOptionTest.java | 128 +++++++++++------- 2 files changed, 85 insertions(+), 54 deletions(-) diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java index 2e163f3b..20e7cb84 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java @@ -63,7 +63,7 @@ public void validate() throws InvalidInputException { assertContentModel(Collections.singleton(Option.class)); assertContainsChildOfType(Collections.singleton(Option.class)); - if(getAttribute(REQUIRED_ATTR) != null) { + if (getAttribute(REQUIRED_ATTR) != null) { assertAttributeValue(REQUIRED_ATTR, Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString())); } @@ -72,8 +72,7 @@ public void validate() throws InvalidInputException { } @Override - protected void buildAttribute(MessageMLParser parser, - Node item) throws InvalidInputException { + protected void buildAttribute(MessageMLParser parser, Node item) throws InvalidInputException { switch (item.getNodeName()) { case NAME_ATTR: case REQUIRED_ATTR: @@ -83,7 +82,7 @@ protected void buildAttribute(MessageMLParser parser, setAttribute(item.getNodeName(), getStringAttribute(item)); break; case ID_ATTR: - if(format != FormatEnum.PRESENTATIONML){ + if (format != FormatEnum.PRESENTATIONML) { throwInvalidInputException(item); } fillAttributes(parser, item); @@ -94,7 +93,7 @@ protected void buildAttribute(MessageMLParser parser, } @Override - public String getElementId(){ + public String getElementId() { return ELEMENT_ID; } @@ -116,7 +115,7 @@ private void assertOnlyOneOptionSelected() throws InvalidInputException { .filter(selectedAttr -> selectedAttr != null && selectedAttr.equalsIgnoreCase(Boolean.TRUE.toString())) .count(); - if(numberOfSelectedOptions > 1) { + if (numberOfSelectedOptions > 1) { throw new InvalidInputException("Element \"select\" can only have one selected \"option\""); } } diff --git a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java index 9b034d4f..5b0f1374 100644 --- a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java +++ b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java @@ -1,5 +1,9 @@ package org.symphonyoss.symphony.messageml.elements.form; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.symphonyoss.symphony.messageml.markdown.MarkdownRenderer.addEscapeCharacter; + import org.junit.Test; import org.symphonyoss.symphony.messageml.MessageMLContext; import org.symphonyoss.symphony.messageml.bi.BiFields; @@ -21,10 +25,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNull; -import static org.symphonyoss.symphony.messageml.markdown.MarkdownRenderer.addEscapeCharacter; - public class SelectOptionTest extends ElementTest { private static final String NAME_ATTR = "name"; @@ -41,7 +41,9 @@ public void testCompleteRequiredSelect() throws Exception { String name = "complete-required-id"; boolean required = true; String placeholder = "placeholder-here"; - String input = "
" + ACTION_BTN_ELEMENT + "
"; context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); @@ -60,9 +62,11 @@ public void testCompleteRequiredSelectWithLabelAndTooltip() throws Exception { String title = "tooltip"; boolean required = true; String placeholder = "placeholder-here"; - String input = "
" + ACTION_BTN_ELEMENT + "
"; + String input = + "
" + ACTION_BTN_ELEMENT + "
"; context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); Element messageML = context.getMessageML(); @@ -80,7 +84,9 @@ public void testSelectWithUnderscore() throws Exception { String title = "tooltip-here"; boolean required = true; String placeholder = "placeholder-here"; - String input = "
" + ACTION_BTN_ELEMENT + "
"; context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); @@ -92,12 +98,13 @@ public void testSelectWithUnderscore() throws Exception { assertEquals("Select class", Select.class, select.getClass()); verifySelectPresentation((Select) select, name, true, required, placeholder, true, true); } + @Test public void testCompleteNotRequiredSelect() throws Exception { String name = "complete-id"; boolean required = false; String input = "
" + ACTION_BTN_ELEMENT + "
"; + "\">" + ACTION_BTN_ELEMENT + ""; context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); Element messageML = context.getMessageML(); @@ -111,7 +118,8 @@ public void testCompleteNotRequiredSelect() throws Exception { @Test public void testSimpleSelect() throws Exception { String name = "simple-id"; - String input = "
" + ACTION_BTN_ELEMENT + String input = "" + ACTION_BTN_ELEMENT + "
"; context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); @@ -120,14 +128,15 @@ public void testSimpleSelect() throws Exception { Element select = form.getChildren().get(0); assertEquals("Select class", Select.class, select.getClass()); - verifySelectPresentation((Select) select, name, false,false, null, false, false); + verifySelectPresentation((Select) select, name, false, false, null, false, false); } @Test public void testDoubleOptionSelect() throws Exception { String name = "simple-id"; - String input = "
" + ACTION_BTN_ELEMENT + "
"; + String input = "
" + ACTION_BTN_ELEMENT + "
"; context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); Element messageML = context.getMessageML(); @@ -135,13 +144,14 @@ public void testDoubleOptionSelect() throws Exception { Element select = form.getChildren().get(0); assertEquals("Select class", Select.class, select.getClass()); - verifySelectPresentation((Select) select, name, false,false, null, false, false); + verifySelectPresentation((Select) select, name, false, false, null, false, false); } @Test public void testOptionWithSelectedAttr() throws Exception { String name = "simple-id"; - String input = "
" + ACTION_BTN_ELEMENT + "
"; context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); @@ -150,13 +160,14 @@ public void testOptionWithSelectedAttr() throws Exception { Element select = form.getChildren().get(0); assertEquals("Select class", Select.class, select.getClass()); - verifySelectPresentation((Select) select, name, false,false, null, false, false); + verifySelectPresentation((Select) select, name, false, false, null, false, false); } @Test public void testDoubleOptionWithSelectedAttrAsTrue() throws Exception { String name = "simple-id"; - String input = "
"; expectedException.expect(InvalidInputException.class); @@ -168,11 +179,13 @@ public void testDoubleOptionWithSelectedAttrAsTrue() throws Exception { @Test public void testOptionWithInvalidValueForSelectedAttr() throws Exception { String name = "simple-id"; - String input = "
"; expectedException.expect(InvalidInputException.class); - expectedException.expectMessage("Attribute \"selected\" of element \"option\" can only be one of the following values: [true, false]."); + expectedException.expectMessage( + "Attribute \"selected\" of element \"option\" can only be one of the following values: [true, false]."); context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); } @@ -180,10 +193,12 @@ public void testOptionWithInvalidValueForSelectedAttr() throws Exception { @Test public void testChildlessSelect() throws Exception { String name = "childless-select"; - String input = "
"; + String input = + "
"; expectedException.expect(InvalidInputException.class); - expectedException.expectMessage("The \"select\" element must have at least one child that is any of the following elements: [option]."); + expectedException.expectMessage( + "The \"select\" element must have at least one child that is any of the following elements: [option]."); context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); } @@ -191,17 +206,20 @@ public void testChildlessSelect() throws Exception { @Test public void testSelectWithEmptyChild() throws Exception { String name = "empty-child-select"; - String input = "
"; + String input = + "
"; expectedException.expect(InvalidInputException.class); - expectedException.expectMessage("The \"select\" element must have at least one child that is any of the following elements: [option]."); + expectedException.expectMessage( + "The \"select\" element must have at least one child that is any of the following elements: [option]."); context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); } @Test public void testSelectWithoutName() throws Exception { - String input = "
"; + String input = "
"; expectedException.expect(InvalidInputException.class); expectedException.expectMessage("The attribute \"name\" is required"); @@ -211,7 +229,8 @@ public void testSelectWithoutName() throws Exception { @Test public void testSelectWithBlankName() throws Exception { - String input = "
"; + String input = "
"; expectedException.expect(InvalidInputException.class); expectedException.expectMessage("The attribute \"name\" is required"); @@ -222,7 +241,8 @@ public void testSelectWithBlankName() throws Exception { @Test public void testOptionWithoutValue() throws Exception { String name = "nameless-option"; - String input = "
"; + String input = "
"; expectedException.expect(InvalidInputException.class); expectedException.expectMessage("The attribute \"value\" is required"); @@ -233,10 +253,12 @@ public void testOptionWithoutValue() throws Exception { @Test public void testSelectWithInvalidRequiredAttribute() throws Exception { String name = "invalid-required-select"; - String input = "
"; + String input = "
"; expectedException.expect(InvalidInputException.class); - expectedException.expectMessage("Attribute \"required\" of element \"select\" can only be one of the following values: [true, false]."); + expectedException.expectMessage( + "Attribute \"required\" of element \"select\" can only be one of the following values: [true, false]."); context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); } @@ -244,7 +266,8 @@ public void testSelectWithInvalidRequiredAttribute() throws Exception { @Test public void testSelectWithInvalidAttribute() throws Exception { String name = "invalid-attribute-select"; - String input = "
"; + String input = "
"; expectedException.expect(InvalidInputException.class); expectedException.expectMessage("Attribute \"invalid\" is not allowed in \"select\""); @@ -255,7 +278,8 @@ public void testSelectWithInvalidAttribute() throws Exception { @Test public void testOptionWithInvalidAttribute() throws Exception { String name = "invalid-attribute-option"; - String input = "
"; + String input = "
"; expectedException.expect(InvalidInputException.class); expectedException.expectMessage("Attribute \"invalid\" is not allowed in \"option\""); @@ -287,7 +311,8 @@ public void testOptionOutOfSelect() throws Exception { @Test public void testSelectWithInvalidChild() throws Exception { String name = "invalid-child-select"; - String input = "
"; + String input = "
"; expectedException.expect(InvalidInputException.class); expectedException.expectMessage("Element \"span\" is not allowed in \"select\""); @@ -298,7 +323,8 @@ public void testSelectWithInvalidChild() throws Exception { @Test public void testOptionWithInvalidChild() throws Exception { String name = "invalid-child-option"; - String input = "
"; + String input = "
"; expectedException.expect(InvalidInputException.class); expectedException.expectMessage("Element \"span\" is not allowed in \"option\""); @@ -361,8 +387,8 @@ public void testBiContextSelect() } private String getRequiredPresentationML(String required) { - if(required != null) { - if(required.equals("true") || required.equals("false")) { + if (required != null) { + if (required.equals("true") || required.equals("false")) { return String.format(" required=\"%s\"", required); } } @@ -377,7 +403,7 @@ private String getExpectedSelectMarkdown(Select select, boolean hasLabel, boolea StringBuilder expectedMarkdown = new StringBuilder(FORM_MARKDOWN_HEADER); expectedMarkdown.append("(Dropdown"); String placeholder = select.getAttribute(DATA_PLACEHOLDER_ATTR); - expectedMarkdown.append((placeholder!= null || hasLabel || hasTitle) ? ":" : ""); + expectedMarkdown.append((placeholder != null || hasLabel || hasTitle) ? ":" : ""); expectedMarkdown.append((placeholder != null) ? "[" + addEscapeCharacter((placeholder)) + "]" : ""); expectedMarkdown.append(hasLabel ? "[" + addEscapeCharacter(select.getAttribute(LABEL_ATTR)) + "]" : ""); expectedMarkdown.append(hasTitle ? "[" + addEscapeCharacter(select.getAttribute(TITLE_ATTR)) + "]" : ""); @@ -385,24 +411,27 @@ private String getExpectedSelectMarkdown(Select select, boolean hasLabel, boolea for (Element option : select.getChildren()) { if (option instanceof Option) { - expectedMarkdown.append("-" + option.getChild(0).asText() + "\n"); + expectedMarkdown.append("-").append(option.getChild(0).asText()).append("\n"); } } - - expectedMarkdown.append(ACTION_BTN_MARKDOWN + "\n" + FORM_MARKDOWN_FOOTER); + + expectedMarkdown.append(ACTION_BTN_MARKDOWN + "\n").append(FORM_MARKDOWN_FOOTER); return expectedMarkdown.toString(); } - private String getExpectedSelectPresentation(Select select, boolean hasLabel, boolean hasTitle, String uniqueLabelId) { + private String getExpectedSelectPresentation(Select select, boolean hasLabel, boolean hasTitle, + String uniqueLabelId) { String selectOpeningTag = "
" + ((hasLabel || hasTitle) ? "
" : "") - + (hasLabel ? "" : "") - + (hasTitle ? "" : "") + + (hasLabel ? "" : "") + + (hasTitle ? "" : "") + ""; @@ -415,7 +444,8 @@ private String getExpectedSelectPresentation(Select select, boolean hasLabel, bo option.getAttribute(VALUE_ATTR) + "\">" + option.getChild(0).asText() + ""; } } - return selectOpeningTag + selectChildren + selectClosingTag + ((hasLabel || hasTitle) ? "
" : "") + ACTION_BTN_ELEMENT + formDivClosingTag; + return selectOpeningTag + selectChildren + selectClosingTag + ((hasLabel || hasTitle) ? "
" : "") + + ACTION_BTN_ELEMENT + formDivClosingTag; } private String getPlaceholderAttribute(String placeholder) { @@ -426,8 +456,8 @@ private String getOptionSelectedExpectedText(Element option) { return option.getAttribute(SELECTED_ATTR) != null ? " selected=\"" + option.getAttribute(SELECTED_ATTR) + "\"" : ""; } - private void verifySelectPresentation(Select select, String name, boolean requiredAttrProvided, boolean requiredValue, - String placeholder, boolean hasLabel, boolean hasTitle) { + private void verifySelectPresentation(Select select, String name, boolean requiredAttrProvided, boolean requiredValue, + String placeholder, boolean hasLabel, boolean hasTitle) { assertEquals("Select name attribute", name, select.getAttribute(NAME_ATTR)); if (requiredAttrProvided) { assertEquals("Select required attribute", String.valueOf(requiredValue), select.getAttribute(REQUIRED_ATTR)); @@ -445,8 +475,10 @@ private void verifySelectPresentation(Select select, String name, boolean requir String dropdownRegex = ".*(\"dropdown-(.*?)\").*"; Pattern pattern = Pattern.compile(dropdownRegex); Matcher matcher = pattern.matcher(presentationML); - - assertEquals("Select presentationML", getExpectedSelectPresentation(select, hasLabel, hasTitle, matcher.matches() ? matcher.group(2) : null), presentationML); + + assertEquals("Select presentationML", + getExpectedSelectPresentation(select, hasLabel, hasTitle, matcher.matches() ? matcher.group(2) : null), + presentationML); assertEquals("Select markdown", getExpectedSelectMarkdown(select, hasLabel, hasTitle), context.getMarkdown()); } } From c6199505bc2ba23f7365e2f5cfb77a75b5b8cce6 Mon Sep 17 00:00:00 2001 From: Youri Bonnaffe Date: Tue, 26 Oct 2021 16:09:34 +0200 Subject: [PATCH 2/6] PLAT-11463: Multi select element The select/dropdown menu now supports selecting multiple options. A new multiple (boolean) attribute has been added to it to enable this behavior. min/max attributes are also added to specify the number of options to select. If multiple is used, then multiple options can be selected by default. Only the multiple attribute is propagated in BI events. --- .../symphony/messageml/bi/BiFields.java | 1 + .../symphony/messageml/elements/Select.java | 68 +++- .../elements/form/SelectOptionTest.java | 321 ++++++++++++++++++ 3 files changed, 384 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/symphonyoss/symphony/messageml/bi/BiFields.java b/src/main/java/org/symphonyoss/symphony/messageml/bi/BiFields.java index 8bb11935..49ece01d 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/bi/BiFields.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/bi/BiFields.java @@ -67,6 +67,7 @@ public enum BiFields { HIGHLIGHTED_OPTIONS("highlighted_options", BiEventType.MESSAGEML_ELEMENT_SENT), VALIDATION_STRICT("validation_strict", BiEventType.MESSAGEML_ELEMENT_SENT), REQUIRED("required", BiEventType.MESSAGEML_ELEMENT_SENT), + MULTI_SELECT("is_multi_select", BiEventType.MESSAGEML_ELEMENT_SENT), MESSAGE_LENGTH("message_length", BiEventType.MESSAGEML_MESSAGE_SENT), ENTITY_JSON_SIZE("entities_json_size", BiEventType.MESSAGEML_MESSAGE_SENT), FREEMARKER("use_freemarker", BiEventType.MESSAGEML_MESSAGE_SENT), diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java index 20e7cb84..4072dfdd 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java @@ -30,11 +30,24 @@ import java.util.Map; /** - * Class representing dropdown menu - Symphony Elements. - * - * @author lumoura - * @since 3/22/18 + * This class represents the Symphony Element Dialog which is represented with tag name "select" (drop down menu). + * The messageML representation of the select element can contain the following attributes: + *
    + *
  • name (required) -> to identify this element
  • + *
  • required -> true/false, to enforce a non empty option to be selected
  • + *
  • data-placeholder -> string, text displayed in the dropdown menu before an option is selected
  • + *
  • title -> string, the description that will be displayed when clicking the tooltip icon
  • + *
  • label -> string, definition of the label that will be displayed on top of the Masked Text Field Element
  • + *
  • multiple -> true/false, to allow for multiple options to be selected
  • + *
  • min -> integer, minimum number of options to select if multiple=true
  • + *
  • max -> integer, maximum number of options to select if multiple=true
  • + *
+ * It can contain the following child tags: + *
    + *
  • option (required) -> the possible options to select {@link Option}
  • + *
*/ + public class Select extends FormElement implements LabelableElement, TooltipableElement { public static final String MESSAGEML_TAG = "select"; @@ -42,6 +55,9 @@ public class Select extends FormElement implements LabelableElement, Tooltipable private static final String REQUIRED_ATTR = "required"; private static final String OPTION_SELECTED_ATTR = "selected"; private static final String DATA_PLACEHOLDER_ATTR = "data-placeholder"; + private static final String MULTIPLE_ATTR = "multiple"; + private static final String MIN_ATTR = "min"; + private static final String MAX_ATTR = "max"; public Select(Element parent) { super(parent, MESSAGEML_TAG); @@ -59,6 +75,7 @@ public void validate() throws InvalidInputException { if (getAttribute(NAME_ATTR) == null) { throw new InvalidInputException("The attribute \"name\" is required"); } + assertAttributeNotBlank(NAME_ATTR); assertContentModel(Collections.singleton(Option.class)); assertContainsChildOfType(Collections.singleton(Option.class)); @@ -67,8 +84,28 @@ public void validate() throws InvalidInputException { assertAttributeValue(REQUIRED_ATTR, Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString())); } - assertOnlyOneOptionSelected(); - assertAttributeNotBlank(NAME_ATTR); + if (getAttribute(MULTIPLE_ATTR) != null) { + assertAttributeValue(MULTIPLE_ATTR, Arrays.asList(Boolean.TRUE.toString(), Boolean.FALSE.toString())); + } + + boolean multipleAttributeValue = Boolean.parseBoolean(getAttribute(MULTIPLE_ATTR)); + if (getAttribute(MIN_ATTR) != null && !multipleAttributeValue) { + throw new InvalidInputException("Attribute \"min\" is not allowed. Attribute \"multiple\" missing"); + } + + if (getAttribute(MAX_ATTR) != null && !multipleAttributeValue) { + throw new InvalidInputException("Attribute \"max\" is not allowed. Attribute \"multiple\" missing"); + } + + int min = checkIntegerAttribute(MIN_ATTR, "Attribute \"min\" is not valid"); + int max = checkIntegerAttribute(MAX_ATTR, "Attribute \"max\" is not valid"); + if (min > max) { + throw new InvalidInputException("Attribute \"min\" is greater than attribute \"max\""); + } + + if (!multipleAttributeValue) { + assertOnlyOneOptionSelected(); + } } @Override @@ -79,6 +116,9 @@ protected void buildAttribute(MessageMLParser parser, Node item) throws InvalidI case DATA_PLACEHOLDER_ATTR: case LABEL: case TITLE: + case MULTIPLE_ATTR: + case MIN_ATTR: + case MAX_ATTR: setAttribute(item.getNodeName(), getStringAttribute(item)); break; case ID_ATTR: @@ -105,10 +145,26 @@ public void updateBiContext(BiContext context) { this.putOneIfPresent(attributesMapBi, BiFields.LABEL.getValue(), LABEL); this.putOneIfPresent(attributesMapBi, BiFields.PLACEHOLDER.getValue(), DATA_PLACEHOLDER_ATTR); this.putOneIfPresent(attributesMapBi, BiFields.REQUIRED.getValue(), REQUIRED_ATTR); + this.putOneIfPresent(attributesMapBi, BiFields.MULTI_SELECT.getValue(), MULTIPLE_ATTR); context.addItem(new BiItem(BiFields.SELECT.getValue(), attributesMapBi)); } + private int checkIntegerAttribute(String attributeName, String errorMessage) throws InvalidInputException { + int value = 0; + if (getAttribute(attributeName) != null) { + try { + value = Integer.parseInt(getAttribute(attributeName)); + if (value <= 1) { + throw new InvalidInputException(errorMessage); + } + } catch (NumberFormatException e) { + throw new InvalidInputException(errorMessage, e); + } + } + return value; + } + private void assertOnlyOneOptionSelected() throws InvalidInputException { long numberOfSelectedOptions = getChildren().stream() .map(child -> child.getAttribute(OPTION_SELECTED_ATTR)) diff --git a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java index 5b0f1374..236bba4b 100644 --- a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java +++ b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java @@ -332,6 +332,286 @@ public void testOptionWithInvalidChild() throws Exception { context.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); } + @Test + public void testMultiSelect() throws Exception { + //language=XML + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + + Element form = context.getMessageML().getChildren().get(0); + Element select = form.getChildren().get(0); + assertEquals("Select class", Select.class, select.getClass()); + + //language=HTML + String expectedPresentationML = "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + assertEquals(trimXml(expectedPresentationML), context.getPresentationML()); + } + + @Test + public void testMultiSelectMinMax() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + + Element form = context.getMessageML().getChildren().get(0); + Element select = form.getChildren().get(0); + assertEquals("Select class", Select.class, select.getClass()); + + //language=HTML + String expectedPresentationML = "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + assertEquals(trimXml(expectedPresentationML), context.getPresentationML()); + } + + @Test + public void testMultiSelectMultiSelected() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + + //language=HTML + String expectedPresentationML = "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + assertEquals(trimXml(expectedPresentationML), context.getPresentationML()); + } + + @Test + public void testMultiSelectWithoutMultiple() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage("Attribute \"min\" is not allowed. Attribute \"multiple\" missing"); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + + @Test + public void testMultiSelectMultipleFalse() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage("Attribute \"min\" is not allowed. Attribute \"multiple\" missing"); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + + @Test + public void testMultiSelectWithoutMultipleMinOnly() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage("Attribute \"min\" is not allowed. Attribute \"multiple\" missing"); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + + @Test + public void testMultiSelectWithoutMultipleMaxOnly() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage("Attribute \"max\" is not allowed. Attribute \"multiple\" missing"); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + + @Test + public void testMultiSelectMinGreaterThanMax() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage("Attribute \"min\" is greater than attribute \"max\""); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + + @Test + public void testMultiSelectMinInvalid() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage("Attribute \"min\" is not valid"); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + + @Test + public void testMultiSelectMinInvalidType() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage("Attribute \"min\" is not valid"); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + + @Test + public void testMultiSelectMaxInvalid() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage("Attribute \"max\" is not valid"); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + + @Test + public void testMultiSelectMaxInvalidType() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage("Attribute \"max\" is not valid"); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + + @Test + public void testMultiSelectMultipleInvalid() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage( + "Attribute \"multiple\" of element \"select\" can only be one of the following values: [true, false]"); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + @Test public void testBiContextSelect() throws InvalidInputException, IOException, ProcessingException { @@ -386,6 +666,43 @@ public void testBiContextSelect() assertMessageLengthBiItem(items.get(4), input.length()); } + @Test + public void testBiContextMultiSelect() throws InvalidInputException, IOException, ProcessingException { + MessageMLContext messageMLContext = new MessageMLContext(null); + String input = "\n" + + "
\n" + + "\n" + + " \n" + + "
\n" + + "
"; + + messageMLContext.parseMessageML(input, null, MessageML.MESSAGEML_VERSION); + List items = messageMLContext.getBiContext().getItems(); + + Map selectExpectedAttributes = Stream.of(new Object[][] { + {BiFields.MULTI_SELECT.getValue(), 1}, + {BiFields.TITLE.getValue(), 1}, + {BiFields.LABEL.getValue(), 1}, + {BiFields.PLACEHOLDER.getValue(), 1}, + {BiFields.REQUIRED.getValue(), 1}, + }).collect(Collectors.toMap(property -> property[0], property -> property[1])); + + BiItem selectBiItemExpected = new BiItem(BiFields.SELECT.getValue(), + selectExpectedAttributes.entrySet() + .stream() + .collect(Collectors.toMap(e -> + String.valueOf(e.getKey()), Map.Entry::getValue))); + + assertEquals(5, items.size()); + assertEquals(BiFields.SELECT.getValue(), items.get(1).getName()); + assertSameBiItem(selectBiItemExpected, items.get(1)); + } + private String getRequiredPresentationML(String required) { if (required != null) { if (required.equals("true") || required.equals("false")) { @@ -481,4 +798,8 @@ private void verifySelectPresentation(Select select, String name, boolean requir presentationML); assertEquals("Select markdown", getExpectedSelectMarkdown(select, hasLabel, hasTitle), context.getMarkdown()); } + + private static String trimXml(String input) { // to avoid empty text element upon parsing + return input.replace("\n", "").replace(" ", ""); + } } From 71398566e14a8f332739fee147cc5df69f0a696d Mon Sep 17 00:00:00 2001 From: Youri Bonnaffe Date: Tue, 26 Oct 2021 16:59:44 +0200 Subject: [PATCH 3/6] Update multi select PML conversion Same as required, multiple is represented as a string (true/false) attribute. min/max are represented as data-min/data-max --- .../symphony/messageml/elements/Select.java | 14 ++++++++++---- .../messageml/elements/form/SelectOptionTest.java | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java index 4072dfdd..96e934b8 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java @@ -56,8 +56,10 @@ public class Select extends FormElement implements LabelableElement, Tooltipable private static final String OPTION_SELECTED_ATTR = "selected"; private static final String DATA_PLACEHOLDER_ATTR = "data-placeholder"; private static final String MULTIPLE_ATTR = "multiple"; - private static final String MIN_ATTR = "min"; - private static final String MAX_ATTR = "max"; + private static final String MML_MIN_ATTR = "min"; + private static final String MIN_ATTR = "data-min"; + private static final String MML_MAX_ATTR = "max"; + private static final String MAX_ATTR = "data-max"; public Select(Element parent) { super(parent, MESSAGEML_TAG); @@ -117,10 +119,14 @@ protected void buildAttribute(MessageMLParser parser, Node item) throws InvalidI case LABEL: case TITLE: case MULTIPLE_ATTR: - case MIN_ATTR: - case MAX_ATTR: setAttribute(item.getNodeName(), getStringAttribute(item)); break; + case MML_MIN_ATTR: + setAttribute(MIN_ATTR, getStringAttribute(item)); + break; + case MML_MAX_ATTR: + setAttribute(MAX_ATTR, getStringAttribute(item)); + break; case ID_ATTR: if (format != FormatEnum.PRESENTATIONML) { throwInvalidInputException(item); diff --git a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java index 236bba4b..3c47553a 100644 --- a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java +++ b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java @@ -384,7 +384,7 @@ public void testMultiSelectMinMax() throws Exception { //language=HTML String expectedPresentationML = "
\n" + "
\n" - + " \n" + " \n" + " \n" + " \n" From 12e23660ac4a7370a170fde4c600120b44df4776 Mon Sep 17 00:00:00 2001 From: Youri Bonnaffe Date: Wed, 27 Oct 2021 07:55:05 +0200 Subject: [PATCH 4/6] Min/Max behavior for multi select Support min=0 with multiple=true (so you have a multiselect and allow for 0 options to be selected). Check min max) { + int min = checkIntegerAttribute(MIN_ATTR, 0, "Attribute \"min\" is not valid"); + int max = checkIntegerAttribute(MAX_ATTR, 2, "Attribute \"max\" is not valid"); + if (max > 0 && min > max) { throw new InvalidInputException("Attribute \"min\" is greater than attribute \"max\""); } @@ -156,12 +156,13 @@ public void updateBiContext(BiContext context) { context.addItem(new BiItem(BiFields.SELECT.getValue(), attributesMapBi)); } - private int checkIntegerAttribute(String attributeName, String errorMessage) throws InvalidInputException { + private int checkIntegerAttribute(String attributeName, int minValue, String errorMessage) + throws InvalidInputException { int value = 0; if (getAttribute(attributeName) != null) { try { value = Integer.parseInt(getAttribute(attributeName)); - if (value <= 1) { + if (value < minValue) { throw new InvalidInputException(errorMessage); } } catch (NumberFormatException e) { diff --git a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java index 3c47553a..2359cf97 100644 --- a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java +++ b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java @@ -516,12 +516,40 @@ public void testMultiSelectMinGreaterThanMax() throws Exception { context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); } + @Test + public void testMultiSelectMinGreaterThanMaxUnset() throws Exception { + //language=XML + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + + //language=HTML + String expectedPresentationML = "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + assertEquals(trimXml(expectedPresentationML), context.getPresentationML()); + } + @Test public void testMultiSelectMinInvalid() throws Exception { //language=XML String input = "\n" + "
\n" - + " \n" + " \n" + " \n" + " \n" From 23e2867e9553781c5932d5e99b6cf5ec4dc149fd Mon Sep 17 00:00:00 2001 From: Youri Bonnaffe Date: Wed, 27 Oct 2021 09:54:16 +0200 Subject: [PATCH 5/6] Better error messages / One more case for min Support an error case with multiple + required Move up checkIntegerAttribute in the hierachy for reuse --- .../symphony/messageml/elements/Element.java | 16 +++++++ .../symphony/messageml/elements/Select.java | 26 ++++------ .../elements/form/SelectOptionTest.java | 47 +++++++++++++++++++ 3 files changed, 71 insertions(+), 18 deletions(-) diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java index 00382d94..b5164309 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/Element.java @@ -989,4 +989,20 @@ protected void throwInvalidInputException(org.w3c.dom.Node item) throws InvalidI throw new InvalidInputException("Attribute \"" + item.getNodeName() + "\" is not allowed in \"" + getMessageMLTag() + "\""); } + + protected int checkIntegerAttribute(String attributeName, int minValue, String errorMessage) + throws InvalidInputException { + int value = 0; + if (getAttribute(attributeName) != null) { + try { + value = Integer.parseInt(getAttribute(attributeName)); + if (value < minValue) { + throw new InvalidInputException(errorMessage); + } + } catch (NumberFormatException e) { + throw new InvalidInputException(errorMessage, e); + } + } + return value; + } } diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java index d6c1a9f9..f8ba451d 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java @@ -99,12 +99,18 @@ public void validate() throws InvalidInputException { throw new InvalidInputException("Attribute \"max\" is not allowed. Attribute \"multiple\" missing"); } - int min = checkIntegerAttribute(MIN_ATTR, 0, "Attribute \"min\" is not valid"); - int max = checkIntegerAttribute(MAX_ATTR, 2, "Attribute \"max\" is not valid"); + int min = checkIntegerAttribute(MIN_ATTR, 0, "Attribute \"min\" is not valid, it must be >= 0"); + int max = checkIntegerAttribute(MAX_ATTR, 2, "Attribute \"max\" is not valid, it must be >= 2"); if (max > 0 && min > max) { throw new InvalidInputException("Attribute \"min\" is greater than attribute \"max\""); } + if (multipleAttributeValue && Boolean.parseBoolean(getAttribute(REQUIRED_ATTR)) + && getAttribute(MIN_ATTR) != null && min == 0) { + // multiple=true required=true min=0 + throw new InvalidInputException("Attribute \"min\" cannot be 0 if \"required\" is true"); + } + if (!multipleAttributeValue) { assertOnlyOneOptionSelected(); } @@ -156,22 +162,6 @@ public void updateBiContext(BiContext context) { context.addItem(new BiItem(BiFields.SELECT.getValue(), attributesMapBi)); } - private int checkIntegerAttribute(String attributeName, int minValue, String errorMessage) - throws InvalidInputException { - int value = 0; - if (getAttribute(attributeName) != null) { - try { - value = Integer.parseInt(getAttribute(attributeName)); - if (value < minValue) { - throw new InvalidInputException(errorMessage); - } - } catch (NumberFormatException e) { - throw new InvalidInputException(errorMessage, e); - } - } - return value; - } - private void assertOnlyOneOptionSelected() throws InvalidInputException { long numberOfSelectedOptions = getChildren().stream() .map(child -> child.getAttribute(OPTION_SELECTED_ATTR)) diff --git a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java index 2359cf97..f28db8aa 100644 --- a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java +++ b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java @@ -563,6 +563,53 @@ public void testMultiSelectMinInvalid() throws Exception { context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); } + @Test + public void testMultiSelectRequiredMinInvalid() throws Exception { + //language=XML + String input = "\n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + expectedException.expect(InvalidInputException.class); + expectedException.expectMessage("Attribute \"min\" cannot be 0 if \"required\" is true"); + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + } + + @Test + public void testMultiSelectRequiredMinDefault() throws Exception { + //language=XML + String input = "\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + + context.parseMessageML(trimXml(input), null, MessageML.MESSAGEML_VERSION); + + //language=HTML + String expectedPresentationML = "
\n" + + "
\n" + + " \n" + + " \n" + + "
\n" + + "
"; + assertEquals(trimXml(expectedPresentationML), context.getPresentationML()); + } + @Test public void testMultiSelectMinInvalidType() throws Exception { //language=XML From 27ab359cddac0161a68fc3269a137159539b89f1 Mon Sep 17 00:00:00 2001 From: Youri Bonnaffe Date: Wed, 27 Oct 2021 10:43:49 +0200 Subject: [PATCH 6/6] Refinement on max valid values --- .../org/symphonyoss/symphony/messageml/elements/Select.java | 2 +- .../symphony/messageml/elements/form/SelectOptionTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java index f8ba451d..7e4a3f04 100644 --- a/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java +++ b/src/main/java/org/symphonyoss/symphony/messageml/elements/Select.java @@ -100,7 +100,7 @@ public void validate() throws InvalidInputException { } int min = checkIntegerAttribute(MIN_ATTR, 0, "Attribute \"min\" is not valid, it must be >= 0"); - int max = checkIntegerAttribute(MAX_ATTR, 2, "Attribute \"max\" is not valid, it must be >= 2"); + int max = checkIntegerAttribute(MAX_ATTR, 1, "Attribute \"max\" is not valid, it must be >= 1"); if (max > 0 && min > max) { throw new InvalidInputException("Attribute \"min\" is greater than attribute \"max\""); } diff --git a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java index f28db8aa..1f047eec 100644 --- a/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java +++ b/src/test/java/org/symphonyoss/symphony/messageml/elements/form/SelectOptionTest.java @@ -634,7 +634,7 @@ public void testMultiSelectMaxInvalid() throws Exception { //language=XML String input = "\n" + "
\n" - + " \n" + " \n" + " \n" + " \n"