json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java
author Jaroslav Tulach <jtulach@netbeans.org>
Mon, 22 Feb 2016 19:58:32 +0100
branchNonMutable258088
changeset 1055 c61d247f087a
parent 1047 8ac11c0b7767
child 1072 807ddf523941
permissions -rw-r--r--
#258088: Non-mutable values aren't wrapped into ko.observable, but rather stored as primitive values
     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() throws Exception {
   172         if (js == null) {
   173             final BrwsrCtx ctx = newContext();
   174             Person p1 = Models.bind(new Person(), ctx);
   175             p1.setFirstName("Jarda");
   176             p1.setLastName("Tulach");
   177             Object raw = Models.toRaw(p1);
   178             Person p2 = Models.fromRaw(ctx, Person.class, raw);
   179 
   180             assertEquals(p2.getFirstName(), "Jarda", "First name");
   181             assertEquals(p2.getLastName(), "Tulach", "Last name");
   182 
   183             p2.setFirstName("Jirka");
   184             assertEquals(p2.getFirstName(), "Jirka", "First name updated");
   185 
   186             js = new KnockoutModel();
   187             js.getPeople().add(p1);
   188             js.getPeople().add(p2);
   189         }
   190 
   191         Person p1 = js.getPeople().get(0);
   192         Person p2 = js.getPeople().get(1);
   193 
   194         if (js.getPeople().size() == 2) {
   195             if (!"Jirka".equals(p1.getFirstName())) {
   196                 throw new InterruptedException();
   197             }
   198 
   199             assertEquals(p1.getFirstName(), "Jirka", "First name updated in original object");
   200 
   201             p1.setFirstName("Ondra");
   202             assertEquals(p1.getFirstName(), "Ondra", "1st name updated in original object");
   203 
   204             js.getPeople().add(p1);
   205         }
   206 
   207         if (!"Ondra".equals(p2.getFirstName())) {
   208             throw new InterruptedException();
   209         }
   210         assertEquals(p2.getFirstName(), "Ondra", "1st name updated in copied object");
   211     }
   212 
   213     @KOTest public void modifyComputedProperty() throws Throwable {
   214         Object exp = Utils.exposeHTML(KnockoutTest.class,
   215             "Full name: <div data-bind='with:firstPerson'>\n"
   216                 + "<input id='input' data-bind=\"value: fullName\"></input>\n"
   217                 + "</div>\n"
   218         );
   219         try {
   220             KnockoutModel m = new KnockoutModel();
   221             m.getPeople().add(new Person());
   222 
   223             m = Models.bind(m, newContext());
   224             m.getFirstPerson().setFirstName("Jarda");
   225             m.getFirstPerson().setLastName("Tulach");
   226             m.applyBindings();
   227 
   228             String v = getSetInput("input", null);
   229             assertEquals("Jarda Tulach", v, "Value: " + v);
   230 
   231             getSetInput("input", "Mickey Mouse");
   232             triggerEvent("input", "change");
   233 
   234             assertEquals("Mickey", m.getFirstPerson().getFirstName(), "First name updated");
   235             assertEquals("Mouse", m.getFirstPerson().getLastName(), "Last name updated");
   236         } catch (Throwable t) {
   237             throw t;
   238         } finally {
   239             Utils.exposeHTML(KnockoutTest.class, "");
   240         }
   241     }
   242 
   243     @KOTest public void modifyValueAssertChangeInModelOnBoolean() throws Throwable {
   244         Object exp = Utils.exposeHTML(KnockoutTest.class,
   245             "Latitude: <input id='input' data-bind=\"value: enabled\"></input>\n"
   246         );
   247         try {
   248 
   249             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   250             m.setEnabled(true);
   251             m.applyBindings();
   252 
   253             String v = getSetInput("input", null);
   254             assertEquals("true", v, "Value is really true: " + v);
   255 
   256             getSetInput("input", "false");
   257             triggerEvent("input", "change");
   258 
   259             assertFalse(m.isEnabled(), "Boolean property updated: " + m.isEnabled());
   260         } catch (Throwable t) {
   261             throw t;
   262         } finally {
   263             Utils.exposeHTML(KnockoutTest.class, "");
   264         }
   265     }
   266 
   267     @KOTest public void modifyValueAssertChangeInModel() throws Exception {
   268         Object exp = Utils.exposeHTML(KnockoutTest.class,
   269             "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
   270             "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
   271             "<button id=\"hello\">Say Hello!</button>\n"
   272         );
   273         try {
   274 
   275             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   276             m.setName("Kukuc");
   277             m.applyBindings();
   278 
   279             String v = getSetInput("input", null);
   280             assertEquals("Kukuc", v, "Value is really kukuc: " + v);
   281 
   282             getSetInput("input", "Jardo");
   283             triggerEvent("input", "change");
   284 
   285             assertEquals("Jardo", m.getName(), "Name property updated: " + m.getName());
   286         } finally {
   287             Utils.exposeHTML(KnockoutTest.class, "");
   288         }
   289     }
   290 
   291     private static String getSetSelected(int index, Object value) throws Exception {
   292         String s = "var index = arguments[0];\n"
   293         + "var value = arguments[1];\n"
   294         + "var n = window.document.getElementById('input'); \n "
   295         + "if (value != null) {\n"
   296         + "  n.options[index].value = 'me'; \n"
   297         + "  n.value = 'me'; \n"
   298         + "  ko.dataFor(n.options[index]).archetype(value); // haven't found better way to trigger ko change yet \n"
   299         + "} \n "
   300         + "var op = n.options[n.selectedIndex]; \n"
   301         + "return op ? op.text : n.selectedIndex;\n";
   302         Object ret = Utils.executeScript(
   303             KnockoutTest.class,
   304             s, index, value
   305         );
   306         return ret == null ? null : ret.toString();
   307     }
   308 
   309     @Model(className = "ArchetypeData", properties = {
   310         @Property(name = "artifactId", type = String.class),
   311         @Property(name = "groupId", type = String.class),
   312         @Property(name = "version", type = String.class),
   313         @Property(name = "name", type = String.class),
   314         @Property(name = "description", type = String.class),
   315         @Property(name = "url", type = String.class),
   316     })
   317     static class ArchModel {
   318     }
   319 
   320     @KOTest public void selectWorksOnModels() throws Exception {
   321         if (js == null) {
   322             Utils.exposeHTML(KnockoutTest.class,
   323                 "<select id='input' data-bind=\"options: archetypes,\n" +
   324 "                       optionsText: 'name',\n" +
   325 "                       value: archetype\">\n" +
   326 "                  </select>\n" +
   327 ""
   328             );
   329 
   330             js = Models.bind(new KnockoutModel(), newContext());
   331             js.getArchetypes().add(new ArchetypeData("ko4j", "org.netbeans.html", "0.8.3", "ko4j", "ko4j", null));
   332             js.getArchetypes().add(new ArchetypeData("crud", "org.netbeans.html", "0.8.3", "crud", "crud", null));
   333             js.getArchetypes().add(new ArchetypeData("3rd", "org.netbeans.html", "0.8.3", "3rd", "3rd", null));
   334             js.setArchetype(js.getArchetypes().get(1));
   335             js.applyBindings();
   336 
   337             String v = getSetSelected(0, null);
   338             assertEquals("crud", v, "Second index (e.g. crud) is selected: " + v);
   339 
   340             String sel = getSetSelected(2, Models.toRaw(js.getArchetypes().get(2)));
   341             assertEquals("3rd", sel, "3rd is selected now: " + sel);
   342         }
   343 
   344         if (js.getArchetype() != js.getArchetypes().get(2)) {
   345             throw new InterruptedException();
   346         }
   347 
   348         Utils.exposeHTML(KnockoutTest.class, "");
   349     }
   350 
   351     @KOTest public void nestedObjectEqualsChange() throws Exception {
   352         nestedObjectEqualsChange(true);
   353     }
   354 
   355     @KOTest public void nestedObjectChange() throws Exception {
   356         nestedObjectEqualsChange(false);
   357     }
   358     private  void nestedObjectEqualsChange(boolean preApply) throws Exception {
   359         Utils.exposeHTML(KnockoutTest.class,
   360 "            <div data-bind='with: archetype'>\n" +
   361 "                <input id='input' data-bind='value: groupId'></input>\n" +
   362 "            </div>\n"
   363         );
   364 
   365         js = Models.bind(new KnockoutModel(), newContext());
   366         if (preApply) {
   367             js.applyBindings();
   368         }
   369         js.setArchetype(new ArchetypeData());
   370         js.getArchetype().setGroupId("org.netbeans.html");
   371         js.applyBindings();
   372 
   373         String v = getSetInput("input", null);
   374         assertEquals("org.netbeans.html", v, "groupId has been changed");
   375         Utils.exposeHTML(KnockoutTest.class, "");
   376     }
   377 
   378     @KOTest public void modifyValueAssertAsyncChangeInModel() throws Exception {
   379         if (js == null) {
   380             Utils.exposeHTML(KnockoutTest.class,
   381                 "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
   382                 "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
   383                 "<button id=\"hello\">Say Hello!</button>\n"
   384             );
   385 
   386             js = Models.bind(new KnockoutModel(), newContext());
   387             js.setName("Kukuc");
   388             js.applyBindings();
   389 
   390             String v = getSetInput("input", null);
   391             assertEquals("Kukuc", v, "Value is really kukuc: " + v);
   392 
   393             Timer t = new Timer("Set to Jardo");
   394             t.schedule(new TimerTask() {
   395                 @Override
   396                 public void run() {
   397                     js.setName("Jardo");
   398                 }
   399             }, 1);
   400         }
   401 
   402         String v = getSetInput("input", null);
   403         if (!"Jardo".equals(v)) {
   404             throw new InterruptedException();
   405         }
   406 
   407         Utils.exposeHTML(KnockoutTest.class, "");
   408     }
   409 
   410     @Model(className = "ConstantModel", targetId = "", builder = "assign", properties = {
   411         @Property(name = "doubleValue", mutable = false, type = double.class),
   412         @Property(name = "longValue", mutable = false, type = long.class),
   413         @Property(name = "stringValue", mutable = false, type = String.class),
   414         @Property(name = "boolValue", mutable = false, type = boolean.class),
   415         @Property(name = "intArray", mutable = false, type = int.class, array = true),
   416     })
   417     static class ConstantCntrl {
   418     }
   419 
   420     @KOTest public void nonMutableDouble() throws Exception {
   421         Utils.exposeHTML(KnockoutTest.class,
   422             "Type: <input id='input' data-bind=\"value: typeof doubleValue\"></input>\n"
   423         );
   424 
   425         ConstantModel model = Models.bind(new ConstantModel(), newContext());
   426         model.assignStringValue("Hello").assignDoubleValue(10.0);
   427         model.applyBindings();
   428 
   429         String v = getSetInput("input", null);
   430         assertEquals(v, "number", "Right type found: " + v);
   431 
   432         Utils.exposeHTML(KnockoutTest.class, "");
   433     }
   434 
   435     @KOTest public void nonMutableString() throws Exception {
   436         Utils.exposeHTML(KnockoutTest.class,
   437             "Type: <input id='input' data-bind=\"value: typeof stringValue\"></input>\n"
   438         );
   439 
   440         ConstantModel model = Models.bind(new ConstantModel(), newContext());
   441         model.assignStringValue("Hello").assignDoubleValue(10.0);
   442         model.applyBindings();
   443 
   444         String v = getSetInput("input", null);
   445         assertEquals(v, "string", "Right type found: " + v);
   446 
   447         Utils.exposeHTML(KnockoutTest.class, "");
   448     }
   449 
   450     @KOTest public void nonMutableBoolean() throws Exception {
   451         Utils.exposeHTML(KnockoutTest.class,
   452             "Type: <input id='input' data-bind=\"value: typeof boolValue\"></input>\n"
   453         );
   454 
   455         ConstantModel model = Models.bind(new ConstantModel(), newContext());
   456         model.assignStringValue("Hello").assignBoolValue(true);
   457         model.applyBindings();
   458 
   459         String v = getSetInput("input", null);
   460         assertEquals(v, "boolean", "Right type found: " + v);
   461 
   462         Utils.exposeHTML(KnockoutTest.class, "");
   463     }
   464 
   465     @KOTest public void nonMutableLong() throws Exception {
   466         Utils.exposeHTML(KnockoutTest.class,
   467             "Type: <input id='input' data-bind=\"value: typeof longValue\"></input>\n"
   468         );
   469 
   470         ConstantModel model = Models.bind(new ConstantModel(), newContext());
   471         model.assignStringValue("Hello").assignLongValue(Long.MAX_VALUE);
   472         model.applyBindings();
   473 
   474         String v = getSetInput("input", null);
   475         assertEquals(v, "number", "Right type found: " + v);
   476 
   477         Utils.exposeHTML(KnockoutTest.class, "");
   478     }
   479 
   480     @KOTest public void nonMutableIntArray() throws Exception {
   481         Utils.exposeHTML(KnockoutTest.class,
   482             "Type: <input id='input' data-bind=\"value: typeof intArray\"></input>\n"
   483         );
   484 
   485         ConstantModel model = Models.bind(new ConstantModel(), newContext());
   486         model.assignStringValue("Hello").assignLongValue(Long.MAX_VALUE).assignIntArray(1, 2, 3, 4);
   487         model.applyBindings();
   488 
   489         String v = getSetInput("input", null);
   490         assertEquals(v, "object", "Right type found: " + v);
   491 
   492         Utils.exposeHTML(KnockoutTest.class, "");
   493     }
   494 
   495     private static String getSetInput(String id, String value) throws Exception {
   496         String s = "var value = arguments[0];\n"
   497         + "var n = window.document.getElementById(arguments[1]); \n "
   498         + "if (value != null) n['value'] = value; \n "
   499         + "return n['value'];";
   500         Object ret = Utils.executeScript(
   501             KnockoutTest.class,
   502             s, value, id
   503         );
   504         return ret == null ? null : ret.toString();
   505     }
   506 
   507     private static boolean isChecked(String id) throws Exception {
   508         String s = ""
   509         + "var n = window.document.getElementById(arguments[0]); \n "
   510         + "return n['checked'];";
   511         Object ret = Utils.executeScript(
   512             KnockoutTest.class,
   513             s, id
   514         );
   515         return Boolean.TRUE.equals(ret);
   516     }
   517 
   518     public static void triggerEvent(String id, String ev) throws Exception {
   519         Utils.executeScript(
   520             KnockoutTest.class,
   521             "ko.utils.triggerEvent(window.document.getElementById(arguments[0]), arguments[1]);",
   522             id, ev
   523         );
   524     }
   525 
   526     @KOTest public void displayContentOfArray() throws Exception {
   527         Object exp = Utils.exposeHTML(KnockoutTest.class,
   528             "<ul id='ul' data-bind='foreach: results'>\n"
   529             + "  <li data-bind='text: $data, click: $root.call'/>\n"
   530             + "</ul>\n"
   531         );
   532         try {
   533             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   534             m.getResults().add("Ahoj");
   535             m.applyBindings();
   536 
   537             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   538             assertEquals(cnt, 1, "One child, but was " + cnt);
   539 
   540             m.getResults().add("Hi");
   541 
   542             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   543             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   544 
   545             triggerChildClick("ul", 1);
   546 
   547             assertEquals(1, m.getCallbackCount(), "One callback " + m.getCallbackCount());
   548             assertEquals("Hi", m.getName(), "We got callback from 2nd child " + m.getName());
   549         } finally {
   550             Utils.exposeHTML(KnockoutTest.class, "");
   551         }
   552     }
   553 
   554     @KOTest public void displayContentOfAsyncArray() throws Exception {
   555         if (js == null) {
   556             Utils.exposeHTML(KnockoutTest.class,
   557                 "<ul id='ul' data-bind='foreach: results'>\n"
   558                 + "  <li data-bind='text: $data, click: $root.call'/>\n"
   559                 + "</ul>\n"
   560             );
   561             js = Models.bind(new KnockoutModel(), newContext());
   562             js.getResults().add("Ahoj");
   563             js.applyBindings();
   564 
   565             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   566             assertEquals(cnt, 1, "One child, but was " + cnt);
   567 
   568             Timer t = new Timer("add to array");
   569             t.schedule(new TimerTask() {
   570                 @Override
   571                 public void run() {
   572                     js.getResults().add("Hi");
   573                 }
   574             }, 1);
   575         }
   576 
   577 
   578         int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   579         if (cnt != 2) {
   580             throw new InterruptedException();
   581         }
   582 
   583         try {
   584             triggerChildClick("ul", 1);
   585 
   586             assertEquals(1, js.getCallbackCount(), "One callback " + js.getCallbackCount());
   587             assertEquals("Hi", js.getName(), "We got callback from 2nd child " + js.getName());
   588         } finally {
   589             Utils.exposeHTML(KnockoutTest.class, "");
   590         }
   591     }
   592 
   593     @KOTest public void displayContentOfComputedArray() throws Exception {
   594         Object exp = Utils.exposeHTML(KnockoutTest.class,
   595             "<ul id='ul' data-bind='foreach: bothNames'>\n"
   596             + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   597             + "</ul>\n"
   598         );
   599         try {
   600             Pair m = Models.bind(new Pair("First", "Last", null), newContext());
   601             m.applyBindings();
   602 
   603             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   604             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   605 
   606             triggerChildClick("ul", 1);
   607 
   608             assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   609 
   610             m.setLastName("Verylast");
   611 
   612             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   613             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   614 
   615             triggerChildClick("ul", 1);
   616 
   617             assertEquals("Verylast", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   618 
   619         } finally {
   620             Utils.exposeHTML(KnockoutTest.class, "");
   621         }
   622     }
   623 
   624     @KOTest public void displayContentOfComputedArrayOnASubpair() throws Exception {
   625         Object exp = Utils.exposeHTML(KnockoutTest.class,
   626               "<div data-bind='with: next'>\n"
   627             + "<ul id='ul' data-bind='foreach: bothNames'>\n"
   628             + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   629             + "</ul>"
   630             + "</div>\n"
   631         );
   632         try {
   633             final BrwsrCtx ctx = newContext();
   634             Pair m = Models.bind(new Pair(null, null, new Pair("First", "Last", null)), ctx);
   635             m.applyBindings();
   636 
   637             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   638             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   639 
   640             triggerChildClick("ul", 1);
   641 
   642             assertEquals(PairModel.ctx, ctx, "Context remains the same");
   643 
   644             assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   645         } finally {
   646             Utils.exposeHTML(KnockoutTest.class, "");
   647         }
   648     }
   649 
   650     @KOTest public void displayContentOfComputedArrayOnComputedASubpair() throws Exception {
   651         Object exp = Utils.exposeHTML(KnockoutTest.class,
   652               "<div data-bind='with: nextOne'>\n"
   653             + "<ul id='ul' data-bind='foreach: bothNames'>\n"
   654             + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   655             + "</ul>"
   656             + "</div>\n"
   657         );
   658         try {
   659             Pair m = Models.bind(new Pair(null, null, new Pair("First", "Last", null)), newContext());
   660             m.applyBindings();
   661 
   662             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   663             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   664 
   665             triggerChildClick("ul", 1);
   666 
   667             assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
   668         } finally {
   669             Utils.exposeHTML(KnockoutTest.class, "");
   670         }
   671     }
   672 
   673     @KOTest public void checkBoxToBooleanBinding() throws Exception {
   674         Object exp = Utils.exposeHTML(KnockoutTest.class,
   675             "<input type='checkbox' id='b' data-bind='checked: enabled'></input>\n"
   676         );
   677         try {
   678             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   679             m.applyBindings();
   680 
   681             assertFalse(m.isEnabled(), "Is disabled");
   682 
   683             triggerClick("b");
   684 
   685             assertTrue(m.isEnabled(), "Now the model is enabled");
   686         } finally {
   687             Utils.exposeHTML(KnockoutTest.class, "");
   688         }
   689     }
   690 
   691 
   692 
   693     @KOTest public void displayContentOfDerivedArray() throws Exception {
   694         Object exp = Utils.exposeHTML(KnockoutTest.class,
   695             "<ul id='ul' data-bind='foreach: cmpResults'>\n"
   696             + "  <li><b data-bind='text: $data'></b></li>\n"
   697             + "</ul>\n"
   698         );
   699         try {
   700             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   701             m.getResults().add("Ahoj");
   702             m.applyBindings();
   703 
   704             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   705             assertEquals(cnt, 1, "One child, but was " + cnt);
   706 
   707             m.getResults().add("hello");
   708 
   709             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   710             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   711         } finally {
   712             Utils.exposeHTML(KnockoutTest.class, "");
   713         }
   714     }
   715 
   716     @KOTest public void displayContentOfArrayOfPeople() throws Exception {
   717         Object exp = Utils.exposeHTML(KnockoutTest.class,
   718             "<ul id='ul' data-bind='foreach: people'>\n"
   719             + "  <li data-bind='text: $data.firstName, click: $root.removePerson'></li>\n"
   720             + "</ul>\n"
   721         );
   722         try {
   723             final BrwsrCtx c = newContext();
   724             KnockoutModel m = Models.bind(new KnockoutModel(), c);
   725 
   726             final Person first = Models.bind(new Person(), c);
   727             first.setFirstName("first");
   728             m.getPeople().add(first);
   729 
   730             m.applyBindings();
   731 
   732             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   733             assertEquals(cnt, 1, "One child, but was " + cnt);
   734 
   735             final Person second = Models.bind(new Person(), c);
   736             second.setFirstName("second");
   737             m.getPeople().add(second);
   738 
   739             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   740             assertEquals(cnt, 2, "Two children now, but was " + cnt);
   741 
   742             triggerChildClick("ul", 1);
   743 
   744             assertEquals(1, m.getCallbackCount(), "One callback " + m.getCallbackCount());
   745 
   746             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   747             assertEquals(cnt , 1, "Again one child, but was " + cnt);
   748 
   749             String txt = childText("ul", 0);
   750             assertEquals("first", txt, "Expecting 'first': " + txt);
   751 
   752             first.setFirstName("changed");
   753 
   754             txt = childText("ul", 0);
   755             assertEquals("changed", txt, "Expecting 'changed': " + txt);
   756         } finally {
   757             Utils.exposeHTML(KnockoutTest.class, "");
   758         }
   759     }
   760 
   761     @ComputedProperty
   762     static Person firstPerson(List<Person> people) {
   763         return people.isEmpty() ? null : people.get(0);
   764     }
   765 
   766     @KOTest public void accessFirstPersonWithOnFunction() throws Exception {
   767         Object exp = Utils.exposeHTML(KnockoutTest.class,
   768             "<p id='ul' data-bind='with: firstPerson'>\n"
   769             + "  <span data-bind='text: firstName, click: changeSex'></span>\n"
   770             + "</p>\n"
   771         );
   772         try {
   773             trasfertToFemale();
   774         } finally {
   775             Utils.exposeHTML(KnockoutTest.class, "");
   776         }
   777     }
   778 
   779     @KOTest public void onPersonFunction() throws Exception {
   780         Object exp = Utils.exposeHTML(KnockoutTest.class,
   781             "<ul id='ul' data-bind='foreach: people'>\n"
   782             + "  <li data-bind='text: $data.firstName, click: changeSex'></li>\n"
   783             + "</ul>\n"
   784         );
   785         try {
   786             trasfertToFemale();
   787         } finally {
   788             Utils.exposeHTML(KnockoutTest.class, "");
   789         }
   790     }
   791 
   792     private void trasfertToFemale() throws Exception {
   793         KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   794 
   795         final Person first = Models.bind(new Person(), newContext());
   796         first.setFirstName("first");
   797         first.setSex(Sex.MALE);
   798         m.getPeople().add(first);
   799 
   800 
   801         m.applyBindings();
   802 
   803         int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   804         assertEquals(cnt, 1, "One child, but was " + cnt);
   805 
   806 
   807         triggerChildClick("ul", 0);
   808 
   809         assertEquals(first.getSex(), Sex.FEMALE, "Transverted to female: " + first.getSex());
   810     }
   811 
   812     @KOTest public void stringArrayModificationVisible() throws Exception {
   813         Object exp = Utils.exposeHTML(KnockoutTest.class,
   814                 "<div>\n"
   815                 + "<ul id='ul' data-bind='foreach: results'>\n"
   816                 + "  <li data-bind='text: $data'></li>\n"
   817                 + "</ul>\n"
   818               + "</div>\n"
   819         );
   820         try {
   821             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   822             m.getResults().add("Ahoj");
   823             m.getResults().add("Hello");
   824             m.applyBindings();
   825 
   826             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   827             assertEquals(cnt, 2, "Two children " + cnt);
   828 
   829             Object arr = Utils.addChildren(KnockoutTest.class, "ul", "results", "Hi");
   830             assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   831             final int len = ((Object[])arr).length;
   832 
   833             assertEquals(len, 3, "Three elements in the array " + len);
   834 
   835             int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   836             assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
   837 
   838             assertEquals(m.getResults().size(), 3, "Three java strings: " + m.getResults());
   839         } finally {
   840             Utils.exposeHTML(KnockoutTest.class, "");
   841         }
   842     }
   843 
   844     @KOTest public void intArrayModificationVisible() throws Exception {
   845         Object exp = Utils.exposeHTML(KnockoutTest.class,
   846                 "<div>\n"
   847                 + "<ul id='ul' data-bind='foreach: numbers'>\n"
   848                 + "  <li data-bind='text: $data'></li>\n"
   849                 + "</ul>\n"
   850               + "</div>\n"
   851         );
   852         try {
   853             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   854             m.getNumbers().add(1);
   855             m.getNumbers().add(31);
   856             m.applyBindings();
   857 
   858             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   859             assertEquals(cnt, 2, "Two children " + cnt);
   860 
   861             Object arr = Utils.addChildren(KnockoutTest.class, "ul", "numbers", 42);
   862             assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   863             final int len = ((Object[])arr).length;
   864 
   865             assertEquals(len, 3, "Three elements in the array " + len);
   866 
   867             int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   868             assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
   869 
   870             assertEquals(m.getNumbers().size(), 3, "Three java ints: " + m.getNumbers());
   871             assertEquals(m.getNumbers().get(2), 42, "Meaning of world: " + m.getNumbers());
   872         } finally {
   873             Utils.exposeHTML(KnockoutTest.class, "");
   874         }
   875     }
   876 
   877     @KOTest public void derivedIntArrayModificationVisible() throws Exception {
   878         Object exp = Utils.exposeHTML(KnockoutTest.class,
   879                 "<div>\n"
   880                 + "<ul id='ul' data-bind='foreach: resultLengths'>\n"
   881                 + "  <li data-bind='text: $data'></li>\n"
   882                 + "</ul>\n"
   883               + "</div>\n"
   884         );
   885         try {
   886             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   887             m.getResults().add("Ahoj");
   888             m.getResults().add("Hello");
   889             m.applyBindings();
   890 
   891             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   892             assertEquals(cnt, 2, "Two children " + cnt);
   893 
   894             Object arr = Utils.addChildren(KnockoutTest.class, "ul", "results", "Hi");
   895             assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   896             final int len = ((Object[])arr).length;
   897 
   898             assertEquals(len, 3, "Three elements in the array " + len);
   899 
   900             int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   901             assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
   902 
   903             assertEquals(m.getResultLengths().size(), 3, "Three java ints: " + m.getResultLengths());
   904             assertEquals(m.getResultLengths().get(2), 2, "Size is two: " + m.getResultLengths());
   905         } finally {
   906             Utils.exposeHTML(KnockoutTest.class, "");
   907         }
   908     }
   909 
   910     @KOTest public void archetypeArrayModificationVisible() throws Exception {
   911         Object exp = Utils.exposeHTML(KnockoutTest.class,
   912                 "<div>\n"
   913                 + "<ul id='ul' data-bind='foreach: archetypes'>\n"
   914                 + "  <li data-bind='text: artifactId'></li>\n"
   915                 + "</ul>\n"
   916               + "</div>\n"
   917         );
   918         try {
   919             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   920             m.applyBindings();
   921 
   922             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   923             assertEquals(cnt, 0, "No children " + cnt);
   924 
   925             Object arr = Utils.addChildren(KnockoutTest.class, "ul", "archetypes", new ArchetypeData("aid", "gid", "v", "n", "d", "u"));
   926             assertTrue(arr instanceof Object[], "Got back an array: " + arr);
   927             final int len = ((Object[])arr).length;
   928 
   929             assertEquals(len, 1, "One element in the array " + len);
   930 
   931             int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
   932             assertEquals(newCnt, 1, "One child in the DOM: " + newCnt);
   933 
   934             assertEquals(m.getArchetypes().size(), 1, "One archetype: " + m.getArchetypes());
   935             assertNotNull(m.getArchetypes().get(0), "Not null: " + m.getArchetypes());
   936             assertEquals(m.getArchetypes().get(0).getArtifactId(), "aid", "'aid' == " + m.getArchetypes());
   937         } finally {
   938             Utils.exposeHTML(KnockoutTest.class, "");
   939         }
   940     }
   941 
   942     @Function
   943     static void call(KnockoutModel m, String data) {
   944         m.setName(data);
   945         m.setCallbackCount(m.getCallbackCount() + 1);
   946     }
   947 
   948     @Function
   949     static void removePerson(KnockoutModel model, Person data) {
   950         model.setCallbackCount(model.getCallbackCount() + 1);
   951         model.getPeople().remove(data);
   952     }
   953 
   954 
   955     @ComputedProperty
   956     static String helloMessage(String name) {
   957         return "Hello " + name + "!";
   958     }
   959 
   960     @ComputedProperty
   961     static List<String> cmpResults(List<String> results) {
   962         return results;
   963     }
   964 
   965     private static void triggerClick(String id) throws Exception {
   966         String s = "var id = arguments[0];"
   967             + "var e = window.document.getElementById(id);\n "
   968             + "if (e.checked) throw 'It should not be checked yet: ' + e;\n "
   969             + "var ev = window.document.createEvent('MouseEvents');\n "
   970             + "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n "
   971             + "e.dispatchEvent(ev);\n "
   972             + "if (!e.checked) {\n"
   973             + "  e.checked = true;\n "
   974             + "  e.dispatchEvent(ev);\n "
   975             + "}\n";
   976         Utils.executeScript(
   977             KnockoutTest.class,
   978             s, id);
   979     }
   980     private static void triggerChildClick(String id, int pos) throws Exception {
   981         String s =
   982             "var id = arguments[0]; var pos = arguments[1];\n" +
   983             "var e = window.document.getElementById(id);\n " +
   984             "var ev = window.document.createEvent('MouseEvents');\n " +
   985             "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n " +
   986             "var list = e.childNodes;\n" +
   987             "var cnt = -1;\n" +
   988             "for (var i = 0; i < list.length; i++) {\n" +
   989             "  if (list[i].nodeType == 1) cnt++;\n" +
   990             "  if (cnt == pos) return list[i].dispatchEvent(ev);\n" +
   991             "}\n" +
   992             "return null;\n";
   993         Utils.executeScript(
   994             KnockoutTest.class,
   995             s, id, pos);
   996     }
   997 
   998     private static String childText(String id, int pos) throws Exception {
   999         String s =
  1000             "var id = arguments[0]; var pos = arguments[1];" +
  1001             "var e = window.document.getElementById(id);\n" +
  1002             "var list = e.childNodes;\n" +
  1003             "var cnt = -1;\n" +
  1004             "for (var i = 0; i < list.length; i++) {\n" +
  1005             "  if (list[i].nodeType == 1) cnt++;\n" +
  1006             "  if (cnt == pos) return list[i].innerHTML;\n" +
  1007             "}\n" +
  1008             "return null;\n";
  1009         return (String)Utils.executeScript(
  1010             KnockoutTest.class,
  1011             s, id, pos);
  1012     }
  1013 
  1014     private static BrwsrCtx newContext() {
  1015         return Utils.newContext(KnockoutTest.class);
  1016     }
  1017 }