You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by jt...@apache.org on 2017/09/03 12:48:46 UTC

[13/24] incubator-netbeans-html4j git commit: [INFRA-15006] Initial donation of HTML/Java NetBeans API. Equivalent to the content of html4j-donation-review.zip donated as part of ApacheNetBeansDonation1.zip with SHA256 being 7f2ca0f61953a190613c9a0fbcc1b

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java b/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java
new file mode 100644
index 0000000..b5e34a6
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/KnockoutTest.java
@@ -0,0 +1,1001 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package net.java.html.json.tests;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Timer;
+import java.util.TimerTask;
+import net.java.html.BrwsrCtx;
+import net.java.html.json.ComputedProperty;
+import net.java.html.json.Function;
+import net.java.html.json.Model;
+import net.java.html.json.Models;
+import net.java.html.json.Property;
+import org.netbeans.html.json.tck.KOTest;
+import static net.java.html.json.tests.Utils.assertEquals;
+import static net.java.html.json.tests.Utils.assertNotNull;
+import static net.java.html.json.tests.Utils.assertTrue;
+import static net.java.html.json.tests.Utils.assertFalse;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+@Model(className="KnockoutModel", targetId = "", properties={
+    @Property(name="name", type=String.class),
+    @Property(name="results", type=String.class, array = true),
+    @Property(name="numbers", type=int.class, array = true),
+    @Property(name="callbackCount", type=int.class),
+    @Property(name="people", type=PersonImpl.class, array = true),
+    @Property(name="enabled", type=boolean.class),
+    @Property(name="latitude", type=double.class),
+    @Property(name="choice", type=KnockoutTest.Choice.class),
+    @Property(name="archetype", type=ArchetypeData.class),
+    @Property(name="archetypes", type=ArchetypeData.class, array = true),
+})
+public final class KnockoutTest {
+    private KnockoutModel js;
+
+    enum Choice {
+        A, B;
+    }
+
+    @ComputedProperty static List<Integer> resultLengths(List<String> results) {
+        Integer[] arr = new Integer[results.size()];
+        for (int i = 0; i < arr.length; i++) {
+            arr[i] = results.get(i).length();
+        }
+        return Arrays.asList(arr);
+    }
+
+    @KOTest public void modifyValueAssertChangeInModelOnEnum() throws Throwable {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "Latitude: <input id='input' data-bind=\"value: choice\"></input>\n"
+        );
+        try {
+
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.setChoice(Choice.A);
+            m.applyBindings();
+
+            String v = getSetInput("input", null);
+            assertEquals("A", v, "Value is really A: " + v);
+
+            getSetInput("input", "B");
+            triggerEvent("input", "change");
+
+            assertEquals(Choice.B, m.getChoice(), "Enum property updated: " + m.getChoice());
+        } catch (Throwable t) {
+            throw t;
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+
+    @KOTest public void modifyRadioValueOnEnum() throws Throwable {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "<input id='i1' type=\"radio\" name=\"choice\" value=\"A\" data-bind=\"checked: choice\"></input>Right\n" +
+            "<input id='input' type=\"radio\" name=\"choice\" value=\"B\" data-bind=\"checked: choice\"></input>Never\n" +
+            "\n"
+        );
+        try {
+
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.setChoice(Choice.B);
+            m.applyBindings();
+
+            assertFalse(isChecked("i1"), "B should be checked now");
+            assertTrue(isChecked("input"), "B should be checked now");
+
+            triggerEvent("i1", "click");
+            assertEquals(Choice.A, m.getChoice(), "Switched to A");
+            assertTrue(isChecked("i1"), "A should be checked now");
+            assertFalse(isChecked("input"), "A should be checked now");
+
+            triggerEvent("input", "click");
+
+            assertEquals(Choice.B, m.getChoice(), "Enum property updated: " + m.getChoice());
+        } catch (Throwable t) {
+            throw t;
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void modifyValueAssertChangeInModelOnDouble() throws Throwable {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "Latitude: <input id='input' data-bind=\"value: latitude\"></input>\n"
+        );
+        try {
+
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.setLatitude(50.5);
+            m.applyBindings();
+
+            String v = getSetInput("input", null);
+            assertEquals("50.5", v, "Value is really 50.5: " + v);
+
+            getSetInput("input", "49.5");
+            triggerEvent("input", "change");
+
+            assertEquals(49.5, m.getLatitude(), "Double property updated: " + m.getLatitude());
+        } catch (Throwable t) {
+            throw t;
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void rawObject() throws Exception {
+        if (js == null) {
+            final BrwsrCtx ctx = newContext();
+            Person p1 = Models.bind(new Person(), ctx);
+            p1.setFirstName("Jarda");
+            p1.setLastName("Tulach");
+            Object raw = Models.toRaw(p1);
+            Person p2 = Models.fromRaw(ctx, Person.class, raw);
+
+            assertEquals(p2.getFirstName(), "Jarda", "First name");
+            assertEquals(p2.getLastName(), "Tulach", "Last name");
+
+            p2.setFirstName("Jirka");
+            assertEquals(p2.getFirstName(), "Jirka", "First name updated");
+
+            js = new KnockoutModel();
+            js.getPeople().add(p1);
+            js.getPeople().add(p2);
+        }
+
+        Person p1 = js.getPeople().get(0);
+        Person p2 = js.getPeople().get(1);
+
+        if (js.getPeople().size() == 2) {
+            if (!"Jirka".equals(p1.getFirstName())) {
+                throw new InterruptedException();
+            }
+
+            assertEquals(p1.getFirstName(), "Jirka", "First name updated in original object");
+
+            p1.setFirstName("Ondra");
+            assertEquals(p1.getFirstName(), "Ondra", "1st name updated in original object");
+
+            js.getPeople().add(p1);
+        }
+
+        if (!"Ondra".equals(p2.getFirstName())) {
+            throw new InterruptedException();
+        }
+        assertEquals(p2.getFirstName(), "Ondra", "1st name updated in copied object");
+    }
+
+    @KOTest public void modifyComputedProperty() throws Throwable {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "Full name: <div data-bind='with:firstPerson'>\n"
+                + "<input id='input' data-bind=\"value: fullName\"></input>\n"
+                + "</div>\n"
+        );
+        try {
+            KnockoutModel m = new KnockoutModel();
+            m.getPeople().add(new Person());
+
+            m = Models.bind(m, newContext());
+            m.getFirstPerson().setFirstName("Jarda");
+            m.getFirstPerson().setLastName("Tulach");
+            m.applyBindings();
+
+            String v = getSetInput("input", null);
+            assertEquals("Jarda Tulach", v, "Value: " + v);
+
+            getSetInput("input", "Mickey Mouse");
+            triggerEvent("input", "change");
+
+            assertEquals("Mickey", m.getFirstPerson().getFirstName(), "First name updated");
+            assertEquals("Mouse", m.getFirstPerson().getLastName(), "Last name updated");
+        } catch (Throwable t) {
+            throw t;
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void modifyValueAssertChangeInModelOnBoolean() throws Throwable {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "Latitude: <input id='input' data-bind=\"value: enabled\"></input>\n"
+        );
+        try {
+
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.setEnabled(true);
+            m.applyBindings();
+
+            String v = getSetInput("input", null);
+            assertEquals("true", v, "Value is really true: " + v);
+
+            getSetInput("input", "false");
+            triggerEvent("input", "change");
+
+            assertFalse(m.isEnabled(), "Boolean property updated: " + m.isEnabled());
+        } catch (Throwable t) {
+            throw t;
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void modifyValueAssertChangeInModel() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
+            "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
+            "<button id=\"hello\">Say Hello!</button>\n"
+        );
+        try {
+
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.setName("Kukuc");
+            m.applyBindings();
+
+            String v = getSetInput("input", null);
+            assertEquals("Kukuc", v, "Value is really kukuc: " + v);
+
+            getSetInput("input", "Jardo");
+            triggerEvent("input", "change");
+
+            assertEquals("Jardo", m.getName(), "Name property updated: " + m.getName());
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    private static String getSetSelected(int index, Object value) throws Exception {
+        String s = "var index = arguments[0];\n"
+        + "var value = arguments[1];\n"
+        + "var n = window.document.getElementById('input'); \n "
+        + "if (value != null) {\n"
+        + "  n.options[index].value = 'me'; \n"
+        + "  n.value = 'me'; \n"
+        + "  ko.dataFor(n.options[index]).archetype(value); // haven't found better way to trigger ko change yet \n"
+        + "} \n "
+        + "var op = n.options[n.selectedIndex]; \n"
+        + "return op ? op.text : n.selectedIndex;\n";
+        Object ret = Utils.executeScript(
+            KnockoutTest.class,
+            s, index, value
+        );
+        return ret == null ? null : ret.toString();
+    }
+
+    @Model(className = "ArchetypeData", properties = {
+        @Property(name = "artifactId", type = String.class),
+        @Property(name = "groupId", type = String.class),
+        @Property(name = "version", type = String.class),
+        @Property(name = "name", type = String.class),
+        @Property(name = "description", type = String.class),
+        @Property(name = "url", type = String.class),
+    })
+    static class ArchModel {
+    }
+
+    @KOTest public void selectWorksOnModels() throws Exception {
+        if (js == null) {
+            Utils.exposeHTML(KnockoutTest.class,
+                "<select id='input' data-bind=\"options: archetypes,\n" +
+"                       optionsText: 'name',\n" +
+"                       value: archetype\">\n" +
+"                  </select>\n" +
+""
+            );
+
+            js = Models.bind(new KnockoutModel(), newContext());
+            js.getArchetypes().add(new ArchetypeData("ko4j", "org.netbeans.html", "0.8.3", "ko4j", "ko4j", null));
+            js.getArchetypes().add(new ArchetypeData("crud", "org.netbeans.html", "0.8.3", "crud", "crud", null));
+            js.getArchetypes().add(new ArchetypeData("3rd", "org.netbeans.html", "0.8.3", "3rd", "3rd", null));
+            js.setArchetype(js.getArchetypes().get(1));
+            js.applyBindings();
+
+            String v = getSetSelected(0, null);
+            assertEquals("crud", v, "Second index (e.g. crud) is selected: " + v);
+
+            String sel = getSetSelected(2, Models.toRaw(js.getArchetypes().get(2)));
+            assertEquals("3rd", sel, "3rd is selected now: " + sel);
+        }
+
+        if (js.getArchetype() != js.getArchetypes().get(2)) {
+            throw new InterruptedException();
+        }
+
+        Utils.exposeHTML(KnockoutTest.class, "");
+    }
+
+    @KOTest public void nestedObjectEqualsChange() throws Exception {
+        nestedObjectEqualsChange(true);
+    }
+
+    @KOTest public void nestedObjectChange() throws Exception {
+        nestedObjectEqualsChange(false);
+    }
+    private  void nestedObjectEqualsChange(boolean preApply) throws Exception {
+        Utils.exposeHTML(KnockoutTest.class,
+"            <div data-bind='with: archetype'>\n" +
+"                <input id='input' data-bind='value: groupId'></input>\n" +
+"            </div>\n"
+        );
+
+        js = Models.bind(new KnockoutModel(), newContext());
+        if (preApply) {
+            js.applyBindings();
+        }
+        js.setArchetype(new ArchetypeData());
+        js.getArchetype().setGroupId("org.netbeans.html");
+        js.applyBindings();
+
+        String v = getSetInput("input", null);
+        assertEquals("org.netbeans.html", v, "groupId has been changed");
+        Utils.exposeHTML(KnockoutTest.class, "");
+    }
+
+    @KOTest public void modifyValueAssertAsyncChangeInModel() throws Exception {
+        if (js == null) {
+            Utils.exposeHTML(KnockoutTest.class,
+                "<h1 data-bind=\"text: helloMessage\">Loading Bck2Brwsr's Hello World...</h1>\n" +
+                "Your name: <input id='input' data-bind=\"value: name\"></input>\n" +
+                "<button id=\"hello\">Say Hello!</button>\n"
+            );
+
+            js = Models.bind(new KnockoutModel(), newContext());
+            js.setName("Kukuc");
+            js.applyBindings();
+
+            String v = getSetInput("input", null);
+            assertEquals("Kukuc", v, "Value is really kukuc: " + v);
+
+            Timer t = new Timer("Set to Jardo");
+            t.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    js.setName("Jardo");
+                }
+            }, 1);
+        }
+
+        String v = getSetInput("input", null);
+        if (!"Jardo".equals(v)) {
+            throw new InterruptedException();
+        }
+
+        Utils.exposeHTML(KnockoutTest.class, "");
+    }
+
+    @Model(className = "ConstantModel", targetId = "", builder = "assign", properties = {
+        @Property(name = "doubleValue", mutable = false, type = double.class),
+        @Property(name = "stringValue", mutable = false, type = String.class),
+        @Property(name = "boolValue", mutable = false, type = boolean.class),
+        @Property(name = "intArray", mutable = false, type = int.class, array = true),
+    })
+    static class ConstantCntrl {
+    }
+
+    @KOTest public void nonMutableDouble() throws Exception {
+        Utils.exposeHTML(KnockoutTest.class,
+            "Type: <input id='input' data-bind=\"value: typeof doubleValue\"></input>\n"
+        );
+
+        ConstantModel model = Models.bind(new ConstantModel(), newContext());
+        model.assignStringValue("Hello").assignDoubleValue(10.0);
+        model.applyBindings();
+
+        String v = getSetInput("input", null);
+        assertEquals(v, "number", "Right type found: " + v);
+
+        Utils.exposeHTML(KnockoutTest.class, "");
+    }
+
+    @KOTest public void nonMutableString() throws Exception {
+        Utils.exposeHTML(KnockoutTest.class,
+            "Type: <input id='input' data-bind=\"value: typeof stringValue\"></input>\n"
+        );
+
+        ConstantModel model = Models.bind(new ConstantModel(), newContext());
+        model.assignStringValue("Hello").assignDoubleValue(10.0);
+        model.applyBindings();
+
+        String v = getSetInput("input", null);
+        assertEquals(v, "string", "Right type found: " + v);
+
+        Utils.exposeHTML(KnockoutTest.class, "");
+    }
+
+    @KOTest public void nonMutableBoolean() throws Exception {
+        Utils.exposeHTML(KnockoutTest.class,
+            "Type: <input id='input' data-bind=\"value: typeof boolValue\"></input>\n"
+        );
+
+        ConstantModel model = Models.bind(new ConstantModel(), newContext());
+        model.assignStringValue("Hello").assignBoolValue(true);
+        model.applyBindings();
+
+        String v = getSetInput("input", null);
+        assertEquals(v, "boolean", "Right type found: " + v);
+
+        Utils.exposeHTML(KnockoutTest.class, "");
+    }
+
+    @KOTest public void nonMutableIntArray() throws Exception {
+        Utils.exposeHTML(KnockoutTest.class,
+            "Type: <input id='input' data-bind=\"value: typeof intArray\"></input>\n"
+        );
+
+        ConstantModel model = Models.bind(new ConstantModel(), newContext());
+        model.assignStringValue("Hello").assignDoubleValue(Long.MAX_VALUE).assignIntArray(1, 2, 3, 4);
+        model.applyBindings();
+
+        String v = getSetInput("input", null);
+        assertEquals(v, "object", "Right type found: " + v);
+
+        Utils.exposeHTML(KnockoutTest.class, "");
+    }
+
+    private static String getSetInput(String id, String value) throws Exception {
+        String s = "var value = arguments[0];\n"
+        + "var n = window.document.getElementById(arguments[1]); \n "
+        + "if (value != null) n['value'] = value; \n "
+        + "return n['value'];";
+        Object ret = Utils.executeScript(
+            KnockoutTest.class,
+            s, value, id
+        );
+        return ret == null ? null : ret.toString();
+    }
+
+    private static boolean isChecked(String id) throws Exception {
+        String s = ""
+        + "var n = window.document.getElementById(arguments[0]); \n "
+        + "return n['checked'];";
+        Object ret = Utils.executeScript(
+            KnockoutTest.class,
+            s, id
+        );
+        return Boolean.TRUE.equals(ret);
+    }
+
+    public static void triggerEvent(String id, String ev) throws Exception {
+        Utils.executeScript(
+            KnockoutTest.class,
+            "ko.utils.triggerEvent(window.document.getElementById(arguments[0]), arguments[1]);",
+            id, ev
+        );
+    }
+
+    @KOTest public void displayContentOfArray() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "<ul id='ul' data-bind='foreach: results'>\n"
+            + "  <li data-bind='text: $data, click: $root.call'/>\n"
+            + "</ul>\n"
+        );
+        try {
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.getResults().add("Ahoj");
+            m.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 1, "One child, but was " + cnt);
+
+            m.getResults().add("Hi");
+
+            cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Two children now, but was " + cnt);
+
+            triggerChildClick("ul", 1);
+
+            assertEquals(1, m.getCallbackCount(), "One callback " + m.getCallbackCount());
+            assertEquals("Hi", m.getName(), "We got callback from 2nd child " + m.getName());
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void displayContentOfAsyncArray() throws Exception {
+        if (js == null) {
+            Utils.exposeHTML(KnockoutTest.class,
+                "<ul id='ul' data-bind='foreach: results'>\n"
+                + "  <li data-bind='text: $data, click: $root.call'/>\n"
+                + "</ul>\n"
+            );
+            js = Models.bind(new KnockoutModel(), newContext());
+            js.getResults().add("Ahoj");
+            js.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 1, "One child, but was " + cnt);
+
+            Timer t = new Timer("add to array");
+            t.schedule(new TimerTask() {
+                @Override
+                public void run() {
+                    js.getResults().add("Hi");
+                }
+            }, 1);
+        }
+
+
+        int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+        if (cnt != 2) {
+            throw new InterruptedException();
+        }
+
+        try {
+            triggerChildClick("ul", 1);
+
+            assertEquals(1, js.getCallbackCount(), "One callback " + js.getCallbackCount());
+            assertEquals("Hi", js.getName(), "We got callback from 2nd child " + js.getName());
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void displayContentOfComputedArray() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "<ul id='ul' data-bind='foreach: bothNames'>\n"
+            + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
+            + "</ul>\n"
+        );
+        try {
+            Pair m = Models.bind(new Pair("First", "Last", null), newContext());
+            m.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Two children now, but was " + cnt);
+
+            triggerChildClick("ul", 1);
+
+            assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
+
+            m.setLastName("Verylast");
+
+            cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Two children now, but was " + cnt);
+
+            triggerChildClick("ul", 1);
+
+            assertEquals("Verylast", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
+
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void displayContentOfComputedArrayOnASubpair() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+              "<div data-bind='with: next'>\n"
+            + "<ul id='ul' data-bind='foreach: bothNames'>\n"
+            + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
+            + "</ul>"
+            + "</div>\n"
+        );
+        try {
+            final BrwsrCtx ctx = newContext();
+            Pair m = Models.bind(new Pair(null, null, new Pair("First", "Last", null)), ctx);
+            m.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Two children now, but was " + cnt);
+
+            triggerChildClick("ul", 1);
+
+            assertEquals(PairModel.ctx, ctx, "Context remains the same");
+
+            assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void displayContentOfComputedArrayOnComputedASubpair() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+              "<div data-bind='with: nextOne'>\n"
+            + "<ul id='ul' data-bind='foreach: bothNames'>\n"
+            + "  <li data-bind='text: $data, click: $root.assignFirstName'/>\n"
+            + "</ul>"
+            + "</div>\n"
+        );
+        try {
+            Pair m = Models.bind(new Pair(null, null, new Pair("First", "Last", null)), newContext());
+            m.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Two children now, but was " + cnt);
+
+            triggerChildClick("ul", 1);
+
+            assertEquals("Last", m.getFirstName(), "We got callback from 2nd child " + m.getFirstName());
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void checkBoxToBooleanBinding() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "<input type='checkbox' id='b' data-bind='checked: enabled'></input>\n"
+        );
+        try {
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.applyBindings();
+
+            assertFalse(m.isEnabled(), "Is disabled");
+
+            triggerClick("b");
+
+            assertTrue(m.isEnabled(), "Now the model is enabled");
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+
+
+    @KOTest public void displayContentOfDerivedArray() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "<ul id='ul' data-bind='foreach: cmpResults'>\n"
+            + "  <li><b data-bind='text: $data'></b></li>\n"
+            + "</ul>\n"
+        );
+        try {
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.getResults().add("Ahoj");
+            m.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 1, "One child, but was " + cnt);
+
+            m.getResults().add("hello");
+
+            cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Two children now, but was " + cnt);
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void displayContentOfArrayOfPeople() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "<ul id='ul' data-bind='foreach: people'>\n"
+            + "  <li data-bind='text: $data.firstName, click: $root.removePerson'></li>\n"
+            + "</ul>\n"
+        );
+        try {
+            final BrwsrCtx c = newContext();
+            KnockoutModel m = Models.bind(new KnockoutModel(), c);
+
+            final Person first = Models.bind(new Person(), c);
+            first.setFirstName("first");
+            m.getPeople().add(first);
+
+            m.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 1, "One child, but was " + cnt);
+
+            final Person second = Models.bind(new Person(), c);
+            second.setFirstName("second");
+            m.getPeople().add(second);
+
+            cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Two children now, but was " + cnt);
+
+            triggerChildClick("ul", 1);
+
+            assertEquals(1, m.getCallbackCount(), "One callback " + m.getCallbackCount());
+
+            cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt , 1, "Again one child, but was " + cnt);
+
+            String txt = childText("ul", 0);
+            assertEquals("first", txt, "Expecting 'first': " + txt);
+
+            first.setFirstName("changed");
+
+            txt = childText("ul", 0);
+            assertEquals("changed", txt, "Expecting 'changed': " + txt);
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @ComputedProperty
+    static Person firstPerson(List<Person> people) {
+        return people.isEmpty() ? null : people.get(0);
+    }
+
+    @KOTest public void accessFirstPersonWithOnFunction() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "<p id='ul' data-bind='with: firstPerson'>\n"
+            + "  <span data-bind='text: firstName, click: changeSex'></span>\n"
+            + "</p>\n"
+        );
+        try {
+            trasfertToFemale();
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void onPersonFunction() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+            "<ul id='ul' data-bind='foreach: people'>\n"
+            + "  <li data-bind='text: $data.firstName, click: changeSex'></li>\n"
+            + "</ul>\n"
+        );
+        try {
+            trasfertToFemale();
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    private void trasfertToFemale() throws Exception {
+        KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+
+        final Person first = Models.bind(new Person(), newContext());
+        first.setFirstName("first");
+        first.setSex(Sex.MALE);
+        m.getPeople().add(first);
+
+
+        m.applyBindings();
+
+        int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+        assertEquals(cnt, 1, "One child, but was " + cnt);
+
+
+        triggerChildClick("ul", 0);
+
+        assertEquals(first.getSex(), Sex.FEMALE, "Transverted to female: " + first.getSex());
+    }
+
+    @KOTest public void stringArrayModificationVisible() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+                "<div>\n"
+                + "<ul id='ul' data-bind='foreach: results'>\n"
+                + "  <li data-bind='text: $data'></li>\n"
+                + "</ul>\n"
+              + "</div>\n"
+        );
+        try {
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.getResults().add("Ahoj");
+            m.getResults().add("Hello");
+            m.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Two children " + cnt);
+
+            Object arr = Utils.addChildren(KnockoutTest.class, "ul", "results", "Hi");
+            assertTrue(arr instanceof Object[], "Got back an array: " + arr);
+            final int len = ((Object[])arr).length;
+
+            assertEquals(len, 3, "Three elements in the array " + len);
+
+            int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
+
+            assertEquals(m.getResults().size(), 3, "Three java strings: " + m.getResults());
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void intArrayModificationVisible() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+                "<div>\n"
+                + "<ul id='ul' data-bind='foreach: numbers'>\n"
+                + "  <li data-bind='text: $data'></li>\n"
+                + "</ul>\n"
+              + "</div>\n"
+        );
+        try {
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.getNumbers().add(1);
+            m.getNumbers().add(31);
+            m.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Two children " + cnt);
+
+            Object arr = Utils.addChildren(KnockoutTest.class, "ul", "numbers", 42);
+            assertTrue(arr instanceof Object[], "Got back an array: " + arr);
+            final int len = ((Object[])arr).length;
+
+            assertEquals(len, 3, "Three elements in the array " + len);
+
+            int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
+
+            assertEquals(m.getNumbers().size(), 3, "Three java ints: " + m.getNumbers());
+            assertEquals(m.getNumbers().get(2), 42, "Meaning of world: " + m.getNumbers());
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void derivedIntArrayModificationVisible() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+                "<div>\n"
+                + "<ul id='ul' data-bind='foreach: resultLengths'>\n"
+                + "  <li data-bind='text: $data'></li>\n"
+                + "</ul>\n"
+              + "</div>\n"
+        );
+        try {
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.getResults().add("Ahoj");
+            m.getResults().add("Hello");
+            m.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 2, "Two children " + cnt);
+
+            Object arr = Utils.addChildren(KnockoutTest.class, "ul", "results", "Hi");
+            assertTrue(arr instanceof Object[], "Got back an array: " + arr);
+            final int len = ((Object[])arr).length;
+
+            assertEquals(len, 3, "Three elements in the array " + len);
+
+            int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(newCnt, 3, "Three children in the DOM: " + newCnt);
+
+            assertEquals(m.getResultLengths().size(), 3, "Three java ints: " + m.getResultLengths());
+            assertEquals(m.getResultLengths().get(2), 2, "Size is two: " + m.getResultLengths());
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @KOTest public void archetypeArrayModificationVisible() throws Exception {
+        Object exp = Utils.exposeHTML(KnockoutTest.class,
+                "<div>\n"
+                + "<ul id='ul' data-bind='foreach: archetypes'>\n"
+                + "  <li data-bind='text: artifactId'></li>\n"
+                + "</ul>\n"
+              + "</div>\n"
+        );
+        try {
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
+            m.applyBindings();
+
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 0, "No children " + cnt);
+
+            Object arr = Utils.addChildren(KnockoutTest.class, "ul", "archetypes", new ArchetypeData("aid", "gid", "v", "n", "d", "u"));
+            assertTrue(arr instanceof Object[], "Got back an array: " + arr);
+            final int len = ((Object[])arr).length;
+
+            assertEquals(len, 1, "One element in the array " + len);
+
+            int newCnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(newCnt, 1, "One child in the DOM: " + newCnt);
+
+            assertEquals(m.getArchetypes().size(), 1, "One archetype: " + m.getArchetypes());
+            assertNotNull(m.getArchetypes().get(0), "Not null: " + m.getArchetypes());
+            assertEquals(m.getArchetypes().get(0).getArtifactId(), "aid", "'aid' == " + m.getArchetypes());
+        } finally {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }
+    }
+
+    @Function
+    static void call(KnockoutModel m, String data) {
+        m.setName(data);
+        m.setCallbackCount(m.getCallbackCount() + 1);
+    }
+
+    @Function
+    static void removePerson(KnockoutModel model, Person data) {
+        model.setCallbackCount(model.getCallbackCount() + 1);
+        model.getPeople().remove(data);
+    }
+
+
+    @ComputedProperty
+    static String helloMessage(String name) {
+        return "Hello " + name + "!";
+    }
+
+    @ComputedProperty
+    static List<String> cmpResults(List<String> results) {
+        return results;
+    }
+
+    private static void triggerClick(String id) throws Exception {
+        String s = "var id = arguments[0];"
+            + "var e = window.document.getElementById(id);\n "
+            + "if (e.checked) throw 'It should not be checked yet: ' + e;\n "
+            + "var ev = window.document.createEvent('MouseEvents');\n "
+            + "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n "
+            + "e.dispatchEvent(ev);\n "
+            + "if (!e.checked) {\n"
+            + "  e.checked = true;\n "
+            + "  e.dispatchEvent(ev);\n "
+            + "}\n";
+        Utils.executeScript(
+            KnockoutTest.class,
+            s, id);
+    }
+    private static void triggerChildClick(String id, int pos) throws Exception {
+        String s =
+            "var id = arguments[0]; var pos = arguments[1];\n" +
+            "var e = window.document.getElementById(id);\n " +
+            "var ev = window.document.createEvent('MouseEvents');\n " +
+            "ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n " +
+            "var list = e.childNodes;\n" +
+            "var cnt = -1;\n" +
+            "for (var i = 0; i < list.length; i++) {\n" +
+            "  if (list[i].nodeType == 1) cnt++;\n" +
+            "  if (cnt == pos) return list[i].dispatchEvent(ev);\n" +
+            "}\n" +
+            "return null;\n";
+        Utils.executeScript(
+            KnockoutTest.class,
+            s, id, pos);
+    }
+
+    private static String childText(String id, int pos) throws Exception {
+        String s =
+            "var id = arguments[0]; var pos = arguments[1];" +
+            "var e = window.document.getElementById(id);\n" +
+            "var list = e.childNodes;\n" +
+            "var cnt = -1;\n" +
+            "for (var i = 0; i < list.length; i++) {\n" +
+            "  if (list[i].nodeType == 1) cnt++;\n" +
+            "  if (cnt == pos) return list[i].innerHTML;\n" +
+            "}\n" +
+            "return null;\n";
+        return (String)Utils.executeScript(
+            KnockoutTest.class,
+            s, id, pos);
+    }
+
+    private static BrwsrCtx newContext() {
+        return Utils.newContext(KnockoutTest.class);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/MinesTest.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/MinesTest.java b/json-tck/src/main/java/net/java/html/json/tests/MinesTest.java
new file mode 100644
index 0000000..a6059b7
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/MinesTest.java
@@ -0,0 +1,356 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package net.java.html.json.tests;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+import net.java.html.BrwsrCtx;
+import net.java.html.json.ComputedProperty;
+import net.java.html.json.Function;
+import net.java.html.json.Model;
+import net.java.html.json.ModelOperation;
+import net.java.html.json.Models;
+import net.java.html.json.Property;
+import org.netbeans.html.json.tck.KOTest;
+import static net.java.html.json.tests.Utils.assertEquals;
+
+/** Tests model of a mine field and its behavior in the browser.
+ */
+@Model(className = "Mines", targetId = "", properties = {
+    @Property(name = "state", type = MinesTest.GameState.class),
+    @Property(name = "rows", type = Row.class, array = true),
+})
+public final class MinesTest {
+    Mines m;
+    @KOTest public void paintTheGridOnClick() throws Throwable {
+        if (m == null) {
+            BrwsrCtx ctx = Utils.newContext(MinesTest.class);
+            Object exp = Utils.exposeHTML(MinesTest.class, 
+    "            <button id='init' data-bind='click: normalSize'></button>\n" +
+    "            <table>\n" +
+    "                <tbody id='table'>\n" +
+    "                    <!-- ko foreach: rows -->\n" +
+    "                    <tr>\n" +
+    "                        <!-- ko foreach: columns -->\n" +
+    "                        <td data-bind='css: style' >\n" +
+    "                            <div data-bind='text: html'></div>\n" +
+    "                        </td>\n" +
+    "                        <!-- /ko -->\n" +
+    "                    </tr>\n" +
+    "                    <!-- /ko -->\n" +
+    "                </tbody>\n" +
+    "            </table>\n" +
+    ""
+            );
+            m = Models.bind(new Mines(), ctx);
+            m.applyBindings();
+            int cnt = Utils.countChildren(MinesTest.class, "table");
+            assertEquals(cnt, 0, "Table is empty: " + cnt);
+            scheduleClick("init", 100);
+        }
+
+
+        int cnt = Utils.countChildren(MinesTest.class, "table");
+        if (cnt == 0) {
+            throw new InterruptedException();
+        }
+        assertEquals(cnt, 10, "There is ten rows in the table now: " + cnt);
+        
+        Utils.exposeHTML(MinesTest.class, "");
+    }
+    
+    @KOTest public void countAround() throws Exception {
+        Mines mines = new Mines();
+        mines.init(5, 5, 0);
+        mines.getRows().get(0).getColumns().get(0).setMine(true);
+        mines.getRows().get(1).getColumns().get(0).setMine(true);
+        mines.getRows().get(0).getColumns().get(1).setMine(true);
+        
+        int cnt = around(mines, 1, 1);
+        assertEquals(cnt, 3, "There are three mines around. Was: " + cnt);
+    }
+    
+    private static void scheduleClick(String id, int delay) throws Exception {
+        String s = "var id = arguments[0]; var delay = arguments[1];"
+            + "var e = window.document.getElementById(id);\n "
+            + "var f = function() {;\n "
+            + "  var ev = window.document.createEvent('MouseEvents');\n "
+            + "  ev.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);\n "
+            + "  e.dispatchEvent(ev);\n"
+            + "};\n"
+            + "window.setTimeout(f, delay);";
+        Utils.executeScript(
+            MinesTest.class,
+            s, id, delay);
+    }
+    
+    enum GameState {
+        IN_PROGRESS, WON, LOST;
+    }
+    
+    @Model(className = "Row", properties = {
+        @Property(name = "columns", type = Square.class, array = true)
+    })
+    static class RowModel {
+    }
+
+    @Model(className = "Square", properties = {
+        @Property(name = "state", type = SquareType.class),
+        @Property(name = "mine", type = boolean.class)
+    })
+    static class SquareModel {
+        @ComputedProperty static String html(SquareType state) {
+            if (state == null) return "&nbsp;";
+            switch (state) {
+                case EXPLOSION: return "&#x2717;";
+                case UNKNOWN: return "&nbsp;";
+                case DISCOVERED: return "&#x2714;";  
+                case N_0: return "&nbsp;";
+            }
+            return "&#x278" + (state.ordinal() - 1);
+        }
+        
+        @ComputedProperty static String style(SquareType state) {
+            return state == null ? null : state.toString();
+        }
+    }
+    
+    enum SquareType {
+        N_0, N_1, N_2, N_3, N_4, N_5, N_6, N_7, N_8,
+        UNKNOWN, EXPLOSION, DISCOVERED;
+        
+        final boolean isVisible() {
+            return name().startsWith("N_");
+        }
+
+        final SquareType moreBombsAround() {
+            switch (this) {
+                case EXPLOSION:
+                case UNKNOWN:
+                case DISCOVERED:
+                case N_8:
+                    return this;
+            }
+            return values()[ordinal() + 1];
+        }
+    }
+    
+    @ComputedProperty static boolean fieldShowing(GameState state) {
+        return state != null;
+    }
+    
+    @Function static void normalSize(Mines m) {
+        m.init(10, 10, 10);
+    }
+    
+    @ModelOperation static void init(Mines model, int width, int height, int mines) {
+        List<Row> rows = new ArrayList<Row>(height);
+        for (int y = 0; y < height; y++) {
+            Square[] columns = new Square[width];
+            for (int x = 0; x < width; x++) {
+                columns[x] = new Square(SquareType.UNKNOWN, false);
+            }
+            rows.add(new Row(columns));
+        }
+        
+        Random r = new Random();
+        while (mines > 0) {
+            int x = r.nextInt(width);
+            int y = r.nextInt(height);
+            final Square s = rows.get(y).getColumns().get(x);
+            if (s.isMine()) {
+                continue;
+            }
+            s.setMine(true);
+            mines--;
+        }
+
+        model.setState(GameState.IN_PROGRESS);
+        model.getRows().clear();
+        model.getRows().addAll(rows);
+    }
+    
+    @ModelOperation static void computeMines(Mines model) {
+        List<Integer> xBombs = new ArrayList<Integer>();
+        List<Integer> yBombs = new ArrayList<Integer>();
+        final List<Row> rows = model.getRows();
+        boolean emptyHidden = false;
+        SquareType[][] arr = new SquareType[rows.size()][];
+        for (int y = 0; y < rows.size(); y++) {
+            final List<Square> columns = rows.get(y).getColumns();
+            arr[y] = new SquareType[columns.size()];
+            for (int x = 0; x < columns.size(); x++) {
+                Square sq = columns.get(x);
+                if (sq.isMine()) {
+                    xBombs.add(x);
+                    yBombs.add(y);
+                }
+                if (sq.getState().isVisible()) {
+                    arr[y][x] = SquareType.N_0;
+                } else {
+                    if (!sq.isMine()) {
+                        emptyHidden = true;
+                    }
+                }
+            }
+        }
+        for (int i = 0; i < xBombs.size(); i++) {
+            int x = xBombs.get(i);
+            int y = yBombs.get(i);
+            
+            incrementAround(arr, x, y);
+        }
+        for (int y = 0; y < rows.size(); y++) {
+            final List<Square> columns = rows.get(y).getColumns();
+            for (int x = 0; x < columns.size(); x++) {
+                Square sq = columns.get(x);
+                final SquareType newState = arr[y][x];
+                if (newState != null && newState != sq.getState()) {
+                    sq.setState(newState);
+                }
+            }
+        }
+        
+        if (!emptyHidden) {
+            model.setState(GameState.WON);
+            showAllBombs(model, SquareType.DISCOVERED);
+        }
+    }
+    
+    private static void incrementAround(SquareType[][] arr, int x, int y) {
+        incrementAt(arr, x - 1, y - 1);
+        incrementAt(arr, x - 1, y);
+        incrementAt(arr, x - 1, y + 1);
+
+        incrementAt(arr, x + 1, y - 1);
+        incrementAt(arr, x + 1, y);
+        incrementAt(arr, x + 1, y + 1);
+        
+        incrementAt(arr, x, y - 1);
+        incrementAt(arr, x, y + 1);
+    }
+    
+    private static void incrementAt(SquareType[][] arr, int x, int y) {
+        if (y >= 0 && y < arr.length) {
+            SquareType[] r = arr[y];
+            if (x >= 0 && x < r.length) {
+                SquareType sq = r[x];
+                if (sq != null) {
+                    r[x] = sq.moreBombsAround();
+                }
+            }
+        }
+    }
+    
+    static void showAllBombs(Mines model, SquareType state) {
+        for (Row row : model.getRows()) {
+            for (Square square : row.getColumns()) {
+                if (square.isMine()) {
+                    square.setState(state);
+                }
+            }
+        }
+    }
+    
+    private static void expandKnown(Mines model, Square data) {
+        final List<Row> rows = model.getRows();
+        for (int y = 0; y < rows.size(); y++) {
+            final List<Square> columns = rows.get(y).getColumns();
+            for (int x = 0; x < columns.size(); x++) {
+                Square sq = columns.get(x);
+                if (sq == data) {
+                    expandKnown(model, x, y);
+                    return;
+                }
+            }
+        }
+    }
+    private static void expandKnown(Mines model, int x , int y) {
+        if (y < 0 || y >= model.getRows().size()) {
+            return;
+        }
+        final List<Square> columns = model.getRows().get(y).getColumns();
+        if (x < 0 || x >= columns.size()) {
+            return;
+        }
+        final Square sq = columns.get(x);
+        if (sq.getState() == SquareType.UNKNOWN) {
+            int around = around(model, x, y);
+            final SquareType t = SquareType.valueOf("N_" + around);
+            sq.setState(t);
+            if (sq.getState() == SquareType.N_0) {
+                expandKnown(model, x - 1, y - 1);
+                expandKnown(model, x - 1, y);
+                expandKnown(model, x - 1, y + 1);
+                expandKnown(model, x , y - 1);
+                expandKnown(model, x, y + 1);
+                expandKnown(model, x + 1, y - 1);
+                expandKnown(model, x + 1, y);
+                expandKnown(model, x + 1, y + 1);
+            }
+        }
+    }
+    private static int around(Mines model, int x, int y) {
+        return minesAt(model, x - 1, y - 1)
+                + minesAt(model, x - 1, y)
+                + minesAt(model, x - 1, y + 1)
+                + minesAt(model, x, y - 1)
+                + minesAt(model, x, y + 1)
+                + minesAt(model, x + 1, y - 1)
+                + minesAt(model, x + 1, y)
+                + minesAt(model, x + 1, y + 1);
+    }
+
+    private static int minesAt(Mines model, int x, int y) {
+        if (y < 0 || y >= model.getRows().size()) {
+            return 0;
+        }
+        final List<Square> columns = model.getRows().get(y).getColumns();
+        if (x < 0 || x >= columns.size()) {
+            return 0;
+        }
+        Square sq = columns.get(x);
+        return sq.isMine() ? 1 : 0;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/OperationsTest.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/OperationsTest.java b/json-tck/src/main/java/net/java/html/json/tests/OperationsTest.java
new file mode 100644
index 0000000..dfb1951
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/OperationsTest.java
@@ -0,0 +1,96 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package net.java.html.json.tests;
+
+import net.java.html.json.Models;
+import org.netbeans.html.json.tck.KOTest;
+import static net.java.html.json.tests.Utils.assertEquals;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public class OperationsTest {
+    private JSONik js;
+    private Person p;
+    
+    @KOTest public void syncOperation() {
+        js = Models.bind(new JSONik(), Utils.newContext(OperationsTest.class));
+        p = new Person("Sitar", "Gitar", Sex.MALE, null);
+        js.applyBindings();
+        js.setFetched(p);
+        Person p = js.getFetched();
+        assertEquals("Sitar", p.getFirstName(), "Expecting Sitar immediately: " + p.getFirstName());
+        assertEquals(Sex.MALE, p.getSex(), "Expecting MALE immediately: " + p.getSex());
+    }
+    
+    
+    @KOTest public void asyncOperation() throws InterruptedException {
+        if (js == null) {
+            if (Utils.skipIfNoFullJDK()) {
+                return;
+            }
+            
+            
+            js = Models.bind(new JSONik(), Utils.newContext(OperationsTest.class));
+            p = new Person("Sitar", "Gitar", Sex.MALE, null);
+            js.applyBindings();
+
+            js.setFetched(null);
+            new Thread(new Runnable() {
+                @Override
+                public void run() {
+                    js.assignFetched(p);
+                }
+            }).start();
+        }
+    
+        Person p = js.getFetched();
+        if (p == null) {
+            throw new InterruptedException();
+        }
+        
+        assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + p.getFirstName());
+        assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex());
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/PairModel.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/PairModel.java b/json-tck/src/main/java/net/java/html/json/tests/PairModel.java
new file mode 100644
index 0000000..20843ec
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/PairModel.java
@@ -0,0 +1,80 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package net.java.html.json.tests;
+
+import java.util.Arrays;
+import java.util.List;
+import net.java.html.BrwsrCtx;
+import net.java.html.json.ComputedProperty;
+import net.java.html.json.Function;
+import net.java.html.json.Model;
+import net.java.html.json.Property;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+@Model(className = "Pair", targetId = "", properties = {
+    @Property(name = "firstName", type = String.class),
+    @Property(name = "lastName", type = String.class),
+    @Property(name = "next", type = Pair.class)
+})
+class PairModel {
+    static BrwsrCtx ctx;
+    
+    @ComputedProperty 
+    static List<String> bothNames(String firstName, String lastName) {
+        return Arrays.asList(firstName, lastName);
+    }
+    
+    @ComputedProperty
+    static Pair nextOne(Pair next) {
+        return next;
+    }
+    
+    @Function 
+    static void assignFirstName(Pair m, String data) {
+        ctx = BrwsrCtx.findDefault(Pair.class);
+        m.setFirstName(data);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/PersonImpl.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/PersonImpl.java b/json-tck/src/main/java/net/java/html/json/tests/PersonImpl.java
new file mode 100644
index 0000000..e7f8ede
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/PersonImpl.java
@@ -0,0 +1,100 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package net.java.html.json.tests;
+
+import net.java.html.json.ComputedProperty;
+import net.java.html.json.Function;
+import net.java.html.json.Model;
+import net.java.html.json.Property;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+@Model(className = "Person", properties = {
+    @Property(name = "firstName", type = String.class),
+    @Property(name = "lastName", type = String.class),
+    @Property(name = "sex", type = Sex.class),
+    @Property(name = "address", type = Address.class)
+})
+final class PersonImpl {
+    @ComputedProperty(write = "parseNames")
+    public static String fullName(String firstName, String lastName) {
+        return firstName + " " + lastName;
+    }
+
+    static void parseNames(Person p, String fullName) {
+        String[] arr = fullName.split(" ");
+        p.setFirstName(arr[0]);
+        p.setLastName(arr[1]);
+    }
+    
+    @ComputedProperty
+    public static String sexType(Sex sex) {
+        return sex == null ? "unknown" : sex.toString();
+    }
+    
+    @Function
+    static void changeSex(Person p) {
+        if (p.getSex() == Sex.MALE) {
+            p.setSex(Sex.FEMALE);
+        } else {
+            p.setSex(Sex.MALE);
+        }
+    }
+    
+    @Model(className = "People", properties = {
+        @Property(array = true, name = "info", type = Person.class),
+        @Property(array = true, name = "nicknames", type = String.class),
+        @Property(array = true, name = "age", type = int.class),
+        @Property(array = true, name = "sex", type = Sex.class)
+    })
+    public class PeopleImpl {
+    }
+    
+    @Model(className = "Address", properties = {
+        @Property(name = "street", type = String.class)
+    })
+    static class Addrss {
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/Sex.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/Sex.java b/json-tck/src/main/java/net/java/html/json/tests/Sex.java
new file mode 100644
index 0000000..2a266d7
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/Sex.java
@@ -0,0 +1,51 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package net.java.html.json.tests;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public enum Sex {
+    MALE, FEMALE;
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/Utils.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/Utils.java b/json-tck/src/main/java/net/java/html/json/tests/Utils.java
new file mode 100644
index 0000000..ddaf8ec
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/Utils.java
@@ -0,0 +1,225 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package net.java.html.json.tests;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.Map;
+import java.util.ServiceLoader;
+import net.java.html.BrwsrCtx;
+import org.netbeans.html.json.tck.KnockoutTCK;
+
+/**
+ *
+ * @author Jaroslav Tulach
+ */
+public final class Utils {
+    private static KnockoutTCK instantiatedTCK;
+
+    static boolean skipIfNoFullJDK() {
+        try {
+            Class<?> thread = Class.forName("java.lang.Thread");
+            Thread t = new Thread("Empty");
+            t.setName("Different");
+            t.setDaemon(false);
+            t.interrupt();
+            t.start();
+        } catch (ClassNotFoundException ex) {
+            return true;
+        } catch (SecurityException ex) {
+            return true;
+        }
+        return false;
+    }
+
+    private Utils() {
+    }
+    
+    public static void registerTCK(KnockoutTCK tck) {
+        instantiatedTCK = tck;
+    }
+
+    static  BrwsrCtx newContext(Class<?> clazz) {
+        for (KnockoutTCK tck : tcks(clazz)) {
+            BrwsrCtx c = tck.createContext();
+            if (c != null) {
+                return c;
+            }
+        }
+        throw new AssertionError("Can't find appropriate Context in ServiceLoader!");
+    }
+    static Object createObject(Map<String,Object> values, Class<?> clazz) {
+        for (KnockoutTCK tck : tcks(clazz)) {
+            Object o = tck.createJSON(values);
+            if (o != null) {
+                return o;
+            }
+        }
+        throw new AssertionError("Can't find appropriate Context in ServiceLoader!");
+    }
+    static Object executeScript(Class<?> clazz, 
+        String script, Object... arguments
+    ) throws Exception {
+        for (KnockoutTCK tck : tcks(clazz)) {
+            return tck.executeScript(script, arguments);
+        }
+        throw new AssertionError("Can't find appropriate Context in ServiceLoader!");
+    }
+
+    private static Iterable<KnockoutTCK> tcks(Class<?> clazz) {
+        if (instantiatedTCK != null) {
+            return Collections.singleton(instantiatedTCK);
+        }
+        return ServiceLoader.load(KnockoutTCK.class, cl(clazz));
+    }
+    
+    static Object exposeHTML(Class<?> clazz, String html) throws Exception {
+        String s = 
+          "var n = window.document.getElementById('ko.test.div'); \n "
+        + "if (!n) { \n"
+        + "  n = window.document.createElement('div'); \n "
+        + "  n.id = 'ko.test.div'; \n "
+        + "  var body = window.document.getElementsByTagName('body')[0];\n"
+        + "  body.appendChild(n);\n"
+        + "}\n"
+        + "n.innerHTML = arguments[0]; \n ";
+        return executeScript(clazz, s, html);
+    }
+
+    static int countChildren(Class<?> caller, String id) throws Exception {
+        return ((Number) executeScript(caller, 
+            "var e = window.document.getElementById(arguments[0]);\n" + 
+            "if (typeof e === 'undefined') return -2;\n " + 
+            "var list = e.childNodes;\n" +
+            "var cnt = 0;\n" + 
+            "for (var i = 0; i < list.length; i++) {\n" + 
+            "  if (list[i].nodeType == 1) cnt++;\n" + 
+            "}\n" + 
+            "return cnt;\n"
+            , id
+        )).intValue();
+    }
+
+    static Object addChildren(Class<?> caller, String id, String field, Object value) throws Exception {
+        return executeScript(caller, 
+            "var e = window.document.getElementById(arguments[0]);\n" + 
+            "var f = arguments[1];\n" + 
+            "var v = arguments[2];\n" + 
+            "if (typeof e === 'undefined') return -2;\n " + 
+            "var c = ko.contextFor(e);\n" +
+            "var fn = c.$rawData[f];\n" +
+            "var arr = c.$rawData[f]();\n" +
+            "arr.push(v);\n" + 
+            "fn(arr);\n" + 
+            "return arr;\n"
+            , id, field, value
+        );
+    }
+    
+    static String prepareURL(
+        Class<?> clazz, String content, String mimeType, String... parameters) {
+        for (KnockoutTCK tck : tcks(clazz)) {
+            URI o = tck.prepareURL(content, mimeType, parameters);
+            if (o != null) {
+                return o.toString();
+            }
+        }
+        throw new IllegalStateException();
+    }
+
+    static boolean canFailWebSockets(
+        Class<?> clazz) {
+        for (KnockoutTCK tck : tcks(clazz)) {
+            if (tck.canFailWebSocketTest()) {
+                return true;
+            }
+        }
+        return false;
+    }
+    
+    private static ClassLoader cl(Class<?> c) {
+        try {
+            return c.getClassLoader();
+        } catch (SecurityException ex) {
+            return null;
+        }
+    }
+    
+    static void fail(String msg) {
+        throw new AssertionError(msg);
+    }
+    
+    static void assertTrue(boolean c, String msg) {
+        if (!c) {
+            throw new AssertionError(msg);
+        }
+    }
+
+    static void assertFalse(boolean c, String msg) {
+        if (c) {
+            throw new AssertionError(msg);
+        }
+    }
+    
+    static void assertNull(Object o, String msg) {
+        if (o != null) {
+            throw new AssertionError(msg + " but was: " + o);
+        }
+    }
+
+    static void assertNotNull(Object o, String msg) {
+        if (o == null) {
+            throw new AssertionError(msg);
+        }
+    }
+    
+    static void assertEquals(Object a, Object b, String msg) {
+        if (a == b) {
+            return;
+        }
+        if (a != null && a.equals(b)) {
+            return;
+        }
+        throw new AssertionError(msg + " expecting: " + b + " actual: " + a);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java b/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java
new file mode 100644
index 0000000..267438d
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/WebSocketTest.java
@@ -0,0 +1,179 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package net.java.html.json.tests;
+
+import net.java.html.BrwsrCtx;
+import net.java.html.json.Model;
+import net.java.html.json.Models;
+import net.java.html.json.OnReceive;
+import net.java.html.json.Property;
+import org.netbeans.html.json.tck.KOTest;
+import static net.java.html.json.tests.Utils.assertEquals;
+import static net.java.html.json.tests.Utils.assertNotNull;
+import static net.java.html.json.tests.Utils.assertTrue;
+import static net.java.html.json.tests.Utils.fail;
+
+/** Testing support of WebSocket communication.
+ *
+ * @author Jaroslav Tulach
+ */
+@Model(className = "WebSocketik", targetId="", properties = {
+    @Property(name = "fetched", type = Person.class),
+    @Property(name = "fetchedCount", type = int.class),
+    @Property(name = "open", type = int.class),
+    @Property(name = "fetchedResponse", type = String.class),
+    @Property(name = "fetchedSex", type = Sex.class, array = true)
+})
+public final class WebSocketTest {
+    private WebSocketik js;
+    private String url;
+    
+    @OnReceive(url = "{url}", data = Sex.class, method = "WebSocket", onError = "error")
+    static void querySex(WebSocketik model, Person data) {
+        if (data == null) {
+            model.setOpen(1);
+        } else {
+            model.setFetched(data);
+        }
+    }
+    
+    @KOTest public void connectUsingWebSocket() throws Throwable {
+        if (js == null) {
+            url = Utils.prepareURL(
+                JSONTest.class, "{'firstName': 'Mitar', 'sex': '$0' }", 
+                "application/javascript",
+                "protocol:ws"
+            );
+            
+            js = Models.bind(new WebSocketik(), newContext());
+            js.applyBindings();
+
+            js.setFetched(null);
+            
+            // connects to the server
+            js.querySex(url, null);
+        }
+        
+        if (bailOutIfNotSupported(js)) {
+            return;
+        }
+        
+        if (js.getOpen() == 0) {
+            throw new InterruptedException();
+        }
+        if (js.getOpen() == 1) {
+            // send a query to the server
+            js.querySex(url, Sex.FEMALE);
+            js.setOpen(2);
+        }
+    
+        Person p = js.getFetched();
+        if (p == null) {
+            throw new InterruptedException();
+        }
+        
+        assertEquals("Mitar", p.getFirstName(), "Unexpected: " + p.getFirstName());
+        assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + p.getSex());
+
+        if (js.getOpen() == 2) {
+            // close the socket
+            js.querySex(url, null);
+            js.setOpen(3);
+        }
+        
+        if (js.getFetchedResponse() == null) {
+            throw new InterruptedException();
+        }
+        assertEquals("null", js.getFetchedResponse(), "Should be null: " + js.getFetchedResponse());
+    }
+    
+    @KOTest public void errorUsingWebSocket() throws Throwable {
+        if (js == null) {
+            js = Models.bind(new WebSocketik(), newContext());
+            js.applyBindings();
+
+            js.setFetched(null);
+            js.querySex("http://wrong.protocol", null);
+        }
+
+        if (js.getFetchedResponse() == null) {
+            throw new InterruptedException();
+        }
+
+        assertNotNull(js.getFetchedResponse(), "Error reported");
+    }
+
+    @KOTest public void haveToOpenTheWebSocket() throws Throwable {
+        js = Models.bind(new WebSocketik(), newContext());
+        js.applyBindings();
+
+        js.setFetched(null);
+        try {
+            js.querySex("http://wrong.protocol", Sex.MALE);
+            fail("Should throw an exception");
+        } catch (IllegalStateException ex) {
+            assertTrue(ex.getMessage().contains("not open"), "Expecting 'not open' msg: " + ex.getMessage());
+        }
+    }
+    
+    static void error(WebSocketik model, Exception ex) {
+        if (ex != null) {
+            model.setFetchedResponse(ex.getClass() + ":" + ex.getMessage());
+        } else {
+            model.setFetchedResponse("null");
+        }
+    }
+    
+    private static BrwsrCtx newContext() {
+        return Utils.newContext(WebSocketTest.class);
+    }
+
+    private boolean bailOutIfNotSupported(WebSocketik js) {
+        if (js.getFetchedResponse() == null) {
+            return false;
+        }
+        return js.getFetchedResponse().contains("UnsupportedOperationException") &&
+            Utils.canFailWebSockets(WebSocketTest.class);
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-netbeans-html4j/blob/226089a5/json-tck/src/main/java/org/netbeans/html/json/tck/JavaScriptTCK.java
----------------------------------------------------------------------
diff --git a/json-tck/src/main/java/org/netbeans/html/json/tck/JavaScriptTCK.java b/json-tck/src/main/java/org/netbeans/html/json/tck/JavaScriptTCK.java
new file mode 100644
index 0000000..755eb1c
--- /dev/null
+++ b/json-tck/src/main/java/org/netbeans/html/json/tck/JavaScriptTCK.java
@@ -0,0 +1,73 @@
+/**
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common
+ * Development and Distribution License("CDDL") (collectively, the
+ * "License"). You may not use this file except in compliance with the
+ * License. You can obtain a copy of the License at
+ * http://www.netbeans.org/cddl-gplv2.html
+ * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
+ * specific language governing permissions and limitations under the
+ * License.  When distributing the software, include this License Header
+ * Notice in each file and include the License file at
+ * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the GPL Version 2 section of the License file that
+ * accompanied this code. If applicable, add the following below the
+ * License Header, with the fields enclosed by brackets [] replaced by
+ * your own identifying information:
+ * "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * Contributor(s):
+ *
+ * The Original Software is NetBeans. The Initial Developer of the Original
+ * Software is Oracle. Portions Copyright 2013-2016 Oracle. All Rights Reserved.
+ *
+ * If you wish your version of this file to be governed by only the CDDL
+ * or only the GPL Version 2, indicate your decision by adding
+ * "[Contributor] elects to include this software in this distribution
+ * under the [CDDL or GPL Version 2] license." If you do not indicate a
+ * single choice of license, a recipient has the option to distribute
+ * your version of this file under either the CDDL, the GPL Version 2 or
+ * to extend the choice of license to its licensees as provided above.
+ * However, if you add GPL Version 2 code and therefore, elected the GPL
+ * Version 2 license, then the option applies only if the new code is
+ * made subject to such option by the copyright holder.
+ */
+package org.netbeans.html.json.tck;
+
+import net.java.html.js.tests.GCBodyTest;
+import net.java.html.js.tests.JavaScriptBodyTest;
+import org.netbeans.html.boot.spi.Fn;
+import org.netbeans.html.boot.spi.Fn.Presenter;
+
+/** Entry point for those who want to verify that their implementation of
+ * {@link Presenter} is good enough to support existing Java/JavaScript 
+ * communication use-cases. Subclass this class, get list of {@link #testClasses() classes}
+ * find methods annotated by {@link KOTest} annotation and execute them.
+ * <p>
+ *
+ * @author Jaroslav Tulach
+ * @since 0.7
+ */
+public abstract class JavaScriptTCK {
+    /** Gives you list of classes included in the TCK. Their test methods
+     * are annotated by {@link KOTest} annotation. The methods are public
+     * instance methods that take no arguments. The method should be 
+     * invoke in a presenter context {@link Fn#activate(org.netbeans.html.boot.spi.Fn.Presenter)}.
+     * 
+     * @return classes with methods annotated by {@link KOTest} annotation
+     */
+    protected static Class<?>[] testClasses() {
+        return new Class[] { 
+            JavaScriptBodyTest.class, GCBodyTest.class
+        };
+    }
+    
+}