json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java
author Jaroslav Tulach <jtulach@netbeans.org>
Sun, 13 Dec 2015 21:30:52 +0100
changeset 1029 e737d579349a
parent 1028 453e44c757ff
child 1047 8ac11c0b7767
permissions -rw-r--r--
Making sure binding from original to copied property is also kept
     1 /**
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     5  *
     6  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
     7  * Other names may be trademarks of their respective owners.
     8  *
     9  * The contents of this file are subject to the terms of either the GNU
    10  * General Public License Version 2 only ("GPL") or the Common
    11  * Development and Distribution License("CDDL") (collectively, the
    12  * "License"). You may not use this file except in compliance with the
    13  * License. You can obtain a copy of the License at
    14  * http://www.netbeans.org/cddl-gplv2.html
    15  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    16  * specific language governing permissions and limitations under the
    17  * License.  When distributing the software, include this License Header
    18  * Notice in each file and include the License file at
    19  * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    20  * particular file as subject to the "Classpath" exception as provided
    21  * by Oracle in the GPL Version 2 section of the License file that
    22  * accompanied this code. If applicable, add the following below the
    23  * License Header, with the fields enclosed by brackets [] replaced by
    24  * your own identifying information:
    25  * "Portions Copyrighted [year] [name of copyright owner]"
    26  *
    27  * Contributor(s):
    28  *
    29  * The Original Software is NetBeans. The Initial Developer of the Original
    30  * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    31  *
    32  * If you wish your version of this file to be governed by only the CDDL
    33  * or only the GPL Version 2, indicate your decision by adding
    34  * "[Contributor] elects to include this software in this distribution
    35  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    36  * single choice of license, a recipient has the option to distribute
    37  * your version of this file under either the CDDL, the GPL Version 2 or
    38  * to extend the choice of license to its licensees as provided above.
    39  * However, if you add GPL Version 2 code and therefore, elected the GPL
    40  * Version 2 license, then the option applies only if the new code is
    41  * made subject to such option by the copyright holder.
    42  */
    43 package net.java.html.json.tests;
    44 
    45 import java.util.Arrays;
    46 import java.util.List;
    47 import java.util.Timer;
    48 import java.util.TimerTask;
    49 import net.java.html.BrwsrCtx;
    50 import net.java.html.json.ComputedProperty;
    51 import net.java.html.json.Function;
    52 import net.java.html.json.Model;
    53 import net.java.html.json.Models;
    54 import net.java.html.json.Property;
    55 import org.netbeans.html.json.tck.KOTest;
    56 import static net.java.html.json.tests.Utils.assertEquals;
    57 import static net.java.html.json.tests.Utils.assertNotNull;
    58 import static net.java.html.json.tests.Utils.assertTrue;
    59 import static net.java.html.json.tests.Utils.assertFalse;
    60 
    61 /**
    62  *
    63  * @author Jaroslav Tulach
    64  */
    65 @Model(className="KnockoutModel", targetId = "", properties={
    66     @Property(name="name", type=String.class),
    67     @Property(name="results", type=String.class, array = true),
    68     @Property(name="numbers", type=int.class, array = true),
    69     @Property(name="callbackCount", type=int.class),
    70     @Property(name="people", type=PersonImpl.class, array = true),
    71     @Property(name="enabled", type=boolean.class),
    72     @Property(name="latitude", type=double.class),
    73     @Property(name="choice", type=KnockoutTest.Choice.class),
    74     @Property(name="archetype", type=ArchetypeData.class),
    75     @Property(name="archetypes", type=ArchetypeData.class, array = true),
    76 }) 
    77 public final class KnockoutTest {
    78     private KnockoutModel js;
    79     
    80     enum Choice {
    81         A, B;
    82     }
    83     
    84     @ComputedProperty static List<Integer> resultLengths(List<String> results) {
    85         Integer[] arr = new Integer[results.size()];
    86         for (int i = 0; i < arr.length; i++) {
    87             arr[i] = results.get(i).length();
    88         }
    89         return Arrays.asList(arr);
    90     }
    91     
    92     @KOTest public void modifyValueAssertChangeInModelOnEnum() throws Throwable {
    93         Object exp = Utils.exposeHTML(KnockoutTest.class, 
    94             "Latitude: <input id='input' data-bind=\"value: choice\"></input>\n"
    95         );
    96         try {
    97 
    98             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
    99             m.setChoice(Choice.A);
   100             m.applyBindings();
   101 
   102             String v = getSetInput("input", null);
   103             assertEquals("A", v, "Value is really A: " + v);
   104 
   105             getSetInput("input", "B");
   106             triggerEvent("input", "change");
   107 
   108             assertEquals(Choice.B, m.getChoice(), "Enum property updated: " + m.getChoice());
   109         } catch (Throwable t) {
   110             throw t;
   111         } finally {
   112             Utils.exposeHTML(KnockoutTest.class, "");
   113         }
   114     }
   115 
   116 
   117     @KOTest public void modifyRadioValueOnEnum() throws Throwable {
   118         Object exp = Utils.exposeHTML(KnockoutTest.class,
   119             "<input id='i1' type=\"radio\" name=\"choice\" value=\"A\" data-bind=\"checked: choice\"></input>Right\n" +
   120             "<input id='input' type=\"radio\" name=\"choice\" value=\"B\" data-bind=\"checked: choice\"></input>Never\n" +
   121             "\n"
   122         );
   123         try {
   124 
   125             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   126             m.setChoice(Choice.B);
   127             m.applyBindings();
   128 
   129             assertFalse(isChecked("i1"), "B should be checked now");
   130             assertTrue(isChecked("input"), "B should be checked now");
   131 
   132             triggerEvent("i1", "click");
   133             assertEquals(Choice.A, m.getChoice(), "Switched to A");
   134             assertTrue(isChecked("i1"), "A should be checked now");
   135             assertFalse(isChecked("input"), "A should be checked now");
   136 
   137             triggerEvent("input", "click");
   138 
   139             assertEquals(Choice.B, m.getChoice(), "Enum property updated: " + m.getChoice());
   140         } catch (Throwable t) {
   141             throw t;
   142         } finally {
   143             Utils.exposeHTML(KnockoutTest.class, "");
   144         }
   145     }
   146     
   147     @KOTest public void modifyValueAssertChangeInModelOnDouble() throws Throwable {
   148         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   149             "Latitude: <input id='input' data-bind=\"value: latitude\"></input>\n"
   150         );
   151         try {
   152 
   153             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   154             m.setLatitude(50.5);
   155             m.applyBindings();
   156 
   157             String v = getSetInput("input", null);
   158             assertEquals("50.5", v, "Value is really 50.5: " + v);
   159 
   160             getSetInput("input", "49.5");
   161             triggerEvent("input", "change");
   162 
   163             assertEquals(49.5, m.getLatitude(), "Double property updated: " + m.getLatitude());
   164         } catch (Throwable t) {
   165             throw t;
   166         } finally {
   167             Utils.exposeHTML(KnockoutTest.class, "");
   168         }
   169     }
   170     
   171     @KOTest public void rawObject() {
   172         final BrwsrCtx ctx = newContext();
   173         Person p1 = Models.bind(new Person(), ctx);
   174         p1.setFirstName("Jarda");
   175         p1.setLastName("Tulach");
   176         Object raw = Models.toRaw(p1);
   177         Person p2 = Models.fromRaw(ctx, Person.class, raw);
   178         
   179         assertEquals(p2.getFirstName(), "Jarda", "First name");
   180         assertEquals(p2.getLastName(), "Tulach", "Last name");
   181 
   182         p2.setFirstName("Jirka");
   183         assertEquals(p2.getFirstName(), "Jirka", "First name updated");
   184         assertEquals(p1.getFirstName(), "Jirka", "First name updated in original object");
   185 
   186         p1.setFirstName("Ondra");
   187         assertEquals(p1.getFirstName(), "Ondra", "1st name updated in original object");
   188         assertEquals(p2.getFirstName(), "Ondra", "1st name updated in copied object");
   189     }
   190 
   191     @KOTest public void modifyComputedProperty() throws Throwable {
   192         Object exp = Utils.exposeHTML(KnockoutTest.class,
   193             "Full name: <div data-bind='with:firstPerson'>\n"
   194                 + "<input id='input' data-bind=\"value: fullName\"></input>\n"
   195                 + "</div>\n"
   196         );
   197         try {
   198             KnockoutModel m = new KnockoutModel();
   199             m.getPeople().add(new Person());
   200 
   201             m = Models.bind(m, newContext());
   202             m.getFirstPerson().setFirstName("Jarda");
   203             m.getFirstPerson().setLastName("Tulach");
   204             m.applyBindings();
   205 
   206             String v = getSetInput("input", null);
   207             assertEquals("Jarda Tulach", v, "Value: " + v);
   208 
   209             getSetInput("input", "Mickey Mouse");
   210             triggerEvent("input", "change");
   211 
   212             assertEquals("Mickey", m.getFirstPerson().getFirstName(), "First name updated");
   213             assertEquals("Mouse", m.getFirstPerson().getLastName(), "Last name updated");
   214         } catch (Throwable t) {
   215             throw t;
   216         } finally {
   217             Utils.exposeHTML(KnockoutTest.class, "");
   218         }
   219     }
   220     
   221     @KOTest public void modifyValueAssertChangeInModelOnBoolean() throws Throwable {
   222         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   223             "Latitude: <input id='input' data-bind=\"value: enabled\"></input>\n"
   224         );
   225         try {
   226 
   227             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   228             m.setEnabled(true);
   229             m.applyBindings();
   230 
   231             String v = getSetInput("input", null);
   232             assertEquals("true", v, "Value is really true: " + v);
   233 
   234             getSetInput("input", "false");
   235             triggerEvent("input", "change");
   236 
   237             assertFalse(m.isEnabled(), "Boolean property updated: " + m.isEnabled());
   238         } catch (Throwable t) {
   239             throw t;
   240         } finally {
   241             Utils.exposeHTML(KnockoutTest.class, "");
   242         }
   243     }
   244     
   245     @KOTest public void modifyValueAssertChangeInModel() throws Exception {
   246         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   247             "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
   248             "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
   249             "<button id=\"hello\">Say Hello!</button>\n"
   250         );
   251         try {
   252 
   253             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   254             m.setName("Kukuc");
   255             m.applyBindings();
   256 
   257             String v = getSetInput("input", null);
   258             assertEquals("Kukuc", v, "Value is really kukuc: " + v);
   259 
   260             getSetInput("input", "Jardo");
   261             triggerEvent("input", "change");
   262 
   263             assertEquals("Jardo", m.getName(), "Name property updated: " + m.getName());
   264         } finally {
   265             Utils.exposeHTML(KnockoutTest.class, "");
   266         }
   267     }
   268     
   269     private static String getSetSelected(int index, Object value) throws Exception {
   270         String s = "var index = arguments[0];\n"
   271         + "var value = arguments[1];\n"
   272         + "var n = window.document.getElementById('input'); \n "
   273         + "if (value != null) {\n"
   274         + "  n.options[index].value = 'me'; \n"
   275         + "  n.value = 'me'; \n"
   276         + "  ko.dataFor(n.options[index]).archetype(value); // haven't found better way to trigger ko change yet \n"
   277         + "} \n "
   278         + "var op = n.options[n.selectedIndex]; \n"
   279         + "return op ? op.text : n.selectedIndex;\n";
   280         Object ret = Utils.executeScript(
   281             KnockoutTest.class,
   282             s, index, value
   283         );
   284         return ret == null ? null : ret.toString();
   285     }
   286     
   287     @Model(className = "ArchetypeData", properties = {
   288         @Property(name = "artifactId", type = String.class),
   289         @Property(name = "groupId", type = String.class),
   290         @Property(name = "version", type = String.class),
   291         @Property(name = "name", type = String.class),
   292         @Property(name = "description", type = String.class),
   293         @Property(name = "url", type = String.class),
   294     })
   295     static class ArchModel {
   296     }
   297     
   298     @KOTest public void selectWorksOnModels() throws Exception {
   299         if (js == null) {
   300             Utils.exposeHTML(KnockoutTest.class, 
   301                 "<select id='input' data-bind=\"options: archetypes,\n" +
   302 "                       optionsText: 'name',\n" +
   303 "                       value: archetype\">\n" +
   304 "                  </select>\n" +
   305 ""
   306             );
   307             
   308             js = Models.bind(new KnockoutModel(), newContext());
   309             js.getArchetypes().add(new ArchetypeData("ko4j", "org.netbeans.html", "0.8.3", "ko4j", "ko4j", null));
   310             js.getArchetypes().add(new ArchetypeData("crud", "org.netbeans.html", "0.8.3", "crud", "crud", null));
   311             js.getArchetypes().add(new ArchetypeData("3rd", "org.netbeans.html", "0.8.3", "3rd", "3rd", null));
   312             js.setArchetype(js.getArchetypes().get(1));
   313             js.applyBindings();
   314             
   315             String v = getSetSelected(0, null);
   316             assertEquals("crud", v, "Second index (e.g. crud) is selected: " + v);
   317             
   318             String sel = getSetSelected(2, Models.toRaw(js.getArchetypes().get(2)));
   319             assertEquals("3rd", sel, "3rd is selected now: " + sel);
   320         }
   321         
   322         if (js.getArchetype() != js.getArchetypes().get(2)) {
   323             throw new InterruptedException();
   324         }
   325         
   326         Utils.exposeHTML(KnockoutTest.class, "");
   327     }
   328 
   329     @KOTest public void nestedObjectEqualsChange() throws Exception {
   330         nestedObjectEqualsChange(true);
   331     }
   332 
   333     @KOTest public void nestedObjectChange() throws Exception {
   334         nestedObjectEqualsChange(false);
   335     }
   336     private  void nestedObjectEqualsChange(boolean preApply) throws Exception {
   337         Utils.exposeHTML(KnockoutTest.class,
   338 "            <div data-bind='with: archetype'>\n" +
   339 "                <input id='input' data-bind='value: groupId'></input>\n" +
   340 "            </div>\n"
   341         );
   342 
   343         js = Models.bind(new KnockoutModel(), newContext());
   344         if (preApply) {
   345             js.applyBindings();
   346         }
   347         js.setArchetype(new ArchetypeData());
   348         js.getArchetype().setGroupId("org.netbeans.html");
   349         js.applyBindings();
   350 
   351         String v = getSetInput("input", null);
   352         assertEquals("org.netbeans.html", v, "groupId has been changed");
   353         Utils.exposeHTML(KnockoutTest.class, "");
   354     }
   355     
   356     @KOTest public void modifyValueAssertAsyncChangeInModel() throws Exception {
   357         if (js == null) {
   358             Utils.exposeHTML(KnockoutTest.class, 
   359                 "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
   360                 "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
   361                 "<button id=\"hello\">Say Hello!</button>\n"
   362             );
   363             
   364             js = Models.bind(new KnockoutModel(), newContext());
   365             js.setName("Kukuc");
   366             js.applyBindings();
   367             
   368             String v = getSetInput("input", null);
   369             assertEquals("Kukuc", v, "Value is really kukuc: " + v);
   370             
   371             Timer t = new Timer("Set to Jardo");
   372             t.schedule(new TimerTask() {
   373                 @Override
   374                 public void run() {
   375                     js.setName("Jardo");
   376                 }
   377             }, 1);
   378         }
   379         
   380         String v = getSetInput("input", null);
   381         if (!"Jardo".equals(v)) {
   382             throw new InterruptedException();
   383         }
   384         
   385         Utils.exposeHTML(KnockoutTest.class, "");
   386     }
   387     
   388     private static String getSetInput(String id, String value) throws Exception {
   389         String s = "var value = arguments[0];\n"
   390         + "var n = window.document.getElementById(arguments[1]); \n "
   391         + "if (value != null) n['value'] = value; \n "
   392         + "return n['value'];";
   393         Object ret = Utils.executeScript(
   394             KnockoutTest.class,
   395             s, value, id
   396         );
   397         return ret == null ? null : ret.toString();
   398     }
   399 
   400     private static boolean isChecked(String id) throws Exception {
   401         String s = ""
   402         + "var n = window.document.getElementById(arguments[0]); \n "
   403         + "return n['checked'];";
   404         Object ret = Utils.executeScript(
   405             KnockoutTest.class,
   406             s, id
   407         );
   408         return Boolean.TRUE.equals(ret);
   409     }
   410     
   411     public static void triggerEvent(String id, String ev) throws Exception {
   412         Utils.executeScript(
   413             KnockoutTest.class,
   414             "ko.utils.triggerEvent(window.document.getElementById(arguments[0]), arguments[1]);",
   415             id, ev
   416         );
   417     }
   418     
   419     @KOTest public void displayContentOfArray() throws Exception {
   420         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   421             "<ul id='ul' data-bind='foreach: results'>\n"
   422             + "  <li data-bind='text: $data, click: $root.call'/>\n"
   423             + "</ul>\n"
   424         );
   425         try {
   426             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   427             m.getResults().add("Ahoj");
   428             m.applyBindings();
   429 
   430             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   431             assertEquals(cnt, 1, "One child, but was " + cnt);
   432 
   433             m.getResults().add("Hi");
   434 
   435             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   436             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   437 
   438             triggerChildClick("ul", 1);
   439 
   440             assertEquals(1, m.getCallbackCount(), "One callback " + m.getCallbackCount());
   441             assertEquals("Hi", m.getName(), "We got callback from 2nd child " + m.getName());
   442         } finally {
   443             Utils.exposeHTML(KnockoutTest.class, "");
   444         }
   445     }
   446     
   447     @KOTest public void displayContentOfAsyncArray() throws Exception {
   448         if (js == null) {
   449             Utils.exposeHTML(KnockoutTest.class, 
   450                 "<ul id='ul' data-bind='foreach: results'>\n"
   451                 + "  <li data-bind='text: $data, click: $root.call'/>\n"
   452                 + "</ul>\n"
   453             );
   454             js = Models.bind(new KnockoutModel(), newContext());
   455             js.getResults().add("Ahoj");
   456             js.applyBindings();
   457 
   458             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   459             assertEquals(cnt, 1, "One child, but was " + cnt);
   460             
   461             Timer t = new Timer("add to array");
   462             t.schedule(new TimerTask() {
   463                 @Override
   464                 public void run() {
   465                     js.getResults().add("Hi");
   466                 }
   467             }, 1);
   468         }
   469 
   470 
   471         int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   472         if (cnt != 2) {
   473             throw new InterruptedException();
   474         }
   475 
   476         try {
   477             triggerChildClick("ul", 1);
   478 
   479             assertEquals(1, js.getCallbackCount(), "One callback " + js.getCallbackCount());
   480             assertEquals("Hi", js.getName(), "We got callback from 2nd child " + js.getName());
   481         } finally {
   482             Utils.exposeHTML(KnockoutTest.class, "");
   483         }
   484     }
   485     
   486     @KOTest public void displayContentOfComputedArray() throws Exception {
   487         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   488             "<ul id='ul' data-bind='foreach: bothNames'>\n"
   489             + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   490             + "</ul>\n"
   491         );
   492         try {
   493             Pair m = Models.bind(new Pair("First", "Last", null), newContext());
   494             m.applyBindings();
   495 
   496             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   497             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   498 
   499             triggerChildClick("ul", 1);
   500 
   501             assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   502             
   503             m.setLastName("Verylast");
   504 
   505             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   506             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   507             
   508             triggerChildClick("ul", 1);
   509 
   510             assertEquals("Verylast", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   511             
   512         } finally {
   513             Utils.exposeHTML(KnockoutTest.class, "");
   514         }
   515     }
   516     
   517     @KOTest public void displayContentOfComputedArrayOnASubpair() throws Exception {
   518         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   519               "<div data-bind='with: next'>\n"
   520             + "<ul id='ul' data-bind='foreach: bothNames'>\n"
   521             + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   522             + "</ul>"
   523             + "</div>\n"
   524         );
   525         try {
   526             final BrwsrCtx ctx = newContext();
   527             Pair m = Models.bind(new Pair(null, null, new Pair("First", "Last", null)), ctx);
   528             m.applyBindings();
   529 
   530             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   531             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   532 
   533             triggerChildClick("ul", 1);
   534             
   535             assertEquals(PairModel.ctx, ctx, "Context remains the same");
   536 
   537             assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   538         } finally {
   539             Utils.exposeHTML(KnockoutTest.class, "");
   540         }
   541     }
   542     
   543     @KOTest public void displayContentOfComputedArrayOnComputedASubpair() throws Exception {
   544         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   545               "<div data-bind='with: nextOne'>\n"
   546             + "<ul id='ul' data-bind='foreach: bothNames'>\n"
   547             + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   548             + "</ul>"
   549             + "</div>\n"
   550         );
   551         try {
   552             Pair m = Models.bind(new Pair(null, null, new Pair("First", "Last", null)), newContext());
   553             m.applyBindings();
   554 
   555             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   556             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   557 
   558             triggerChildClick("ul", 1);
   559 
   560             assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   561         } finally {
   562             Utils.exposeHTML(KnockoutTest.class, "");
   563         }
   564     }
   565 
   566     @KOTest public void checkBoxToBooleanBinding() throws Exception {
   567         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   568             "<input type='checkbox' id='b' data-bind='checked: enabled'></input>\n"
   569         );
   570         try {
   571             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   572             m.applyBindings();
   573 
   574             assertFalse(m.isEnabled(), "Is disabled");
   575 
   576             triggerClick("b");
   577 
   578             assertTrue(m.isEnabled(), "Now the model is enabled");
   579         } finally {
   580             Utils.exposeHTML(KnockoutTest.class, "");
   581         }
   582     }
   583     
   584     
   585     
   586     @KOTest public void displayContentOfDerivedArray() throws Exception {
   587         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   588             "<ul id='ul' data-bind='foreach: cmpResults'>\n"
   589             + "  <li><b data-bind='text: $data'></b></li>\n"
   590             + "</ul>\n"
   591         );
   592         try {
   593             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   594             m.getResults().add("Ahoj");
   595             m.applyBindings();
   596 
   597             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   598             assertEquals(cnt, 1, "One child, but was " + cnt);
   599 
   600             m.getResults().add("hello");
   601 
   602             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   603             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   604         } finally {
   605             Utils.exposeHTML(KnockoutTest.class, "");
   606         }
   607     }
   608     
   609     @KOTest public void displayContentOfArrayOfPeople() throws Exception {
   610         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   611             "<ul id='ul' data-bind='foreach: people'>\n"
   612             + "  <li data-bind='text: $data.firstName, click: $root.removePerson'></li>\n"
   613             + "</ul>\n"
   614         );
   615         try {
   616             final BrwsrCtx c = newContext();
   617             KnockoutModel m = Models.bind(new KnockoutModel(), c);
   618 
   619             final Person first = Models.bind(new Person(), c);
   620             first.setFirstName("first");
   621             m.getPeople().add(first);
   622 
   623             m.applyBindings();
   624 
   625             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   626             assertEquals(cnt, 1, "One child, but was " + cnt);
   627 
   628             final Person second = Models.bind(new Person(), c);
   629             second.setFirstName("second");
   630             m.getPeople().add(second);
   631 
   632             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   633             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   634 
   635             triggerChildClick("ul", 1);
   636 
   637             assertEquals(1, m.getCallbackCount(), "One callback " + m.getCallbackCount());
   638 
   639             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   640             assertEquals(cnt , 1, "Again one child, but was " + cnt);
   641 
   642             String txt = childText("ul", 0);
   643             assertEquals("first", txt, "Expecting 'first': " + txt);
   644 
   645             first.setFirstName("changed");
   646 
   647             txt = childText("ul", 0);
   648             assertEquals("changed", txt, "Expecting 'changed': " + txt);
   649         } finally {
   650             Utils.exposeHTML(KnockoutTest.class, "");
   651         }
   652     }
   653     
   654     @ComputedProperty
   655     static Person firstPerson(List<Person> people) {
   656         return people.isEmpty() ? null : people.get(0);
   657     }
   658     
   659     @KOTest public void accessFirstPersonWithOnFunction() throws Exception {
   660         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   661             "<p id='ul' data-bind='with: firstPerson'>\n"
   662             + "  <span data-bind='text: firstName, click: changeSex'></span>\n"
   663             + "</p>\n"
   664         );
   665         try {
   666             trasfertToFemale();
   667         } finally {
   668             Utils.exposeHTML(KnockoutTest.class, "");
   669         }
   670     }
   671     
   672     @KOTest public void onPersonFunction() throws Exception {
   673         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   674             "<ul id='ul' data-bind='foreach: people'>\n"
   675             + "  <li data-bind='text: $data.firstName, click: changeSex'></li>\n"
   676             + "</ul>\n"
   677         );
   678         try {
   679             trasfertToFemale();
   680         } finally {
   681             Utils.exposeHTML(KnockoutTest.class, "");
   682         }
   683     }
   684     
   685     private void trasfertToFemale() throws Exception {
   686         KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   687 
   688         final Person first = Models.bind(new Person(), newContext());
   689         first.setFirstName("first");
   690         first.setSex(Sex.MALE);
   691         m.getPeople().add(first);
   692 
   693 
   694         m.applyBindings();
   695 
   696         int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   697         assertEquals(cnt, 1, "One child, but was " + cnt);
   698 
   699 
   700         triggerChildClick("ul", 0);
   701 
   702         assertEquals(first.getSex(), Sex.FEMALE, "Transverted to female: " + first.getSex());
   703     }
   704     
   705     @KOTest public void stringArrayModificationVisible() throws Exception {
   706         Object exp = Utils.exposeHTML(KnockoutTest.class,
   707                 "<div>\n"
   708                 + "<ul id='ul' data-bind='foreach: results'>\n"
   709                 + "  <li data-bind='text: $data'></li>\n"
   710                 + "</ul>\n"
   711               + "</div>\n"
   712         );
   713         try {
   714             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   715             m.getResults().add("Ahoj");
   716             m.getResults().add("Hello");
   717             m.applyBindings();
   718             
   719             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   720             assertEquals(cnt, 2, "Two children " + cnt);
   721             
   722             Object arr = Utils.addChildren(KnockoutTest.class, "ul", "results", "Hi");
   723             assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   724             final int len = ((Object[])arr).length;
   725             
   726             assertEquals(len, 3, "Three elements in the array " + len);
   727             
   728             int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   729             assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
   730             
   731             assertEquals(m.getResults().size(), 3, "Three java strings: " + m.getResults());
   732         } finally {
   733             Utils.exposeHTML(KnockoutTest.class, "");
   734         }
   735     }
   736 
   737     @KOTest public void intArrayModificationVisible() throws Exception {
   738         Object exp = Utils.exposeHTML(KnockoutTest.class,
   739                 "<div>\n"
   740                 + "<ul id='ul' data-bind='foreach: numbers'>\n"
   741                 + "  <li data-bind='text: $data'></li>\n"
   742                 + "</ul>\n"
   743               + "</div>\n"
   744         );
   745         try {
   746             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   747             m.getNumbers().add(1);
   748             m.getNumbers().add(31);
   749             m.applyBindings();
   750             
   751             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   752             assertEquals(cnt, 2, "Two children " + cnt);
   753             
   754             Object arr = Utils.addChildren(KnockoutTest.class, "ul", "numbers", 42);
   755             assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   756             final int len = ((Object[])arr).length;
   757             
   758             assertEquals(len, 3, "Three elements in the array " + len);
   759             
   760             int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   761             assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
   762             
   763             assertEquals(m.getNumbers().size(), 3, "Three java ints: " + m.getNumbers());
   764             assertEquals(m.getNumbers().get(2), 42, "Meaning of world: " + m.getNumbers());
   765         } finally {
   766             Utils.exposeHTML(KnockoutTest.class, "");
   767         }
   768     }
   769 
   770     @KOTest public void derivedIntArrayModificationVisible() throws Exception {
   771         Object exp = Utils.exposeHTML(KnockoutTest.class,
   772                 "<div>\n"
   773                 + "<ul id='ul' data-bind='foreach: resultLengths'>\n"
   774                 + "  <li data-bind='text: $data'></li>\n"
   775                 + "</ul>\n"
   776               + "</div>\n"
   777         );
   778         try {
   779             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   780             m.getResults().add("Ahoj");
   781             m.getResults().add("Hello");
   782             m.applyBindings();
   783             
   784             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   785             assertEquals(cnt, 2, "Two children " + cnt);
   786             
   787             Object arr = Utils.addChildren(KnockoutTest.class, "ul", "results", "Hi");
   788             assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   789             final int len = ((Object[])arr).length;
   790             
   791             assertEquals(len, 3, "Three elements in the array " + len);
   792             
   793             int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   794             assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
   795             
   796             assertEquals(m.getResultLengths().size(), 3, "Three java ints: " + m.getResultLengths());
   797             assertEquals(m.getResultLengths().get(2), 2, "Size is two: " + m.getResultLengths());
   798         } finally {
   799             Utils.exposeHTML(KnockoutTest.class, "");
   800         }
   801     }
   802     
   803     @KOTest public void archetypeArrayModificationVisible() throws Exception {
   804         Object exp = Utils.exposeHTML(KnockoutTest.class,
   805                 "<div>\n"
   806                 + "<ul id='ul' data-bind='foreach: archetypes'>\n"
   807                 + "  <li data-bind='text: artifactId'></li>\n"
   808                 + "</ul>\n"
   809               + "</div>\n"
   810         );
   811         try {
   812             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   813             m.applyBindings();
   814             
   815             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   816             assertEquals(cnt, 0, "No children " + cnt);
   817             
   818             Object arr = Utils.addChildren(KnockoutTest.class, "ul", "archetypes", new ArchetypeData("aid", "gid", "v", "n", "d", "u"));
   819             assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   820             final int len = ((Object[])arr).length;
   821             
   822             assertEquals(len, 1, "One element in the array " + len);
   823             
   824             int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   825             assertEquals(newCnt, 1, "One child in the DOM: " + newCnt);
   826             
   827             assertEquals(m.getArchetypes().size(), 1, "One archetype: " + m.getArchetypes());
   828             assertNotNull(m.getArchetypes().get(0), "Not null: " + m.getArchetypes());
   829             assertEquals(m.getArchetypes().get(0).getArtifactId(), "aid", "'aid' == " + m.getArchetypes());
   830         } finally {
   831             Utils.exposeHTML(KnockoutTest.class, "");
   832         }
   833     }
   834 
   835     @Function
   836     static void call(KnockoutModel m, String data) {
   837         m.setName(data);
   838         m.setCallbackCount(m.getCallbackCount() + 1);
   839     }
   840 
   841     @Function
   842     static void removePerson(KnockoutModel model, Person data) {
   843         model.setCallbackCount(model.getCallbackCount() + 1);
   844         model.getPeople().remove(data);
   845     }
   846     
   847     
   848     @ComputedProperty
   849     static String helloMessage(String name) {
   850         return "Hello " + name + "!";
   851     }
   852     
   853     @ComputedProperty
   854     static List<String> cmpResults(List<String> results) {
   855         return results;
   856     }
   857     
   858     private static void triggerClick(String id) throws Exception {
   859         String s = "var id = arguments[0];"
   860             + "var e = window.document.getElementById(id);\n "
   861             + "if (e.checked) throw 'It should not be checked yet: ' + e;\n "
   862             + "var ev = window.document.createEvent('MouseEvents');\n "
   863             + "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n "
   864             + "e.dispatchEvent(ev);\n "
   865             + "if (!e.checked) {\n"
   866             + "  e.checked = true;\n "
   867             + "  e.dispatchEvent(ev);\n "
   868             + "}\n";
   869         Utils.executeScript(
   870             KnockoutTest.class,
   871             s, id);
   872     }
   873     private static void triggerChildClick(String id, int pos) throws Exception {
   874         String s = 
   875             "var id = arguments[0]; var pos = arguments[1];\n" +
   876             "var e = window.document.getElementById(id);\n " +
   877             "var ev = window.document.createEvent('MouseEvents');\n " +
   878             "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n " +
   879             "var list = e.childNodes;\n" +
   880             "var cnt = -1;\n" + 
   881             "for (var i = 0; i < list.length; i++) {\n" + 
   882             "  if (list[i].nodeType == 1) cnt++;\n" + 
   883             "  if (cnt == pos) return list[i].dispatchEvent(ev);\n" + 
   884             "}\n" + 
   885             "return null;\n";
   886         Utils.executeScript(
   887             KnockoutTest.class,
   888             s, id, pos);
   889     }
   890 
   891     private static String childText(String id, int pos) throws Exception {
   892         String s = 
   893             "var id = arguments[0]; var pos = arguments[1];" +
   894             "var e = window.document.getElementById(id);\n" +
   895             "var list = e.childNodes;\n" +
   896             "var cnt = -1;\n" + 
   897             "for (var i = 0; i < list.length; i++) {\n" + 
   898             "  if (list[i].nodeType == 1) cnt++;\n" + 
   899             "  if (cnt == pos) return list[i].innerHTML;\n" + 
   900             "}\n" + 
   901             "return null;\n";
   902         return (String)Utils.executeScript(
   903             KnockoutTest.class,
   904             s, id, pos);
   905     }
   906 
   907     private static BrwsrCtx newContext() {
   908         return Utils.newContext(KnockoutTest.class);
   909     }
   910 }