json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java
author Jaroslav Tulach <jtulach@netbeans.org>
Sat, 02 Aug 2014 12:59:31 +0200
changeset 790 30f20d9c0986
parent 758 0fff91d8e613
child 809 57c6bd4c5261
permissions -rw-r--r--
Fixing Javadoc to succeed on JDK8
     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.List;
    46 import java.util.Timer;
    47 import java.util.TimerTask;
    48 import net.java.html.BrwsrCtx;
    49 import net.java.html.json.ComputedProperty;
    50 import net.java.html.json.Function;
    51 import net.java.html.json.Model;
    52 import net.java.html.json.Models;
    53 import net.java.html.json.Property;
    54 import org.apidesign.html.json.tck.KOTest;
    55 
    56 /**
    57  *
    58  * @author Jaroslav Tulach
    59  */
    60 @Model(className="KnockoutModel", properties={
    61     @Property(name="name", type=String.class),
    62     @Property(name="results", type=String.class, array = true),
    63     @Property(name="callbackCount", type=int.class),
    64     @Property(name="people", type=PersonImpl.class, array = true),
    65     @Property(name="enabled", type=boolean.class),
    66     @Property(name="latitude", type=double.class),
    67     @Property(name="choice", type=KnockoutTest.Choice.class),
    68 }) 
    69 public final class KnockoutTest {
    70     private KnockoutModel js;
    71     
    72     enum Choice {
    73         A, B;
    74     }
    75     
    76     @KOTest public void modifyValueAssertChangeInModelOnEnum() throws Throwable {
    77         Object exp = Utils.exposeHTML(KnockoutTest.class, 
    78             "Latitude: <input id='input' data-bind=\"value: choice\"></input>\n"
    79         );
    80         try {
    81 
    82             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
    83             m.setChoice(Choice.A);
    84             m.applyBindings();
    85 
    86             String v = getSetInput(null);
    87             assert "A".equals(v) : "Value is really A: " + v;
    88 
    89             getSetInput("B");
    90             triggerEvent("input", "change");
    91 
    92             assert Choice.B == m.getChoice(): "Enum property updated: " + m.getChoice();
    93         } catch (Throwable t) {
    94             throw t;
    95         } finally {
    96             Utils.exposeHTML(KnockoutTest.class, "");
    97         }
    98     }
    99     
   100     @KOTest public void modifyValueAssertChangeInModelOnDouble() throws Throwable {
   101         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   102             "Latitude: <input id='input' data-bind=\"value: latitude\"></input>\n"
   103         );
   104         try {
   105 
   106             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   107             m.setLatitude(50.5);
   108             m.applyBindings();
   109 
   110             String v = getSetInput(null);
   111             assert "50.5".equals(v) : "Value is really 50.5: " + v;
   112 
   113             getSetInput("49.5");
   114             triggerEvent("input", "change");
   115 
   116             assert 49.5 == m.getLatitude() : "Double property updated: " + m.getLatitude();
   117         } catch (Throwable t) {
   118             throw t;
   119         } finally {
   120             Utils.exposeHTML(KnockoutTest.class, "");
   121         }
   122     }
   123     
   124     @KOTest public void modifyValueAssertChangeInModelOnBoolean() throws Throwable {
   125         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   126             "Latitude: <input id='input' data-bind=\"value: enabled\"></input>\n"
   127         );
   128         try {
   129 
   130             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   131             m.setEnabled(true);
   132             m.applyBindings();
   133 
   134             String v = getSetInput(null);
   135             assert "true".equals(v) : "Value is really true: " + v;
   136 
   137             getSetInput("false");
   138             triggerEvent("input", "change");
   139 
   140             assert false == m.isEnabled(): "Boolean property updated: " + m.isEnabled();
   141         } catch (Throwable t) {
   142             throw t;
   143         } finally {
   144             Utils.exposeHTML(KnockoutTest.class, "");
   145         }
   146     }
   147     
   148     @KOTest public void modifyValueAssertChangeInModel() throws Exception {
   149         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   150             "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
   151             "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
   152             "<button id=\"hello\">Say Hello!</button>\n"
   153         );
   154         try {
   155 
   156             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   157             m.setName("Kukuc");
   158             m.applyBindings();
   159 
   160             String v = getSetInput(null);
   161             assert "Kukuc".equals(v) : "Value is really kukuc: " + v;
   162 
   163             getSetInput("Jardo");
   164             triggerEvent("input", "change");
   165 
   166             assert "Jardo".equals(m.getName()) : "Name property updated: " + m.getName();
   167         } finally {
   168             Utils.exposeHTML(KnockoutTest.class, "");
   169         }
   170     }
   171     
   172     @KOTest public void modifyValueAssertAsyncChangeInModel() throws Exception {
   173         if (js == null) {
   174             Utils.exposeHTML(KnockoutTest.class, 
   175                 "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
   176                 "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
   177                 "<button id=\"hello\">Say Hello!</button>\n"
   178             );
   179             
   180             js = Models.bind(new KnockoutModel(), newContext());
   181             js.setName("Kukuc");
   182             js.applyBindings();
   183             
   184             String v = getSetInput(null);
   185             assert "Kukuc".equals(v) : "Value is really kukuc: " + v;
   186             
   187             Timer t = new Timer("Set to Jardo");
   188             t.schedule(new TimerTask() {
   189                 @Override
   190                 public void run() {
   191                     js.setName("Jardo");
   192                 }
   193             }, 1);
   194         }
   195         
   196         String v = getSetInput(null);
   197         if (!"Jardo".equals(v)) {
   198             throw new InterruptedException();
   199         }
   200         
   201         Utils.exposeHTML(KnockoutTest.class, "");
   202     }
   203     
   204     private static String getSetInput(String value) throws Exception {
   205         String s = "var value = arguments[0];\n"
   206         + "var n = window.document.getElementById('input'); \n "
   207         + "if (value != null) n['value'] = value; \n "
   208         + "return n['value'];";
   209         Object ret = Utils.executeScript(
   210             KnockoutTest.class,
   211             s, value
   212         );
   213         return ret == null ? null : ret.toString();
   214     }
   215     
   216     public static void triggerEvent(String id, String ev) throws Exception {
   217         Utils.executeScript(
   218             KnockoutTest.class,
   219             "ko.utils.triggerEvent(window.document.getElementById(arguments[0]), arguments[1]);",
   220             id, ev
   221         );
   222     }
   223     
   224     @KOTest public void displayContentOfArray() throws Exception {
   225         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   226             "<ul id='ul' data-bind='foreach: results'>\n"
   227             + "  <li data-bind='text: $data, click: $root.call'/>\n"
   228             + "</ul>\n"
   229         );
   230         try {
   231             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   232             m.getResults().add("Ahoj");
   233             m.applyBindings();
   234 
   235             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   236             assert cnt == 1 : "One child, but was " + cnt;
   237 
   238             m.getResults().add("Hi");
   239 
   240             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   241             assert cnt == 2 : "Two children now, but was " + cnt;
   242 
   243             triggerChildClick("ul", 1);
   244 
   245             assert 1 == m.getCallbackCount() : "One callback " + m.getCallbackCount();
   246             assert "Hi".equals(m.getName()) : "We got callback from 2nd child " + m.getName();
   247         } finally {
   248             Utils.exposeHTML(KnockoutTest.class, "");
   249         }
   250     }
   251     
   252     @KOTest public void displayContentOfAsyncArray() throws Exception {
   253         if (js == null) {
   254             Utils.exposeHTML(KnockoutTest.class, 
   255                 "<ul id='ul' data-bind='foreach: results'>\n"
   256                 + "  <li data-bind='text: $data, click: $root.call'/>\n"
   257                 + "</ul>\n"
   258             );
   259             js = Models.bind(new KnockoutModel(), newContext());
   260             js.getResults().add("Ahoj");
   261             js.applyBindings();
   262 
   263             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   264             assert cnt == 1 : "One child, but was " + cnt;
   265             
   266             Timer t = new Timer("add to array");
   267             t.schedule(new TimerTask() {
   268                 @Override
   269                 public void run() {
   270                     js.getResults().add("Hi");
   271                 }
   272             }, 1);
   273         }
   274 
   275 
   276         int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   277         if (cnt != 2) {
   278             throw new InterruptedException();
   279         }
   280 
   281         try {
   282             triggerChildClick("ul", 1);
   283 
   284             assert 1 == js.getCallbackCount() : "One callback " + js.getCallbackCount();
   285             assert "Hi".equals(js.getName()) : "We got callback from 2nd child " + js.getName();
   286         } finally {
   287             Utils.exposeHTML(KnockoutTest.class, "");
   288         }
   289     }
   290     
   291     @KOTest public void displayContentOfComputedArray() throws Exception {
   292         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   293             "<ul id='ul' data-bind='foreach: bothNames'>\n"
   294             + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   295             + "</ul>\n"
   296         );
   297         try {
   298             Pair m = Models.bind(new Pair("First", "Last", null), newContext());
   299             m.applyBindings();
   300 
   301             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   302             assert cnt == 2 : "Two children now, but was " + cnt;
   303 
   304             triggerChildClick("ul", 1);
   305 
   306             assert "Last".equals(m.getFirstName()) : "We got callback from 2nd child " + m.getFirstName();
   307             
   308             m.setLastName("Verylast");
   309 
   310             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   311             assert cnt == 2 : "Two children now, but was " + cnt;
   312             
   313             triggerChildClick("ul", 1);
   314 
   315             assert "Verylast".equals(m.getFirstName()) : "We got callback from 2nd child " + m.getFirstName();
   316             
   317         } finally {
   318             Utils.exposeHTML(KnockoutTest.class, "");
   319         }
   320     }
   321     
   322     @KOTest public void displayContentOfComputedArrayOnASubpair() throws Exception {
   323         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   324               "<div data-bind='with: next'>\n"
   325             + "<ul id='ul' data-bind='foreach: bothNames'>\n"
   326             + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   327             + "</ul>"
   328             + "</div>\n"
   329         );
   330         try {
   331             final BrwsrCtx ctx = newContext();
   332             Pair m = Models.bind(new Pair(null, null, new Pair("First", "Last", null)), ctx);
   333             m.applyBindings();
   334 
   335             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   336             assert cnt == 2 : "Two children now, but was " + cnt;
   337 
   338             triggerChildClick("ul", 1);
   339             
   340             assert PairModel.ctx == ctx : "Context remains the same";
   341 
   342             assert "Last".equals(m.getFirstName()) : "We got callback from 2nd child " + m.getFirstName();
   343         } finally {
   344             Utils.exposeHTML(KnockoutTest.class, "");
   345         }
   346     }
   347     
   348     @KOTest public void displayContentOfComputedArrayOnComputedASubpair() throws Exception {
   349         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   350               "<div data-bind='with: nextOne'>\n"
   351             + "<ul id='ul' data-bind='foreach: bothNames'>\n"
   352             + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
   353             + "</ul>"
   354             + "</div>\n"
   355         );
   356         try {
   357             Pair m = Models.bind(new Pair(null, null, new Pair("First", "Last", null)), newContext());
   358             m.applyBindings();
   359 
   360             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   361             assert cnt == 2 : "Two children now, but was " + cnt;
   362 
   363             triggerChildClick("ul", 1);
   364 
   365             assert "Last".equals(m.getFirstName()) : "We got callback from 2nd child " + m.getFirstName();
   366         } finally {
   367             Utils.exposeHTML(KnockoutTest.class, "");
   368         }
   369     }
   370 
   371     @KOTest public void checkBoxToBooleanBinding() throws Exception {
   372         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   373             "<input type='checkbox' id='b' data-bind='checked: enabled'></input>\n"
   374         );
   375         try {
   376             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   377             m.applyBindings();
   378 
   379             assert !m.isEnabled() : "Is disabled";
   380 
   381             triggerClick("b");
   382 
   383             assert m.isEnabled() : "Now the model is enabled";
   384         } finally {
   385             Utils.exposeHTML(KnockoutTest.class, "");
   386         }
   387     }
   388     
   389     
   390     
   391     @KOTest public void displayContentOfDerivedArray() throws Exception {
   392         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   393             "<ul id='ul' data-bind='foreach: cmpResults'>\n"
   394             + "  <li><b data-bind='text: $data'></b></li>\n"
   395             + "</ul>\n"
   396         );
   397         try {
   398             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   399             m.getResults().add("Ahoj");
   400             m.applyBindings();
   401 
   402             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   403             assert cnt == 1 : "One child, but was " + cnt;
   404 
   405             m.getResults().add("hello");
   406 
   407             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   408             assert cnt == 2 : "Two children now, but was " + cnt;
   409         } finally {
   410             Utils.exposeHTML(KnockoutTest.class, "");
   411         }
   412     }
   413     
   414     @KOTest public void displayContentOfArrayOfPeople() throws Exception {
   415         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   416             "<ul id='ul' data-bind='foreach: people'>\n"
   417             + "  <li data-bind='text: $data.firstName, click: $root.removePerson'></li>\n"
   418             + "</ul>\n"
   419         );
   420         try {
   421             final BrwsrCtx c = newContext();
   422             KnockoutModel m = Models.bind(new KnockoutModel(), c);
   423 
   424             final Person first = Models.bind(new Person(), c);
   425             first.setFirstName("first");
   426             m.getPeople().add(first);
   427 
   428             m.applyBindings();
   429 
   430             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   431             assert cnt == 1 : "One child, but was " + cnt;
   432 
   433             final Person second = Models.bind(new Person(), c);
   434             second.setFirstName("second");
   435             m.getPeople().add(second);
   436 
   437             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   438             assert cnt == 2 : "Two children now, but was " + cnt;
   439 
   440             triggerChildClick("ul", 1);
   441 
   442             assert 1 == m.getCallbackCount() : "One callback " + m.getCallbackCount();
   443 
   444             cnt = Utils.countChildren(KnockoutTest.class, "ul");
   445             assert cnt == 1 : "Again one child, but was " + cnt;
   446 
   447             String txt = childText("ul", 0);
   448             assert "first".equals(txt) : "Expecting 'first': " + txt;
   449 
   450             first.setFirstName("changed");
   451 
   452             txt = childText("ul", 0);
   453             assert "changed".equals(txt) : "Expecting 'changed': " + txt;
   454         } finally {
   455             Utils.exposeHTML(KnockoutTest.class, "");
   456         }
   457     }
   458     
   459     @ComputedProperty
   460     static Person firstPerson(List<Person> people) {
   461         return people.isEmpty() ? null : people.get(0);
   462     }
   463     
   464     @KOTest public void accessFirstPersonWithOnFunction() throws Exception {
   465         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   466             "<p id='ul' data-bind='with: firstPerson'>\n"
   467             + "  <span data-bind='text: firstName, click: changeSex'></span>\n"
   468             + "</p>\n"
   469         );
   470         try {
   471             trasfertToFemale();
   472         } finally {
   473             Utils.exposeHTML(KnockoutTest.class, "");
   474         }
   475     }
   476     
   477     @KOTest public void onPersonFunction() throws Exception {
   478         Object exp = Utils.exposeHTML(KnockoutTest.class, 
   479             "<ul id='ul' data-bind='foreach: people'>\n"
   480             + "  <li data-bind='text: $data.firstName, click: changeSex'></li>\n"
   481             + "</ul>\n"
   482         );
   483         try {
   484             trasfertToFemale();
   485         } finally {
   486             Utils.exposeHTML(KnockoutTest.class, "");
   487         }
   488     }
   489     
   490     private void trasfertToFemale() throws Exception {
   491         KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
   492 
   493         final Person first = Models.bind(new Person(), newContext());
   494         first.setFirstName("first");
   495         first.setSex(Sex.MALE);
   496         m.getPeople().add(first);
   497 
   498 
   499         m.applyBindings();
   500 
   501         int cnt = Utils.countChildren(KnockoutTest.class, "ul");
   502         assert cnt == 1 : "One child, but was " + cnt;
   503 
   504 
   505         triggerChildClick("ul", 0);
   506 
   507         assert first.getSex() == Sex.FEMALE : "Transverted to female: " + first.getSex();
   508     }
   509     
   510     @Function
   511     static void call(KnockoutModel m, String data) {
   512         m.setName(data);
   513         m.setCallbackCount(m.getCallbackCount() + 1);
   514     }
   515 
   516     @Function
   517     static void removePerson(KnockoutModel model, Person data) {
   518         model.setCallbackCount(model.getCallbackCount() + 1);
   519         model.getPeople().remove(data);
   520     }
   521     
   522     
   523     @ComputedProperty
   524     static String helloMessage(String name) {
   525         return "Hello " + name + "!";
   526     }
   527     
   528     @ComputedProperty
   529     static List<String> cmpResults(List<String> results) {
   530         return results;
   531     }
   532     
   533     private static void triggerClick(String id) throws Exception {
   534         String s = "var id = arguments[0];"
   535             + "var e = window.document.getElementById(id);\n "
   536             + "if (e.checked) throw 'It should not be checked yet: ' + e;\n "
   537             + "var ev = window.document.createEvent('MouseEvents');\n "
   538             + "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n "
   539             + "e.dispatchEvent(ev);\n "
   540             + "if (!e.checked) {\n"
   541             + "  e.checked = true;\n "
   542             + "  e.dispatchEvent(ev);\n "
   543             + "}\n";
   544         Utils.executeScript(
   545             KnockoutTest.class,
   546             s, id);
   547     }
   548     private static void triggerChildClick(String id, int pos) throws Exception {
   549         String s = 
   550             "var id = arguments[0]; var pos = arguments[1];\n" +
   551             "var e = window.document.getElementById(id);\n " +
   552             "var ev = window.document.createEvent('MouseEvents');\n " +
   553             "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n " +
   554             "var list = e.childNodes;\n" +
   555             "var cnt = -1;\n" + 
   556             "for (var i = 0; i < list.length; i++) {\n" + 
   557             "  if (list[i].nodeType == 1) cnt++;\n" + 
   558             "  if (cnt == pos) return list[i].dispatchEvent(ev);\n" + 
   559             "}\n" + 
   560             "return null;\n";
   561         Utils.executeScript(
   562             KnockoutTest.class,
   563             s, id, pos);
   564     }
   565 
   566     private static String childText(String id, int pos) throws Exception {
   567         String s = 
   568             "var id = arguments[0]; var pos = arguments[1];" +
   569             "var e = window.document.getElementById(id);\n" +
   570             "var list = e.childNodes;\n" +
   571             "var cnt = -1;\n" + 
   572             "for (var i = 0; i < list.length; i++) {\n" + 
   573             "  if (list[i].nodeType == 1) cnt++;\n" + 
   574             "  if (cnt == pos) return list[i].innerHTML;\n" + 
   575             "}\n" + 
   576             "return null;\n";
   577         return (String)Utils.executeScript(
   578             KnockoutTest.class,
   579             s, id, pos);
   580     }
   581 
   582     private static BrwsrCtx newContext() {
   583         return Utils.newContext(KnockoutTest.class);
   584     }
   585 }