You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@myfaces.apache.org by hn...@apache.org on 2023/03/22 16:39:23 UTC

[myfaces-tobago] branch tobago-5.x updated: test(Select[One/Many]List): add Jasmine tests

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

hnoeth pushed a commit to branch tobago-5.x
in repository https://gitbox.apache.org/repos/asf/myfaces-tobago.git


The following commit(s) were added to refs/heads/tobago-5.x by this push:
     new 3348ee9ead test(Select[One/Many]List): add Jasmine tests
3348ee9ead is described below

commit 3348ee9ead03febedb05fb25f577eda99c24e362
Author: Henning Noeth <hn...@apache.org>
AuthorDate: Fri Mar 17 14:02:57 2023 +0100

    test(Select[One/Many]List): add Jasmine tests
    
    * update demo
    * add tests
    
    Issue: TOBAGO-2179
    Issue: TOBAGO-2159
---
 .../example/demo/SelectManyListController.java     |  61 +++------
 .../example/demo/SelectOneListController.java      |  61 +++------
 .../45-selectOneList/SelectOneList.test.js         | 119 +++++++++++++++++
 .../45-selectOneList/SelectOneList.xhtml           | 110 ++++++++--------
 .../80-selectManyList/SelectManyList.test.js       | 144 +++++++++++++++++++++
 .../80-selectManyList/SelectManyList.xhtml         | 113 ++++++++--------
 6 files changed, 420 insertions(+), 188 deletions(-)

diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectManyListController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectManyListController.java
index 023b3c826e..11b5eaad1a 100644
--- a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectManyListController.java
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectManyListController.java
@@ -46,20 +46,17 @@ public class SelectManyListController implements Serializable {
   private AstroData astroData;
 
   private List<SolarObject> planets;
-  private SolarObject[] selected1 = new SolarObject[0];
-  private SolarObject[] selected2 = new SolarObject[0];
-  private SolarObject[] selected3 = new SolarObject[0];
-  private SolarObject[] selected4 = new SolarObject[0];
-  private List<String> names;
-  private String[] selected5 = new String[0];
+  private SolarObject[] selectedPlanets;
 
-  private String filterType;
+  private String filterType = "contains";
+  private final List<String> names = new ArrayList<>();
+  private String[] selectedNames = new String[0];
 
   @PostConstruct
   public void init() {
     planets = astroData.getSatellites("Sun");
+    resetSelectedPlanets();
 
-    names = new ArrayList<>();
     try (BufferedReader reader = new BufferedReader(new InputStreamReader(
         Thread.currentThread().getContextClassLoader().getResourceAsStream(
             "org/apache/myfaces/tobago/example/demo/names.txt"), StandardCharsets.UTF_8))) {
@@ -79,36 +76,24 @@ public class SelectManyListController implements Serializable {
     return planets;
   }
 
-  public SolarObject[] getSelected1() {
-    return selected1;
+  public void resetSelectedPlanets() {
+    selectedPlanets = new SolarObject[]{planets.get(1), planets.get(2), planets.get(4)};
   }
 
-  public void setSelected1(SolarObject[] selected1) {
-    this.selected1 = selected1;
+  public SolarObject[] getSelectedPlanets() {
+    return selectedPlanets;
   }
 
-  public SolarObject[] getSelected2() {
-    return selected2;
+  public void setSelectedPlanets(SolarObject[] selectedPlanets) {
+    this.selectedPlanets = selectedPlanets;
   }
 
-  public void setSelected2(SolarObject[] selected2) {
-    this.selected2 = selected2;
-  }
-
-  public SolarObject[] getSelected3() {
-    return selected3;
-  }
-
-  public void setSelected3(SolarObject[] selected3) {
-    this.selected3 = selected3;
-  }
-
-  public SolarObject[] getSelected4() {
-    return selected4;
+  public String getFilterType() {
+    return filterType;
   }
 
-  public void setSelected4(SolarObject[] selected4) {
-    this.selected4 = selected4;
+  public void setFilterType(String filterType) {
+    this.filterType = filterType;
   }
 
   public List<String> getNames() {
@@ -120,19 +105,11 @@ public class SelectManyListController implements Serializable {
     return names;
   }
 
-  public String[] getSelected5() {
-    return selected5;
-  }
-
-  public void setSelected5(String[] selected5) {
-    this.selected5 = selected5;
+  public String[] getSelectedNames() {
+    return selectedNames;
   }
 
-  public String getFilterType() {
-    return filterType;
-  }
-
-  public void setFilterType(String filterType) {
-    this.filterType = filterType;
+  public void setSelectedNames(String[] selectedNames) {
+    this.selectedNames = selectedNames;
   }
 }
diff --git a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectOneListController.java b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectOneListController.java
index f807a1673d..a315da33ef 100644
--- a/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectOneListController.java
+++ b/tobago-example/tobago-example-demo/src/main/java/org/apache/myfaces/tobago/example/demo/SelectOneListController.java
@@ -46,20 +46,17 @@ public class SelectOneListController implements Serializable {
   private AstroData astroData;
 
   private List<SolarObject> planets;
-  private SolarObject selected1;
-  private SolarObject selected2;
-  private SolarObject selected3;
-  private SolarObject selected4;
-  private List<String> names;
-  private String selected5;
+  private SolarObject selectedPlanet;
 
-  private String filterType;
+  private String filterType = "contains";
+  private final List<String> names = new ArrayList<>();
+  private String selectedName;
 
   @PostConstruct
   public void init() {
     planets = astroData.getSatellites("Sun");
+    selectedPlanet = planets.get(7);
 
-    names = new ArrayList<>();
     try (BufferedReader reader = new BufferedReader(new InputStreamReader(
         Thread.currentThread().getContextClassLoader().getResourceAsStream(
             "org/apache/myfaces/tobago/example/demo/names.txt"), StandardCharsets.UTF_8))) {
@@ -79,36 +76,20 @@ public class SelectOneListController implements Serializable {
     return planets;
   }
 
-  public SolarObject getSelected1() {
-    return selected1;
+  public SolarObject getSelectedPlanet() {
+    return selectedPlanet;
   }
 
-  public void setSelected1(SolarObject selected1) {
-    this.selected1 = selected1;
+  public void setSelectedPlanet(SolarObject selectedPlanet) {
+    this.selectedPlanet = selectedPlanet;
   }
 
-  public SolarObject getSelected2() {
-    return selected2;
-  }
-
-  public void setSelected2(SolarObject selected2) {
-    this.selected2 = selected2;
-  }
-
-  public SolarObject getSelected3() {
-    return selected3;
-  }
-
-  public void setSelected3(SolarObject selected3) {
-    this.selected3 = selected3;
-  }
-
-  public SolarObject getSelected4() {
-    return selected4;
+  public String getFilterType() {
+    return filterType;
   }
 
-  public void setSelected4(SolarObject selected4) {
-    this.selected4 = selected4;
+  public void setFilterType(String filterType) {
+    this.filterType = filterType;
   }
 
   public List<String> getNames() {
@@ -120,19 +101,11 @@ public class SelectOneListController implements Serializable {
     return names;
   }
 
-  public String getSelected5() {
-    return selected5;
-  }
-
-  public void setSelected5(String selected5) {
-    this.selected5 = selected5;
-  }
-
-  public String getFilterType() {
-    return filterType;
+  public String getSelectedName() {
+    return selectedName;
   }
 
-  public void setFilterType(String filterType) {
-    this.filterType = filterType;
+  public void setSelectedName(String selectedName) {
+    this.selectedName = selectedName;
   }
 }
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/45-selectOneList/SelectOneList.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/45-selectOneList/SelectOneList.test.js
new file mode 100644
index 0000000000..b19fa4d2d5
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/45-selectOneList/SelectOneList.test.js
@@ -0,0 +1,119 @@
+/*
+ * 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.
+ */
+
+import {JasmineTestTool} from "/tobago/test/tobago-test-tool.js";
+import {elementByIdFn, querySelectorAllFn, querySelectorFn} from "/script/tobago-test.js";
+
+it("Standard: select 'Mars', select 'Saturn'", function (done) {
+  const selectedFieldFn = querySelectorFn(".tobago-select-field[name='page:mainForm:basic:selectedStandard'] span");
+  const marsRowFn = querySelectorFn(".tobago-options[name='page:mainForm:basic:selectedStandard'] tr[data-tobago-value='Mars']");
+  const saturnRowFn = querySelectorFn(".tobago-options[name='page:mainForm:basic:selectedStandard'] tr[data-tobago-value='Saturn']");
+  const neptuneRowFn = querySelectorFn(".tobago-options[name='page:mainForm:basic:selectedStandard'] tr[data-tobago-value='Neptune']");
+  const disabledSelectedFieldFn = querySelectorFn(".tobago-select-field[name='page:mainForm:basic:selectedDisabled'] span");
+  const submitFn = elementByIdFn("page:mainForm:basic:submit");
+
+  const test = new JasmineTestTool(done);
+  test.setup(() => selectedFieldFn().textContent === "Neptune", neptuneRowFn().click(), "click", submitFn);
+
+  test.do(() => expect(selectedFieldFn().textContent).toEqual("Neptune"));
+  test.do(() => expect(disabledSelectedFieldFn().textContent).toEqual("Neptune"));
+
+  test.do(() => marsRowFn().click());
+  test.event("click", submitFn, () => disabledSelectedFieldFn().textContent === "Mars");
+  test.do(() => expect(selectedFieldFn().textContent).toEqual("Mars"));
+  test.do(() => expect(disabledSelectedFieldFn().textContent).toEqual("Mars"));
+
+  test.do(() => saturnRowFn().click());
+  test.event("click", submitFn, () => disabledSelectedFieldFn().textContent === "Saturn");
+  test.do(() => expect(selectedFieldFn().textContent).toEqual("Saturn"));
+  test.do(() => expect(disabledSelectedFieldFn().textContent).toEqual("Saturn"));
+
+  test.start();
+});
+
+it("Filter (contains)", function (done) {
+  const searchFn = elementByIdFn("page:mainForm:selectedFilter::filter");
+  const hiddenRowsFn = querySelectorAllFn("#page\\:mainForm\\:selectedFilter tr.d-none:not(.tobago-no-entries)");
+  const mercuryRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Mercury']");
+  const venusRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Venus']");
+  const earthRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Earth']");
+  const marsRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Mars']");
+  const jupiterRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Jupiter']");
+  const saturnRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Saturn']");
+  const uranusRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Uranus']");
+  const neptuneRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Neptune']");
+  const plutoRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Pluto']");
+  const noEntriesRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr.tobago-no-entries");
+
+  const test = new JasmineTestTool(done);
+  test.setup(() => hiddenRowsFn().length === 0, () => searchFn().value = "", "input", searchFn);
+
+  test.do(() => expect(mercuryRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(venusRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(earthRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(marsRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(jupiterRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(saturnRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(uranusRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(neptuneRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(plutoRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(noEntriesRowFn().classList.contains("d-none")).toBeTrue());
+
+  test.do(() => searchFn().value = "M");
+  test.event("input", searchFn, () => venusRowFn().classList.contains("d-none"));
+
+  test.do(() => expect(mercuryRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(venusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(earthRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(marsRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(jupiterRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(saturnRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(uranusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(neptuneRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(plutoRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(noEntriesRowFn().classList.contains("d-none")).toBeTrue());
+
+  test.do(() => searchFn().value = "Ma");
+  test.event("input", searchFn, () => mercuryRowFn().classList.contains("d-none"));
+
+  test.do(() => expect(mercuryRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(venusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(earthRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(marsRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(jupiterRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(saturnRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(uranusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(neptuneRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(plutoRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(noEntriesRowFn().classList.contains("d-none")).toBeTrue());
+
+  test.do(() => searchFn().value = "Max");
+  test.event("input", searchFn, () => marsRowFn().classList.contains("d-none"));
+
+  test.do(() => expect(mercuryRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(venusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(earthRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(marsRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(jupiterRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(saturnRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(uranusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(neptuneRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(plutoRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(noEntriesRowFn().classList.contains("d-none")).toBeFalse());
+
+  test.start();
+});
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/45-selectOneList/SelectOneList.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/45-selectOneList/SelectOneList.xhtml
index 1798beb163..4ea9a2cfc2 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/45-selectOneList/SelectOneList.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/45-selectOneList/SelectOneList.xhtml
@@ -19,69 +19,77 @@
 
 <ui:composition template="/main.xhtml"
                 xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:f="http://xmlns.jcp.org/jsf/core"
                 xmlns:tc="http://myfaces.apache.org/tobago/component"
-                xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
-                xmlns:f="http://xmlns.jcp.org/jsf/core">
+                xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
+  <p>
+    <tc:badge markup="warning" value="Preliminary feature, may be subject to change!"/>
+  </p>
+  <p>Tag Library Documentation:
+    <tc:link label="&lt;tc:selectOneList/>" image="#{request.contextPath}/image/feather-leaf.png"
+             link="#{apiController.tobagoTld}/tc/selectOneList.html"/>
+    |
+    <tc:link label="&lt;tc:selectItem/>" image="#{request.contextPath}/image/feather-leaf.png"
+             link="#{apiController.tobagoTld}/tc/selectItem.html"/>
+    |
+    <tc:link label="&lt;tc:selectItems/>" image="#{request.contextPath}/image/feather-leaf.png"
+             link="#{apiController.tobagoTld}/tc/selectItems.html"/></p>
 
-  <!-- todo -->
-  <tc:badge markup="warning"
-            value="Preliminary feature, may be subject to change!"/><br/>
-
-  <tc:section label="not expanded / no filter">
-    <tc:selectOneList id="selected1" value="#{selectOneListController.selected1}">
-      <tc:selectItems value="#{selectOneListController.planets}"
-                      var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
-    </tc:selectOneList>
+  <tc:section label="Basic">
+    <tc:form id="basic">
+      <tc:selectOneList id="selectedStandard" label="Standard" value="#{selectOneListController.selectedPlanet}">
+        <tc:selectItems value="#{selectOneListController.planets}"
+                        var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
+      </tc:selectOneList>
+      <tc:selectOneList id="selectedDisabled" label="Disabled" value="#{selectOneListController.selectedPlanet}"
+                        disabled="true">
+        <tc:selectItems value="#{selectOneListController.planets}"
+                        var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
+      </tc:selectOneList>
+      <tc:button id="submit" label="Submit"/>
+    </tc:form>
   </tc:section>
 
-  <tc:section label="expanded / no filter">
-    <tc:selectOneList id="selected2" value="#{selectOneListController.selected2}" expanded="true">
+  <tc:section label="Filter">
+    <p>The <code class="language-markup">&lt;tc:selectOneList/></code> component has a <code
+        class="language-markup">filter</code> attribute with four predefined values:
+      contains, startsWith, containsExact, startsWithExact</p>
+    <tc:selectOneList id="selectedFilter" label="Filter (contains)" filter="contains">
       <tc:selectItems value="#{selectOneListController.planets}"
                       var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
     </tc:selectOneList>
-    <tc:button label="Submit"/>
-  </tc:section>
+    <tc:separator/>
+    <p>Choose between different filter types.</p>
+    <tc:segmentLayout id="advancedFilterExample" medium="4seg 8seg">
+      <tc:selectOneRadio label="Filter type" value="#{selectOneListController.filterType}" required="true"
+                         labelLayout="top">
+        <tc:selectItem itemValue="contains"/>
+        <tc:selectItem itemValue="startsWith"/>
+        <tc:selectItem itemValue="containsExact"/>
+        <tc:selectItem itemValue="startsWithExact"/>
+        <f:ajax render="advancedFilterExample" execute="advancedFilterExample"/>
+      </tc:selectOneRadio>
+      <tc:panel>
+        <tc:selectOneList value="#{selectOneListController.selectedName}"
+                          filter="#{selectOneListController.filterType}" expanded="false">
+          <tc:selectItems value="#{selectOneListController.names}"
+                          var="name" itemLabel="#{name}" itemValue="#{name}"/>
+          <tc:style maxHeight="300px"/>
+        </tc:selectOneList>
 
-  <tc:section label="not expanded / with filter">
-    <tc:selectOneList id="selected3" value="#{selectOneListController.selected3}" filter="contains">
-      <tc:selectItems value="#{selectOneListController.planets}"
-                      var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
-    </tc:selectOneList>
-    <tc:button label="Submit"/>
+        Dependent of the type of the filter:
+        Ignores case.
+        Ignores acute (é), grave (è), circumflex (â, î or ô), tilde (ñ), umlaut and
+        dieresis (ü or ï), and cedilla (ç).
+      </tc:panel>
+    </tc:segmentLayout>
+    <!-- todo custom filter types -->
   </tc:section>
 
-  <tc:section label="expanded / with filter:">
-    <tc:selectOneList id="selected4" value="#{selectOneListController.selected4}" filter="contains" expanded="true">
+  <tc:section label="Expanded">
+    <tc:selectOneList id="selectedExpanded" expanded="true">
       <tc:selectItems value="#{selectOneListController.planets}"
                       var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
     </tc:selectOneList>
-    <tc:button label="Submit"/>
   </tc:section>
-
-  <tc:section id="filter" label="Filter types: contains, startsWith, containsExact, startsWithExact">
-    <tc:selectOneRadio label="Filter type" value="#{selectOneListController.filterType}">
-      <tc:selectItem itemValue="#{null}" itemLabel="please select"/>
-      <tc:selectItem itemValue="contains"/>
-      <tc:selectItem itemValue="startsWith"/>
-      <tc:selectItem itemValue="containsExact"/>
-      <tc:selectItem itemValue="startsWithExact"/>
-      <f:ajax render="filter" execute="filter"/>
-    </tc:selectOneRadio>
-    <tc:selectOneList id="selected5" value="#{selectOneListController.selected5}"
-                       filter="#{selectOneListController.filterType}" expanded="true">
-      <tc:selectItems value="#{selectOneListController.names}"
-                      var="name" itemLabel="#{name}" itemValue="#{name}"/>
-      <tc:style maxHeight="300px"/>
-    </tc:selectOneList>
-
-    Dependent of the type of the filter:
-    Ignores case.
-    Ignores acute (é), grave (è), circumflex (â, î or ô), tilde (ñ), umlaut and
-    dieresis (ü or ï), and cedilla (ç).
-  </tc:section>
-
-  <tc:section label="Filter types: custom">
-    todo
-  </tc:section>
-
 </ui:composition>
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.test.js b/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.test.js
new file mode 100644
index 0000000000..e980348b78
--- /dev/null
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.test.js
@@ -0,0 +1,144 @@
+/*
+ * 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.
+ */
+
+import {JasmineTestTool} from "/tobago/test/tobago-test-tool.js";
+import {elementByIdFn, querySelectorAllFn, querySelectorFn} from "/script/tobago-test.js";
+
+it("Standard: remove 'Earth'", function (done) {
+  const removeEarthButtonFn = querySelectorFn("#page\\:mainForm\\:basic\\:selectedStandard\\:\\:field .btn-group[data-tobago-value='Earth'] .tobago-button");
+  const badgeVenusFn = querySelectorFn("#page\\:mainForm\\:basic\\:selectedStandard\\:\\:field .btn-group[data-tobago-value='Venus']");
+  const badgeEarthFn = querySelectorFn("#page\\:mainForm\\:basic\\:selectedStandard\\:\\:field .btn-group[data-tobago-value='Earth']");
+  const badgeJupiterFn = querySelectorFn("#page\\:mainForm\\:basic\\:selectedStandard\\:\\:field .btn-group[data-tobago-value='Jupiter']");
+  const selectedRowsFn = querySelectorAllFn("#page\\:mainForm\\:basic\\:selectedStandard tr.table-primary");
+  const disabledBadgeVenusFn = querySelectorFn("#page\\:mainForm\\:basic\\:selectedDisabled\\:\\:field .btn-group[data-tobago-value='Venus']");
+  const disabledBadgeEarthFn = querySelectorFn("#page\\:mainForm\\:basic\\:selectedDisabled\\:\\:field .btn-group[data-tobago-value='Earth']");
+  const disabledBadgeJupiterFn = querySelectorFn("#page\\:mainForm\\:basic\\:selectedDisabled\\:\\:field .btn-group[data-tobago-value='Jupiter']");
+  const submitFn = elementByIdFn("page:mainForm:basic:submit");
+  const resetFn = elementByIdFn("page:mainForm:basic:reset");
+
+  const test = new JasmineTestTool(done);
+  test.setup(() => selectedRowsFn().length === 3
+          && selectedRowsFn()[0].getAttribute("data-tobago-value") === "Venus"
+          && selectedRowsFn()[1].getAttribute("data-tobago-value") === "Earth"
+          && selectedRowsFn()[2].getAttribute("data-tobago-value") === "Jupiter",
+      null, "click", resetFn);
+
+  test.do(() => expect(badgeVenusFn()).not.toBeNull());
+  test.do(() => expect(badgeEarthFn()).not.toBeNull());
+  test.do(() => expect(badgeJupiterFn()).not.toBeNull());
+  test.do(() => expect(selectedRowsFn()[0].getAttribute("data-tobago-value")).toEqual("Venus"));
+  test.do(() => expect(selectedRowsFn()[1].getAttribute("data-tobago-value")).toEqual("Earth"));
+  test.do(() => expect(selectedRowsFn()[2].getAttribute("data-tobago-value")).toEqual("Jupiter"));
+  test.do(() => expect(disabledBadgeVenusFn()).not.toBeNull());
+  test.do(() => expect(disabledBadgeEarthFn()).not.toBeNull());
+  test.do(() => expect(disabledBadgeJupiterFn()).not.toBeNull());
+
+  test.event("click", removeEarthButtonFn, () => selectedRowsFn().length === 2);
+  test.do(() => expect(badgeVenusFn()).not.toBeNull());
+  test.do(() => expect(badgeEarthFn()).toBeNull());
+  test.do(() => expect(badgeJupiterFn()).not.toBeNull());
+  test.do(() => expect(selectedRowsFn()[0].getAttribute("data-tobago-value")).toEqual("Venus"));
+  test.do(() => expect(selectedRowsFn()[1].getAttribute("data-tobago-value")).toEqual("Jupiter"));
+  test.do(() => expect(disabledBadgeVenusFn()).not.toBeNull());
+  test.do(() => expect(disabledBadgeEarthFn()).not.toBeNull());
+  test.do(() => expect(disabledBadgeJupiterFn()).not.toBeNull());
+
+  test.event("click", submitFn, () => disabledBadgeEarthFn() === null);
+  test.do(() => expect(badgeVenusFn()).not.toBeNull());
+  test.do(() => expect(badgeEarthFn()).toBeNull());
+  test.do(() => expect(badgeJupiterFn()).not.toBeNull());
+  test.do(() => expect(selectedRowsFn()[0].getAttribute("data-tobago-value")).toEqual("Venus"));
+  test.do(() => expect(selectedRowsFn()[1].getAttribute("data-tobago-value")).toEqual("Jupiter"));
+  test.do(() => expect(disabledBadgeVenusFn()).not.toBeNull());
+  test.do(() => expect(disabledBadgeEarthFn()).toBeNull());
+  test.do(() => expect(disabledBadgeJupiterFn()).not.toBeNull());
+
+  test.start();
+});
+
+it("Filter (contains)", function (done) {
+  const searchFn = elementByIdFn("page:mainForm:selectedFilter::filter");
+  const hiddenRowsFn = querySelectorAllFn("#page\\:mainForm\\:selectedFilter tr.d-none:not(.tobago-no-entries)");
+  const mercuryRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Mercury']");
+  const venusRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Venus']");
+  const earthRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Earth']");
+  const marsRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Mars']");
+  const jupiterRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Jupiter']");
+  const saturnRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Saturn']");
+  const uranusRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Uranus']");
+  const neptuneRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Neptune']");
+  const plutoRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr[data-tobago-value='Pluto']");
+  const noEntriesRowFn = querySelectorFn("#page\\:mainForm\\:selectedFilter tr.tobago-no-entries");
+
+  const test = new JasmineTestTool(done);
+  test.setup(() => hiddenRowsFn().length === 0, () => searchFn().value = "", "input", searchFn);
+
+  test.do(() => expect(mercuryRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(venusRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(earthRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(marsRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(jupiterRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(saturnRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(uranusRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(neptuneRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(plutoRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(noEntriesRowFn().classList.contains("d-none")).toBeTrue());
+
+  test.do(() => searchFn().value = "M");
+  test.event("input", searchFn, () => venusRowFn().classList.contains("d-none"));
+
+  test.do(() => expect(mercuryRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(venusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(earthRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(marsRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(jupiterRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(saturnRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(uranusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(neptuneRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(plutoRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(noEntriesRowFn().classList.contains("d-none")).toBeTrue());
+
+  test.do(() => searchFn().value = "Ma");
+  test.event("input", searchFn, () => mercuryRowFn().classList.contains("d-none"));
+
+  test.do(() => expect(mercuryRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(venusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(earthRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(marsRowFn().classList.contains("d-none")).toBeFalse());
+  test.do(() => expect(jupiterRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(saturnRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(uranusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(neptuneRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(plutoRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(noEntriesRowFn().classList.contains("d-none")).toBeTrue());
+
+  test.do(() => searchFn().value = "Max");
+  test.event("input", searchFn, () => marsRowFn().classList.contains("d-none"));
+
+  test.do(() => expect(mercuryRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(venusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(earthRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(marsRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(jupiterRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(saturnRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(uranusRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(neptuneRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(plutoRowFn().classList.contains("d-none")).toBeTrue());
+  test.do(() => expect(noEntriesRowFn().classList.contains("d-none")).toBeFalse());
+
+  test.start();
+});
diff --git a/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.xhtml b/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.xhtml
index 6027033c36..faf5d1328e 100644
--- a/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.xhtml
+++ b/tobago-example/tobago-example-demo/src/main/webapp/content/030-select/80-selectManyList/SelectManyList.xhtml
@@ -19,69 +19,80 @@
 
 <ui:composition template="/main.xhtml"
                 xmlns="http://www.w3.org/1999/xhtml"
+                xmlns:f="http://xmlns.jcp.org/jsf/core"
                 xmlns:tc="http://myfaces.apache.org/tobago/component"
-                xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
-                xmlns:f="http://xmlns.jcp.org/jsf/core">
+                xmlns:ui="http://xmlns.jcp.org/jsf/facelets">
+  <p>
+    <tc:badge markup="warning" value="Preliminary feature, may be subject to change!"/>
+  </p>
+  <p>Tag Library Documentation:
+    <tc:link label="&lt;tc:selectManyList/>" image="#{request.contextPath}/image/feather-leaf.png"
+             link="#{apiController.tobagoTld}/tc/selectManyList.html"/>
+    |
+    <tc:link label="&lt;tc:selectItem/>" image="#{request.contextPath}/image/feather-leaf.png"
+             link="#{apiController.tobagoTld}/tc/selectItem.html"/>
+    |
+    <tc:link label="&lt;tc:selectItems/>" image="#{request.contextPath}/image/feather-leaf.png"
+             link="#{apiController.tobagoTld}/tc/selectItems.html"/></p>
 
-  <!-- todo -->
-  <tc:badge markup="warning"
-            value="Preliminary feature, may be subject to change!"/><br/>
-
-  <tc:section label="not expanded / no filter">
-    <tc:selectManyList id="selected1" value="#{selectManyListController.selected1}">
-      <tc:selectItems value="#{selectManyListController.planets}"
-                      var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
-    </tc:selectManyList>
+  <tc:section label="Basic">
+    <tc:form id="basic">
+      <tc:selectManyList id="selectedStandard" label="Standard" value="#{selectManyListController.selectedPlanets}">
+        <tc:selectItems value="#{selectManyListController.planets}"
+                        var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
+      </tc:selectManyList>
+      <tc:selectManyList id="selectedDisabled" label="Disabled" value="#{selectManyListController.selectedPlanets}"
+                         disabled="true">
+        <tc:selectItems value="#{selectManyListController.planets}"
+                        var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
+      </tc:selectManyList>
+      <tc:button id="submit" label="Submit"/>
+      <tc:button id="reset" label="reset for test" action="#{selectManyListController.resetSelectedPlanets()}">
+        <tc:style customClass="d-none"/>
+      </tc:button>
+    </tc:form>
   </tc:section>
 
-  <tc:section label="expanded / no filter">
-    <tc:selectManyList id="selected2" value="#{selectManyListController.selected2}" expanded="true">
+  <tc:section label="Filter">
+    <p>The <code class="language-markup">&lt;tc:selectManyList/></code> component has a <code
+        class="language-markup">filter</code> attribute with four predefined values:
+      contains, startsWith, containsExact, startsWithExact</p>
+    <tc:selectManyList id="selectedFilter" label="Filter (contains)" filter="contains">
       <tc:selectItems value="#{selectManyListController.planets}"
                       var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
     </tc:selectManyList>
-    <tc:button label="Submit"/>
-  </tc:section>
+    <tc:separator/>
+    <p>Choose between different filter types.</p>
+    <tc:segmentLayout id="advancedFilterExample" medium="4seg 8seg">
+      <tc:selectOneRadio label="Filter type" value="#{selectManyListController.filterType}" required="true"
+                         labelLayout="top">
+        <tc:selectItem itemValue="contains"/>
+        <tc:selectItem itemValue="startsWith"/>
+        <tc:selectItem itemValue="containsExact"/>
+        <tc:selectItem itemValue="startsWithExact"/>
+        <f:ajax render="advancedFilterExample" execute="advancedFilterExample"/>
+      </tc:selectOneRadio>
+      <tc:panel>
+        <tc:selectManyList value="#{selectManyListController.selectedNames}"
+                           filter="#{selectManyListController.filterType}" expanded="false">
+          <tc:selectItems value="#{selectManyListController.names}"
+                          var="name" itemLabel="#{name}" itemValue="#{name}"/>
+          <tc:style maxHeight="300px"/>
+        </tc:selectManyList>
 
-  <tc:section label="not expanded / with filter">
-    <tc:selectManyList id="selected3" value="#{selectManyListController.selected3}" filter="contains">
-      <tc:selectItems value="#{selectManyListController.planets}"
-                      var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
-    </tc:selectManyList>
-    <tc:button label="Submit"/>
+        Dependent of the type of the filter:
+        Ignores case.
+        Ignores acute (é), grave (è), circumflex (â, î or ô), tilde (ñ), umlaut and
+        dieresis (ü or ï), and cedilla (ç).
+      </tc:panel>
+    </tc:segmentLayout>
+    <!-- todo custom filter types -->
   </tc:section>
 
-  <tc:section label="expanded / with filter:">
-    <tc:selectManyList id="selected4" value="#{selectManyListController.selected4}" filter="contains" expanded="true">
+  <tc:section label="Expanded">
+    <tc:selectManyList id="selectedExpanded" expanded="true">
       <tc:selectItems value="#{selectManyListController.planets}"
                       var="planet" itemLabel="#{planet.name}" itemValue="#{planet}"/>
     </tc:selectManyList>
-    <tc:button label="Submit"/>
   </tc:section>
-
-  <tc:section id="filter" label="Filter types: contains, startsWith, containsExact, startsWithExact">
-    <tc:selectOneRadio label="Filter type" value="#{selectManyListController.filterType}">
-      <tc:selectItem itemValue="#{null}" itemLabel="please select"/>
-      <tc:selectItem itemValue="contains"/>
-      <tc:selectItem itemValue="startsWith"/>
-      <tc:selectItem itemValue="containsExact"/>
-      <tc:selectItem itemValue="startsWithExact"/>
-      <f:ajax render="filter" execute="filter"/>
-    </tc:selectOneRadio>
-    <tc:selectManyList id="selected5" value="#{selectManyListController.selected5}"
-                       filter="#{selectManyListController.filterType}" expanded="true">
-      <tc:selectItems value="#{selectManyListController.names}"
-                      var="name" itemLabel="#{name}" itemValue="#{name}"/>
-      <tc:style maxHeight="300px"/>
-    </tc:selectManyList>
-
-    Dependent of the type of the filter:
-    Ignores case.
-    Ignores acute (é), grave (è), circumflex (â, î or ô), tilde (ñ), umlaut and
-    dieresis (ü or ï), and cedilla (ç).
-  </tc:section>
-
-  <tc:section label="Filter types: custom">
-    todo
-  </tc:section>
-
 </ui:composition>