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

jtulach pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans-html4j.git


The following commit(s) were added to refs/heads/master by this push:
     new 1e398f8  Automatic handling of Interrupted executions with 
PhaseExecutor
1e398f8 is described below

commit 1e398f86e740ba4aebef9457e0bea2dadf651b56
Author: Jaroslav Tulach <[email protected]>
AuthorDate: Tue Mar 22 18:35:46 2022 +0100

    Automatic handling of Interrupted executions with PhaseExecutor
---
 .../net/java/html/json/tests/GCKnockoutTest.java   |  59 +-
 .../java/net/java/html/json/tests/JSONTest.java    | 438 ++++++-------
 .../net/java/html/json/tests/KnockoutTest.java     | 694 +++++++++++----------
 .../java/html/json/tests/ObtainAndComputeTest.java |  77 ++-
 .../net/java/html/json/tests/PhaseExecutor.java    | 111 ++++
 .../main/java/net/java/html/json/tests/Utils.java  |   2 +-
 6 files changed, 780 insertions(+), 601 deletions(-)

diff --git 
a/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java 
b/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java
index 4024c57..527e4e9 100644
--- a/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java
+++ b/json-tck/src/main/java/net/java/html/json/tests/GCKnockoutTest.java
@@ -31,6 +31,8 @@ import static net.java.html.json.tests.Utils.assertEquals;
     @Property(name = "all", type = Fullname.class, array = true)
 })
 public class GCKnockoutTest {
+    private final PhaseExecutor[] phases = { null };
+
     @Model(className = "Fullname", properties = {
         @Property(name = "firstName", type = String.class),
         @Property(name = "lastName", type = String.class)
@@ -38,42 +40,55 @@ public class GCKnockoutTest {
     static class FullnameCntrl {
     }
 
-    @KOTest public void noLongerNeededArrayElementsCanDisappear() throws 
Exception {
-        BrwsrCtx ctx = Utils.newContext(GCKnockoutTest.class);
-        Object exp = Utils.exposeHTML(GCKnockoutTest.class, """
-                                                            <ul id='ul' 
data-bind='foreach: all'>
-                                                              <li 
data-bind='text: firstName'/>
-                                                            </ul>
-                                                            """);
-        try {
+    class Data {
+        GC m;
+        BrwsrCtx ctx;
+        Fullname removed;
+
+        Data(GC m, BrwsrCtx ctx) {
+            this.m = m;
+            this.ctx = ctx;
+        }
+    }
+
+    @KOTest
+    public void noLongerNeededArrayElementsCanDisappear() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            BrwsrCtx ctx = Utils.newContext(GCKnockoutTest.class);
+            Object exp = Utils.exposeHTML(GCKnockoutTest.class, """
+            <ul id='ul' data-bind='foreach: all'>
+              <li data-bind='text: firstName'/>
+            </ul>
+            """);
             GC m = Models.bind(new GC(), ctx);
             m.getAll().add(Models.bind(new Fullname("Jarda", "Tulach"), ctx));
             Models.applyBindings(m);
-
+            return new Data(m, ctx);
+        }).then((data) -> {
             int cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
             assertEquals(cnt, 1, "One child, but was " + cnt);
 
-            m.getAll().add(Models.bind(new Fullname("HTML", "Java"), ctx));
-
-            cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
+            var m = data.m;
+            m.getAll().add(Models.bind(new Fullname("HTML", "Java"), 
data.ctx));
+        }).then((data) -> {
+            int cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Now two " + cnt);
 
-            Fullname removed = m.getAll().remove(0);
-
-            cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
+            data.removed = data.m.getAll().remove(0);
+        }).then((data) -> {
+            var cnt = Utils.countChildren(GCKnockoutTest.class, "ul");
             assertEquals(cnt, 1, "Again One " + cnt);
 
-            Reference<?> ref = new WeakReference<Object>(removed);
-            removed = null;
+            Reference<?> ref = new WeakReference<Object>(data.removed);
+            data.removed = null;
             assertGC(ref, "Can removed object disappear?");
 
-            ref = new WeakReference<Object>(m);
-            m = null;
+            ref = new WeakReference<Object>(data.m);
+            data.m = null;
             assertNotGC(ref, "Root model cannot GC");
-        } finally {
+        }).finalize((data) -> {
             Utils.exposeHTML(GCKnockoutTest.class, "");
-        }
-
+        }).start();
     }
 
     private void assertGC(Reference<?> ref, String msg) throws Exception {
diff --git a/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java 
b/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java
index f88a201..5aaf4c8 100644
--- a/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java
+++ b/json-tck/src/main/java/net/java/html/json/tests/JSONTest.java
@@ -21,8 +21,6 @@ package net.java.html.json.tests;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.PrintStream;
-import java.lang.reflect.Field;
-import java.nio.charset.Charset;
 import net.java.html.BrwsrCtx;
 import net.java.html.json.Model;
 import net.java.html.json.ModelOperation;
@@ -46,30 +44,27 @@ import org.netbeans.html.json.tck.KOTest;
     @Property(name = "fetchedSex", type = Sex.class, array = true)
 })
 public final class JSONTest {
-    private JSONik js;
-    private Integer orig;
-    private String url;
+    private final PhaseExecutor[] phases = { null };
+    private static class Data {
+        final JSONik js;
+        final Integer orig;
+        final String url;
+        final BrwsrCtx ctx;
 
-    static {
-        if (System.getProperty("java.version").startsWith("1.")) {
-            try {
-                System.setProperty("file.encoding", "windows-1251");
-                Field f = Charset.class.getDeclaredField("defaultCharset");
-                f.setAccessible(true);
-                f.set(null, null);
-                assertEquals(Charset.defaultCharset().toString(), 
"windows-1251", "Encoding has been changed");
-            } catch (Throwable t) {
-                t.printStackTrace();
-            }
+        Data(JSONik js, Integer orig, String url, BrwsrCtx ctx) {
+            this.js = js;
+            this.orig = orig;
+            this.url = url;
+            this.ctx = ctx;
         }
     }
 
     @ModelOperation static void assignFetched(JSONik m, Person p) {
         m.setFetched(p);
     }
-    private BrwsrCtx ctx;
 
-    @KOTest public void toJSONInABrowser() throws Throwable {
+    @KOTest
+    public void toJSONInABrowser() throws Throwable {
         Person p = Models.bind(new Person(), newContext());
         p.setSex(Sex.MALE);
         p.setFirstName("Jára");
@@ -220,86 +215,83 @@ public final class JSONTest {
         model.setFetchedCount(sum);
     }
 
-    @KOTest public void loadAndParseJSON() throws InterruptedException {
-        if (js == null) {
-            url = Utils.prepareURL(
+    @KOTest
+    public void loadAndParseJSON() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "{'firstName': 'Sitar', 'sex': 'MALE'}",
                 "application/json"
             );
-            js = Models.bind(new JSONik(), ctx = newContext());
+            var ctx = newContext();
+            var js = Models.bind(new JSONik(), ctx);
             js.applyBindings();
 
             js.setFetched(null);
             js.fetch(url);
-        }
-
-        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());
-
-        assertEquals(ctx, onCallback, "Context is the same");
-    }
-
-    @KOTest public void loadAndParsePlainText() throws Exception {
-        if (js == null) {
-            url = Utils.prepareURL(
+            return new Data(js, 0, url, ctx);
+        }).then((d) -> {
+            Person p = d.js.getFetched();
+            assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + 
p.getFirstName());
+            assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + 
p.getSex());
+            assertEquals(d.ctx, onCallback, "Context is the same");
+        }).start();
+    }
+
+    @KOTest
+    public void loadAndParsePlainText() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "{'firstName': 'Sitar', 'sex': 'MALE'}",
                 "text/plain"
             );
-            js = Models.bind(new JSONik(), ctx = newContext());
+            var ctx = newContext();
+            var js = Models.bind(new JSONik(), ctx);
             js.applyBindings();
 
             js.setFetched(null);
             js.fetchPlain(url);
-        }
-
-        String s = js.getFetchedResponse();
-        if (s == null) {
-            throw new InterruptedException();
-        }
-
-        assertTrue(s.contains("Sitar"), "The text contains Sitar value: " + s);
-        assertTrue(s.contains("MALE"), "The text contains MALE value: " + s);
-
-        Person p = Models.parse(ctx, Person.class, new 
ByteArrayInputStream(s.getBytes()));
-
-        assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + 
p.getFirstName());
-        assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex());
-
-        assertEquals(ctx, onCallback, "Same context");
-    }
-
-    @KOTest public void loadAndParsePlainTextOnArray() throws Exception {
-        if (js == null) {
-            url = Utils.prepareURL(
+            return new Data(js, 0, url, ctx);
+        }).then((data) -> {
+            String s = data.js.getFetchedResponse();
+            assertTrue(s.contains("Sitar"), "The text contains Sitar value: " 
+ s);
+            assertTrue(s.contains("MALE"), "The text contains MALE value: " + 
s);
+            Person p = Models.parse(data.ctx, Person.class, new 
ByteArrayInputStream(s.getBytes()));
+            assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + 
p.getFirstName());
+            assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + 
p.getSex());
+            assertEquals(data.ctx, onCallback, "Same context");
+        }).start();
+    }
+
+    @KOTest
+    public void loadAndParsePlainTextOnArray() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "[ {'firstName': 'Sitar', 'sex': 'MALE'} ]",
                 "text/plain"
             );
-            js = Models.bind(new JSONik(), ctx = newContext());
+            var ctx = newContext();
+            var js = Models.bind(new JSONik(), ctx);
             js.applyBindings();
 
             js.setFetched(null);
             js.fetchPlain(url);
-        }
-
-        String s = js.getFetchedResponse();
-        if (s == null) {
-            throw new InterruptedException();
-        }
+            return new Data(js, 0, url, ctx);
+        }).then((data) -> {
+            String s = data.js.getFetchedResponse();
+            if (s == null) {
+                throw new InterruptedException();
+            }
 
-        assertTrue(s.contains("Sitar"), "The text contains Sitar value: " + s);
-        assertTrue(s.contains("MALE"), "The text contains MALE value: " + s);
+            assertTrue(s.contains("Sitar"), "The text contains Sitar value: " 
+ s);
+            assertTrue(s.contains("MALE"), "The text contains MALE value: " + 
s);
 
-        Person p = Models.parse(ctx, Person.class, new 
ByteArrayInputStream(s.getBytes()));
+            Person p = Models.parse(data.ctx, Person.class, new 
ByteArrayInputStream(s.getBytes()));
 
-        assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + 
p.getFirstName());
-        assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex());
+            assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + 
p.getFirstName());
+            assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + 
p.getSex());
 
-        assertEquals(ctx, onCallback, "Same context");
+            assertEquals(data.ctx, onCallback, "Same context");
+        }).start();
     }
 
     @OnReceive(url="{url}?callme={me}", jsonp = "me")
@@ -307,79 +299,83 @@ public final class JSONTest {
         model.setFetched(p);
     }
 
-    @KOTest public void loadAndParseJSONP() throws InterruptedException, 
Exception {
-        if (js == null) {
-            url = Utils.prepareURL(
+    @KOTest
+    public void loadAndParseJSONP() throws InterruptedException, Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "$0({'firstName': 'Mitar', 'sex': 'MALE'})",
                 "application/javascript",
                 "callme"
             );
-            orig = scriptElements();
+            var orig = scriptElements();
             assertTrue(orig > 0, "There should be some scripts on the page");
 
-            js = Models.bind(new JSONik(), newContext());
+            var js = Models.bind(new JSONik(), newContext());
             js.applyBindings();
 
             js.setFetched(null);
             js.fetchViaJSONP(url);
-        }
-
-        Person p = js.getFetched();
-        if (p == null) {
-            throw new InterruptedException();
-        }
+            return new Data(js, orig, url, null);
+        }).then((data) -> {
+            Person p = data.js.getFetched();
+            if (p == null) {
+                throw new InterruptedException();
+            }
 
-        assertEquals("Mitar", p.getFirstName(), "Unexpected: " + 
p.getFirstName());
-        assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + p.getSex());
+            assertEquals("Mitar", p.getFirstName(), "Unexpected: " + 
p.getFirstName());
+            assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + 
p.getSex());
 
-        int now = scriptElements();
+            int now = scriptElements();
 
-        assertEquals(orig, now, "The set of elements is unchanged. Delta: " + 
(now - orig));
+            assertEquals(data.orig, now, "The set of elements is unchanged. 
Delta: " + (now - data.orig));
+        }).start();
     }
 
-
-
     @OnReceive(url="{url}", method = "PUT", data = Person.class)
     static void putPerson(JSONik model, String reply) {
         model.setFetchedCount(1);
         model.setFetchedResponse(reply);
     }
 
-    @KOTest public void putPeopleUsesRightMethod() throws 
InterruptedException, Exception {
-        if (js == null) {
-            url = Utils.prepareURL(
+    @KOTest
+    public void putPeopleUsesRightMethod() throws InterruptedException, 
Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "$0\n$1",
                 "text/plain",
                 "http.method", "http.requestBody"
             );
-            orig = scriptElements();
+            var orig = scriptElements();
             assertTrue(orig > 0, "There should be some scripts on the page");
 
-            js = Models.bind(new JSONik(), newContext());
+            var js = Models.bind(new JSONik(), newContext());
             js.applyBindings();
 
             Person p = Models.bind(new Person(), BrwsrCtx.EMPTY);
             p.setFirstName("Jarda");
             js.putPerson(url, p);
-        }
 
-        int cnt = js.getFetchedCount();
-        if (cnt == 0) {
-            throw new InterruptedException();
-        }
-        String res = js.getFetchedResponse();
-        int line = res.indexOf('\n');
-        String msg;
-        if (line >= 0) {
-            msg = res.substring(line + 1);
-            res = res.substring(0, line);
-        } else {
-            msg = res;
-        }
+            return new Data(js, orig, url, null);
+        }).then((data) -> {
+            var js = data.js;
+            int cnt = js.getFetchedCount();
+            if (cnt == 0) {
+                throw new InterruptedException();
+            }
+            String res = js.getFetchedResponse();
+            int line = res.indexOf('\n');
+            String msg;
+            if (line >= 0) {
+                msg = res.substring(line + 1);
+                res = res.substring(0, line);
+            } else {
+                msg = res;
+            }
 
-        assertEquals("PUT", res, "Server was queried with PUT method: " + 
js.getFetchedResponse());
+            assertEquals("PUT", res, "Server was queried with PUT method: " + 
js.getFetchedResponse());
 
-        assertTrue(msg.contains("Jarda"), "Data transferred to the server: " + 
msg);
+            assertTrue(msg.contains("Jarda"), "Data transferred to the server: 
" + msg);
+        }).start();
     }
 
     private static int scriptElements() throws Exception {
@@ -394,79 +390,87 @@ public final class JSONTest {
             "return window.JSON.parse(arguments[0]);", s);
     }
 
-    @KOTest public void loadAndParseJSONSentToArray() throws 
InterruptedException {
-        if (js == null) {
-            url = Utils.prepareURL(
+    @KOTest
+    public void loadAndParseJSONSentToArray() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "{'firstName': 'Sitar', 'sex': 'MALE'}",
                 "application/json"
             );
 
-            js = Models.bind(new JSONik(), newContext());
+            var js = Models.bind(new JSONik(), newContext());
             js.applyBindings();
 
             js.setFetched(null);
             js.fetchArray(url);
-        }
-
-        Person p = js.getFetched();
-        if (p == null) {
-            throw new InterruptedException();
-        }
+            return new Data(js, 0, url, null);
+        }).then((data) -> {
+            Person p = data.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());
+            assertEquals("Sitar", p.getFirstName(), "Expecting Sitar: " + 
p.getFirstName());
+            assertEquals(Sex.MALE, p.getSex(), "Expecting MALE: " + 
p.getSex());
+        }).start();
     }
 
-    @KOTest public void loadAndParseJSONArraySingle() throws 
InterruptedException {
-        if (js == null) {
-            url = Utils.prepareURL(
+    @KOTest
+    public void loadAndParseJSONArraySingle() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "[{'firstName': 'Gitar', 'sex': 'FEMALE'}]",
                 "application/json"
             );
-            js = Models.bind(new JSONik(), newContext());
+            var js = Models.bind(new JSONik(), newContext());
             js.applyBindings();
 
             js.setFetched(null);
             js.fetch(url);
-        }
-
-        Person p = js.getFetched();
-        if (p == null) {
-            throw new InterruptedException();
-        }
+            return new Data(js, 0, url, null);
+        }).then((data) -> {
+            Person p = data.js.getFetched();
+            if (p == null) {
+                throw new InterruptedException();
+            }
 
-        assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + 
p.getFirstName());
-        assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + 
p.getSex());
+            assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + 
p.getFirstName());
+            assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + 
p.getSex());
+        }).start();
     }
 
-    @KOTest public void loadAndParseArrayInPeople() throws 
InterruptedException {
-        if (js == null) {
-            url = Utils.prepareURL(
+    @KOTest
+    public void loadAndParseArrayInPeople() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "{'info':[{'firstName': 'Gitar', 'sex': 
'FEMALE'}]}",
                 "application/json"
             );
-            js = Models.bind(new JSONik(), newContext());
+            var js = Models.bind(new JSONik(), newContext());
             js.applyBindings();
 
             js.fetchPeople(url);
-        }
-
-        if (0 == js.getFetchedCount()) {
-            throw new InterruptedException();
-        }
+            return new Data(js, 0, url, null);
+        }).then((data) -> {
+            var js = data.js;
+            if (0 == js.getFetchedCount()) {
+                throw new InterruptedException();
+            }
 
-        assertEquals(js.getFetchedCount(), 1, "One person loaded: " + 
js.getFetchedCount());
+            assertEquals(js.getFetchedCount(), 1, "One person loaded: " + 
js.getFetchedCount());
 
-        Person p = js.getFetched();
+            Person p = js.getFetched();
 
-        assertNotNull(p, "We should get our person back: " + p);
-        assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + 
p.getFirstName());
-        assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + 
p.getSex());
+            assertNotNull(p, "We should get our person back: " + p);
+            assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + 
p.getFirstName());
+            assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + 
p.getSex());
+        }).start();
     }
 
-    @KOTest public void loadAndParseArrayInPeopleWithHeaders() throws 
InterruptedException {
-        if (js == null) {
-            url = Utils.prepareURL(
+    @KOTest
+    public void loadAndParseArrayInPeopleWithHeaders() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "{'info':[{'firstName': '$0$1$2$3$4', 'sex': 
'FEMALE'}]}",
                 "application/json",
                 "http.header.Easy",
@@ -475,23 +479,26 @@ public final class JSONTest {
                 "http.header.Repeat*ed",
                 "http.header.Same-URL"
             );
-            js = Models.bind(new JSONik(), newContext());
+            var js = Models.bind(new JSONik(), newContext());
             js.applyBindings();
 
             js.fetchPeopleWithHeaders(url, "easy", "harder", "rep");
-        }
-
-        if (0 == js.getFetchedCount()) {
-            throw new InterruptedException();
-        }
+            return new Data(js, 0, url, null);
+        }).then((data) -> {
+            var js = data.js;
+            var url = data.url;
+            if (0 == js.getFetchedCount()) {
+                throw new InterruptedException();
+            }
 
-        assertEquals(js.getFetchedCount(), 1, "One person loaded: " + 
js.getFetchedCount());
+            assertEquals(js.getFetchedCount(), 1, "One person loaded: " + 
js.getFetchedCount());
 
-        Person p = js.getFetched();
+            Person p = js.getFetched();
 
-        assertNotNull(p, "We should get our person back: " + p);
-        assertEquals("easyharderreprep" + url, p.getFirstName(), "Expecting 
header mess: " + p.getFirstName());
-        assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + 
p.getSex());
+            assertNotNull(p, "We should get our person back: " + p);
+            assertEquals("easyharderreprep" + url, p.getFirstName(), 
"Expecting header mess: " + p.getFirstName());
+            assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + 
p.getSex());
+        }).start();
     }
 
     @OnReceive(url="{url}", headers={
@@ -509,23 +516,26 @@ public final class JSONTest {
         model.setFetchedCount(size);
     }
 
-    @KOTest public void loadAndParseArrayOfIntegers() throws 
InterruptedException {
-        if (js == null) {
-            url = Utils.prepareURL(
+    @KOTest
+    public void loadAndParseArrayOfIntegers() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "{'age':[1, 2, 3]}",
                 "application/json"
             );
-            js = Models.bind(new JSONik(), newContext());
+            var js = Models.bind(new JSONik(), newContext());
             js.applyBindings();
 
             js.fetchPeopleAge(url);
-        }
-
-        if (0 == js.getFetchedCount()) {
-            throw new InterruptedException();
-        }
+            return new Data(js, 0, url, null);
+        }).then((data) -> {
+            var js = data.js;
+            if (0 == js.getFetchedCount()) {
+                throw new InterruptedException();
+            }
 
-        assertEquals(js.getFetchedCount(), 6, "1 + 2 + 3 is " + 
js.getFetchedCount());
+            assertEquals(js.getFetchedCount(), 6, "1 + 2 + 3 is " + 
js.getFetchedCount());
+        }).start();
     }
 
     @OnReceive(url="{url}")
@@ -534,71 +544,75 @@ public final class JSONTest {
         model.getFetchedSex().addAll(p.getSex());
     }
 
-    @KOTest public void loadAndParseArrayOfEnums() throws InterruptedException 
{
-        if (js == null) {
-            url = Utils.prepareURL(
+    @KOTest public void loadAndParseArrayOfEnums() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "{'sex':['FEMALE', 'MALE', 'MALE']}",
                 "application/json"
             );
-            js = Models.bind(new JSONik(), newContext());
+            var js = Models.bind(new JSONik(), newContext());
             js.applyBindings();
 
             js.fetchPeopleSex(url);
-        }
-
-        if (0 == js.getFetchedCount()) {
-            throw new InterruptedException();
-        }
+            return new Data(js, 0, url, null);
+        }).then((data) -> {
+            var js = data.js;
+            if (0 == js.getFetchedCount()) {
+                throw new InterruptedException();
+            }
 
-        assertEquals(js.getFetchedCount(), 1, "Loaded");
+            assertEquals(js.getFetchedCount(), 1, "Loaded");
 
-        assertEquals(js.getFetchedSex().size(), 3, "Three values " + 
js.getFetchedSex());
-        assertEquals(js.getFetchedSex().get(0), Sex.FEMALE, "Female first " + 
js.getFetchedSex());
-        assertEquals(js.getFetchedSex().get(1), Sex.MALE, "male 2nd " + 
js.getFetchedSex());
-        assertEquals(js.getFetchedSex().get(2), Sex.MALE, "male 3rd " + 
js.getFetchedSex());
+            assertEquals(js.getFetchedSex().size(), 3, "Three values " + 
js.getFetchedSex());
+            assertEquals(js.getFetchedSex().get(0), Sex.FEMALE, "Female first 
" + js.getFetchedSex());
+            assertEquals(js.getFetchedSex().get(1), Sex.MALE, "male 2nd " + 
js.getFetchedSex());
+            assertEquals(js.getFetchedSex().get(2), Sex.MALE, "male 3rd " + 
js.getFetchedSex());
+        }).start();
     }
 
-    @KOTest public void loadAndParseJSONArray() throws InterruptedException {
-        if (js == null) {
-            url = Utils.prepareURL(
+    @KOTest public void loadAndParseJSONArray() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var url = Utils.prepareURL(
                 JSONTest.class, "[{'firstName': 'Gitar', 'sex': 'FEMALE'},"
                 + "{'firstName': 'Peter', 'sex': 'MALE'}"
                 + "]",
                 "application/json"
             );
-            js = Models.bind(new JSONik(), newContext());
+            var js = Models.bind(new JSONik(), newContext());
             js.applyBindings();
             js.setFetched(null);
 
             js.fetchArray(url);
-        }
-
-
-        Person p = js.getFetched();
-        if (p == null) {
-            throw new InterruptedException();
-        }
+            return new Data(js, 0, url, null);
+        }).then((data) -> {
+            var js = data.js;
+            Person p = js.getFetched();
+            if (p == null) {
+                throw new InterruptedException();
+            }
 
-        assertEquals(js.getFetchedCount(), 2, "We got two values: " + 
js.getFetchedCount());
-        assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + 
p.getFirstName());
-        assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + 
p.getSex());
+            assertEquals(js.getFetchedCount(), 2, "We got two values: " + 
js.getFetchedCount());
+            assertEquals("Gitar", p.getFirstName(), "Expecting Gitar: " + 
p.getFirstName());
+            assertEquals(Sex.FEMALE, p.getSex(), "Expecting FEMALE: " + 
p.getSex());
+        }).start();
     }
 
-    @KOTest public void loadError() throws InterruptedException {
-        if (js == null) {
-            js = Models.bind(new JSONik(), newContext());
+    @KOTest
+    public void loadError() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            var js = Models.bind(new JSONik(), newContext());
             js.applyBindings();
             js.setFetched(null);
 
             js.fetchArray("http://127.0.0.1:54253/does/not/exist.txt";);
-        }
-
-
-        if (js.getFetchedResponse() == null) {
-            throw new InterruptedException();
-        }
-
-        assertEquals("Exception", js.getFetchedResponse(), "Response " + 
js.getFetchedResponse());
+            return new Data(js, 0, null, null);
+        }).then((data) -> {
+            var js = data.js;
+            if (js.getFetchedResponse() == null) {
+                throw new InterruptedException();
+            }
+            assertEquals("Exception", js.getFetchedResponse(), "Response " + 
js.getFetchedResponse());
+        }).start();
     }
 
     @Model(className = "NameAndValue", properties = {
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
index c25567c..d2684f8 100644
--- 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
@@ -48,7 +48,7 @@ import static net.java.html.json.tests.Utils.assertFalse;
     @Property(name="archetypes", type=ArchetypeData.class, array = true),
 })
 public final class KnockoutTest {
-    private KnockoutModel js;
+    private PhaseExecutor[] phases = new PhaseExecutor[1];
 
     enum Choice {
         A, B;
@@ -62,67 +62,65 @@ public final class KnockoutTest {
         return Models.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 {
-
+    @KOTest
+    public void modifyValueAssertChangeInModelOnEnum() throws Throwable {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class,
+                "Latitude: <input id='input' data-bind=\"value: 
choice\"></input>\n"
+            );
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.setChoice(Choice.A);
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             String v = getSetInput("input", null);
             assertEquals("A", v, "Value is really A: " + v);
 
             getSetInput("input", "B");
             triggerEvent("input", "change");
-
+        }).then((m) -> {
             assertEquals(Choice.B, m.getChoice(), "Enum property updated: " + 
m.getChoice());
-        } catch (Throwable t) {
-            throw t;
-        } finally {
+        }).then((data) -> {
+        }).finalize((data) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
 
-    @KOTest public void modifyRadioValueOnEnum() throws Throwable {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
+    @KOTest
+    public void modifyRadioValueOnEnum() throws Throwable {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
             <input id='i1' type="radio" name="choice" value="A" 
data-bind="checked: choice"></input>Right
             <input id='input' type="radio" name="choice" value="B" 
data-bind="checked: choice"></input>Never
-            """
-        );
-        try {
-
+            """);
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.setChoice(Choice.B);
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             assertFalse(isChecked("i1"), "B should be checked now");
             assertTrue(isChecked("input"), "B should be checked now");
 
             triggerEvent("i1", "click");
+        }).then((m) -> {
             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");
-
+        }).then((m) -> {
             assertEquals(Choice.B, m.getChoice(), "Enum property updated: " + 
m.getChoice());
-        } catch (Throwable t) {
-            throw t;
-        } finally {
+        }).finalize((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
     @KOTest public void modifyValueAssertChangeInModelOnDouble() throws 
Throwable {
-        Object exp = Utils.exposeHTML(KnockoutTest.class,
-            "Latitude: <input id='input' data-bind=\"value: 
latitude\"></input>\n"
-        );
-        try {
-
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class,
+                "Latitude: <input id='input' data-bind=\"value: 
latitude\"></input>\n"
+            );
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.setLatitude(50.5);
             m.applyBindings();
@@ -132,17 +130,17 @@ public final class KnockoutTest {
 
             getSetInput("input", "49.5");
             triggerEvent("input", "change");
-
+            return m;
+        }).then((m) -> {
             assertEquals(49.5, m.getLatitude(), "Double property updated: " + 
m.getLatitude());
-        } catch (Throwable t) {
-            throw t;
-        } finally {
+        }).finalize((data) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
-    @KOTest public void rawObject() throws Exception {
-        if (js == null) {
+    @KOTest
+    public void rawObject() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
             final BrwsrCtx ctx = newContext();
             Person p1 = Models.bind(new Person(), ctx);
             p1.setFirstName("Jarda");
@@ -156,41 +154,43 @@ public final class KnockoutTest {
             p2.setFirstName("Jirka");
             assertEquals(p2.getFirstName(), "Jirka", "First name updated");
 
-            js = new KnockoutModel();
+            var js = new KnockoutModel();
             js.getPeople().add(p1);
             js.getPeople().add(p2);
-        }
 
-        Person p1 = js.getPeople().get(0);
-        Person p2 = js.getPeople().get(1);
+            return js;
+        }).then((js) -> {
+            Person p1 = js.getPeople().get(0);
 
-        if (js.getPeople().size() == 2) {
-            if (!"Jirka".equals(p1.getFirstName())) {
-                throw new InterruptedException();
-            }
+            if (js.getPeople().size() == 2) {
+                if (!"Jirka".equals(p1.getFirstName())) {
+                    throw new InterruptedException();
+                }
 
-            assertEquals(p1.getFirstName(), "Jirka", "First name updated in 
original object");
+                assertEquals(p1.getFirstName(), "Jirka", "First name updated 
in original object");
 
-            p1.setFirstName("Ondra");
-            assertEquals(p1.getFirstName(), "Ondra", "1st 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");
+                js.getPeople().add(p1);
+            }
+        }).then((js) -> {
+            Person p2 = js.getPeople().get(1);
+            if (!"Ondra".equals(p2.getFirstName())) {
+                throw new InterruptedException();
+            }
+            assertEquals(p2.getFirstName(), "Ondra", "1st name updated in 
copied object");
+        }).start();
     }
 
-    @KOTest public void modifyComputedProperty() throws Throwable {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
+    @KOTest
+    public void modifyComputedProperty() throws Throwable {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
             Full name: <div data-bind='with:firstPerson'>
             <input id='input' data-bind="value: fullName"></input>
             </div>
-            """
-        );
-        try {
+            """);
             KnockoutModel m = new KnockoutModel();
             m.getPeople().add(new Person());
 
@@ -198,59 +198,58 @@ public final class KnockoutTest {
             m.getFirstPerson().setFirstName("Jarda");
             m.getFirstPerson().setLastName("Tulach");
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             String v = getSetInput("input", null);
             assertEquals("Jarda Tulach", v, "Value: " + v);
 
             getSetInput("input", "Mickey Mouse");
             triggerEvent("input", "change");
-
+        }).then((m) -> {
             assertEquals("Mickey", m.getFirstPerson().getFirstName(), "First 
name updated");
             assertEquals("Mouse", m.getFirstPerson().getLastName(), "Last name 
updated");
-        } catch (Throwable t) {
-            throw t;
-        } finally {
+        }).finalize((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
-    @KOTest public void modifyValueAssertChangeInModelOnBoolean() throws 
Throwable {
-        Object exp = Utils.exposeHTML(KnockoutTest.class,
-            "Latitude: <input id='input' data-bind=\"value: 
enabled\"></input>\n"
-        );
-        try {
-
+    @KOTest
+    public void modifyValueAssertChangeInModelOnBoolean() throws Throwable {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class,
+                "Latitude: <input id='input' data-bind=\"value: 
enabled\"></input>\n"
+            );
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.setEnabled(true);
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             String v = getSetInput("input", null);
             assertEquals("true", v, "Value is really true: " + v);
 
             getSetInput("input", "false");
             triggerEvent("input", "change");
-
+        }).then((m) -> {
             assertFalse(m.isEnabled(), "Boolean property updated: " + 
m.isEnabled());
-        } catch (Throwable t) {
-            throw t;
-        } finally {
+        }).then((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
-    @KOTest public void modifyValueAssertChangeInModel() throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-            <h1 data-bind="text: helloMessage">Loading Bck2Brwsr's Hello 
World...</h1>
-            Your name: <input id='input' data-bind="value: name"></input>
-            <button id="hello">Say Hello!</button>
-            """
-        );
-        try {
-
+    @KOTest
+    public void modifyValueAssertChangeInModel() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
+                <h1 data-bind="text: helloMessage">Loading Bck2Brwsr's Hello 
World...</h1>
+                Your name: <input id='input' data-bind="value: name"></input>
+                <button id="hello">Say Hello!</button>
+                """
+            );
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.setName("Kukuc");
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             String v = getSetInput("input", null);
             assertEquals("Kukuc", v, "Value is really kukuc: " + v);
 
@@ -258,20 +257,20 @@ public final class KnockoutTest {
             triggerEvent("input", "change");
 
             assertEquals("Jardo", m.getName(), "Name property updated: " + 
m.getName());
-        } finally {
+        }).finalize((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
     private static String getSetSelected(int index, Object value) throws 
Exception {
         String s = """
                    var index = arguments[0];
-                   var n = window.document.getElementById('input'); 
+                   var n = window.document.getElementById('input');
                     if (index >= 0) {
-                       n.options.selectedIndex = index; 
-                       ko.utils.triggerEvent(n, 'change'); 
-                   } 
-                    var op = n.options[n.selectedIndex]; 
+                       n.options.selectedIndex = index;
+                       ko.utils.triggerEvent(n, 'change');
+                   }
+                    var op = n.options[n.selectedIndex];
                    return op ? op.text : n.selectedIndex;
                    """;
         Object ret = Utils.executeScript(
@@ -293,36 +292,35 @@ public final class KnockoutTest {
     }
 
     @KOTest public void selectWorksOnModels() throws Exception {
-        if (js == null) {
+        PhaseExecutor.schedule(phases, () -> {
             Utils.exposeHTML(KnockoutTest.class, """
-                                                 <select id='input' 
data-bind="options: archetypes,
-                                                                        
optionsText: 'name',
-                                                                        value: 
archetype">
-                                                                   </select>
-                                                 """);
-
-            {
-                KnockoutModel km = new KnockoutModel();
-                km.getArchetypes().add(new ArchetypeData("ko4j", 
"org.netbeans.html", "0.8.3", "ko4j", "ko4j", null));
-                km.getArchetypes().add(new ArchetypeData("crud", 
"org.netbeans.html", "0.8.3", "crud", "crud", null));
-                km.getArchetypes().add(new ArchetypeData("3rd", 
"org.netbeans.html", "0.8.3", "3rd", "3rd", null));
-                js = Models.bind(km, newContext());
-            }
+            <select id='input' data-bind="options: archetypes,
+                                   optionsText: 'name',
+                                   value: archetype">
+                              </select>
+            """);
+
+            KnockoutModel km = new KnockoutModel();
+            km.getArchetypes().add(new ArchetypeData("ko4j", 
"org.netbeans.html", "0.8.3", "ko4j", "ko4j", null));
+            km.getArchetypes().add(new ArchetypeData("crud", 
"org.netbeans.html", "0.8.3", "crud", "crud", null));
+            km.getArchetypes().add(new ArchetypeData("3rd", 
"org.netbeans.html", "0.8.3", "3rd", "3rd", null));
+            var js = Models.bind(km, newContext());
             js.setArchetype(js.getArchetypes().get(1));
             js.applyBindings();
-
+            return js;
+        }).then((js) -> {
             String v = getSetSelected(-1, 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, "");
+        }).then((js) -> {
+            if (js.getArchetype() != js.getArchetypes().get(2)) {
+                throw new InterruptedException();
+            }
+        }).finalize((js) -> {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }).start();
     }
 
     @KOTest public void nestedObjectEqualsChange() throws Exception {
@@ -332,35 +330,40 @@ public final class KnockoutTest {
     @KOTest public void nestedObjectChange() throws Exception {
         nestedObjectEqualsChange(false);
     }
-    private  void nestedObjectEqualsChange(boolean preApply) throws Exception {
-        Utils.exposeHTML(KnockoutTest.class, """
-                                                         <div data-bind='with: 
archetype'>
-                                                             <input id='input' 
data-bind='value: groupId'></input>
-                                                         </div>
-                                             """);
+    private void nestedObjectEqualsChange(boolean preApply) throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            Utils.exposeHTML(KnockoutTest.class, """
+            <div data-bind='with: archetype'>
+                <input id='input' data-bind='value: groupId'></input>
+            </div>
+            """);
 
-        js = Models.bind(new KnockoutModel(), newContext());
-        if (preApply) {
+            var js = Models.bind(new KnockoutModel(), newContext());
+            if (preApply) {
+                js.applyBindings();
+            }
+            js.setArchetype(Models.bind(new ArchetypeData(), newContext()));
+            js.getArchetype().setGroupId("org.netbeans.html");
             js.applyBindings();
-        }
-        js.setArchetype(Models.bind(new ArchetypeData(), newContext()));
-        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, "");
+            return js;
+        }).then((js) -> {
+            String v = getSetInput("input", null);
+            assertEquals("org.netbeans.html", v, "groupId has been changed");
+        }).finalize((js) -> {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }).start();
     }
 
-    @KOTest public void modifyValueAssertAsyncChangeInModel() throws Exception 
{
-        if (js == null) {
+    @KOTest
+    public void modifyValueAssertAsyncChangeInModel() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
             Utils.exposeHTML(KnockoutTest.class, """
-                                                 <h1 data-bind="text: 
helloMessage">Loading Bck2Brwsr's Hello World...</h1>
-                                                 Your name: <input id='input' 
data-bind="value: name"></input>
-                                                 <button id="hello">Say 
Hello!</button>
-                                                 """);
+            <h1 data-bind="text: helloMessage">Loading Bck2Brwsr's Hello 
World...</h1>
+            Your name: <input id='input' data-bind="value: name"></input>
+            <button id="hello">Say Hello!</button>
+            """);
 
-            js = Models.bind(new KnockoutModel(), newContext());
+            var js = Models.bind(new KnockoutModel(), newContext());
             js.setName("Kukuc");
             js.applyBindings();
 
@@ -373,14 +376,15 @@ public final class KnockoutTest {
                     js.setName("Jardo");
                 }
             });
-        }
-
-        String v = getSetInput("input", null);
-        if (!"Jardo".equals(v)) {
-            throw new InterruptedException();
-        }
-
-        Utils.exposeHTML(KnockoutTest.class, "");
+            return js;
+        }).then((data) -> {
+            String v = getSetInput("input", null);
+            if (!"Jardo".equals(v)) {
+                throw new InterruptedException();
+            }
+        }).finalize((data) -> {
+            Utils.exposeHTML(KnockoutTest.class, "");
+        }).start();
     }
 
     @Model(className = "ConstantModel", targetId = "", builder = "assign", 
properties = {
@@ -459,8 +463,8 @@ public final class KnockoutTest {
     private static String getSetInput(String id, String value) throws 
Exception {
         String s = """
                    var value = arguments[0];
-                   var n = window.document.getElementById(arguments[1]); 
-                    if (value != null) n['value'] = value; 
+                   var n = window.document.getElementById(arguments[1]);
+                    if (value != null) n['value'] = value;
                     return n['value'];""";
         Object ret = Utils.executeScript(
             KnockoutTest.class,
@@ -471,7 +475,7 @@ public final class KnockoutTest {
 
     private static boolean isChecked(String id) throws Exception {
         String s = """
-                   var n = window.document.getElementById(arguments[0]); 
+                   var n = window.document.getElementById(arguments[0]);
                     return n['checked'];""";
         Object ret = Utils.executeScript(
             KnockoutTest.class,
@@ -488,45 +492,46 @@ public final class KnockoutTest {
         );
     }
 
-    @KOTest public void displayContentOfArray() throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-                                                          <ul id='ul' 
data-bind='foreach: results'>
-                                                            <li 
data-bind='text: $data, click: $root.call'/>
-                                                          </ul>
-                                                          """);
-        try {
+    @KOTest
+    public void displayContentOfArray() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
+            <ul id='ul' data-bind='foreach: results'>
+              <li data-bind='text: $data, click: $root.call'/>
+            </ul>
+            """);
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.getResults().add("Ahoj");
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             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");
+        }).then((m) -> {
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Two children now, but was " + cnt);
-
             triggerChildClick("ul", 1);
-
+        }).then((m) -> {
             assertEquals(1, m.getCallbackCount(), "One callback " + 
m.getCallbackCount());
             assertEquals("Hi", m.getName(), "We got callback from 2nd child " 
+ m.getName());
-        } finally {
+        }).finalize((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
     @KOTest public void displayContentOfAsyncArray() throws Exception {
-        if (js == null) {
+        PhaseExecutor.schedule(phases, () -> {
             Utils.exposeHTML(KnockoutTest.class, """
-                                                 <ul id='ul' 
data-bind='foreach: results'>
-                                                   <li data-bind='text: $data, 
click: $root.call'/>
-                                                 </ul>
-                                                 """);
-            js = Models.bind(new KnockoutModel(), newContext());
+            <ul id='ul' data-bind='foreach: results'>
+              <li data-bind='text: $data, click: $root.call'/>
+            </ul>
+            """);
+            var js = Models.bind(new KnockoutModel(), newContext());
             js.getResults().add("Ahoj");
             js.applyBindings();
-
+            return js;
+        }).then((js) -> {
             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 1, "One child, but was " + cnt);
 
@@ -536,188 +541,222 @@ public final class KnockoutTest {
                     js.getResults().add("Hi");
                 }
             });
-        }
-
-
-        int cnt = Utils.countChildren(KnockoutTest.class, "ul");
-        if (cnt != 2) {
-            throw new InterruptedException();
-        }
+        }).then((js) -> {
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            if (cnt != 2) {
+                throw new InterruptedException();
+            }
 
-        try {
             triggerChildClick("ul", 1);
-
+        }).then((js) -> {
             assertEquals(1, js.getCallbackCount(), "One callback " + 
js.getCallbackCount());
             assertEquals("Hi", js.getName(), "We got callback from 2nd child " 
+ js.getName());
-        } finally {
+        }).finalize((js) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
-    @KOTest public void displayContentOfComputedArray() throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-                                                          <ul id='ul' 
data-bind='foreach: bothNames'>
-                                                            <li 
data-bind='text: $data, click: $root.assignFirstName'/>
-                                                          </ul>
-                                                          """);
-        try {
+    @KOTest
+    public void displayContentOfComputedArray() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
+            <ul id='ul' data-bind='foreach: bothNames'>
+              <li data-bind='text: $data, click: $root.assignFirstName'/>
+            </ul>
+            """);
+
+
             Pair m = Models.bind(new Pair("First", "Last", null), 
newContext());
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Two children now, but was " + cnt);
 
             triggerChildClick("ul", 1);
-
+        }).then((m) -> {
             assertEquals("Last", m.getFirstName(), "We got callback from 2nd 
child " + m.getFirstName());
 
             m.setLastName("Verylast");
 
-            cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Two children now, but was " + cnt);
 
             triggerChildClick("ul", 1);
-
+        }).then((m) -> {
             assertEquals("Verylast", m.getFirstName(), "We got callback from 
2nd child " + m.getFirstName());
-
-        } finally {
-            Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).finalize((m) -> {
+           Utils.exposeHTML(KnockoutTest.class, "");
+        }).start();
     }
 
-    @KOTest public void displayContentOfComputedArrayOnASubpair() throws 
Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-                                                          <div 
data-bind='with: next'>
-                                                          <ul id='ul' 
data-bind='foreach: bothNames'>
-                                                            <li 
data-bind='text: $data, click: $root.assignFirstName'/>
-                                                          </ul></div>
-                                                          """);
-        try {
+    @KOTest
+    public void displayContentOfComputedArrayOnASubpair() throws Exception {
+        class ModelCtx {
+            final Pair m;
+            final BrwsrCtx ctx;
+
+            ModelCtx(Pair m, BrwsrCtx ctx) {
+                this.m = m;
+                this.ctx = ctx;
+            }
+        }
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
+            <div data-bind='with: next'>
+            <ul id='ul' data-bind='foreach: bothNames'>
+              <li data-bind='text: $data, click: $root.assignFirstName'/>
+            </ul></div>
+            """);
             final BrwsrCtx ctx = newContext();
             Pair m = Models.bind(new Pair(null, null, new Pair("First", 
"Last", null)), ctx);
             m.applyBindings();
-
+            return new ModelCtx(m, ctx);
+        }).then((data) -> {
             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Two children now, but was " + cnt);
 
             triggerChildClick("ul", 1);
+        }).then((data) -> {
+            assertEquals(PairModel.ctx, data.ctx, "Context remains the same");
 
-            assertEquals(PairModel.ctx, ctx, "Context remains the same");
-
-            assertEquals("Last", m.getFirstName(), "We got callback from 2nd 
child " + m.getFirstName());
-        } finally {
+            assertEquals("Last", data.m.getFirstName(), "We got callback from 
2nd child " + data.m.getFirstName());
+        }).finalize((data) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
-    @KOTest public void displayContentOfComputedArrayOnComputedASubpair() 
throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-                                                          <div 
data-bind='with: nextOne'>
-                                                          <ul id='ul' 
data-bind='foreach: bothNames'>
-                                                            <li 
data-bind='text: $data, click: $root.assignFirstName'/>
-                                                          </ul></div>
-                                                          """);
-        try {
+    @KOTest
+    public void displayContentOfComputedArrayOnComputedASubpair() throws 
Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
+            <div data-bind='with: nextOne'>
+            <ul id='ul' data-bind='foreach: bothNames'>
+              <li data-bind='text: $data, click: $root.assignFirstName'/>
+            </ul></div>
+            """);
             Pair m = Models.bind(new Pair(null, null, new Pair("First", 
"Last", null)), newContext());
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Two children now, but was " + cnt);
 
             triggerChildClick("ul", 1);
-
+        }).then((m) -> {
             assertEquals("Last", m.getFirstName(), "We got callback from 2nd 
child " + m.getFirstName());
-        } finally {
+        }).finalize((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
-    @KOTest public void checkBoxToBooleanBinding() throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class,
-            "<input type='checkbox' id='b' data-bind='checked: 
enabled'></input>\n"
-        );
-        try {
+    @KOTest
+    public void checkBoxToBooleanBinding() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class,
+                "<input type='checkbox' id='b' data-bind='checked: 
enabled'></input>\n"
+            );
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.applyBindings();
 
             assertFalse(m.isEnabled(), "Is disabled");
 
             triggerClick("b");
-
+            return m;
+        }).then((m) -> {
             assertTrue(m.isEnabled(), "Now the model is enabled");
-        } finally {
+        }).finalize((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
 
 
-    @KOTest public void displayContentOfDerivedArray() throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-                                                          <ul id='ul' 
data-bind='foreach: cmpResults'>
-                                                            <li><b 
data-bind='text: $data'></b></li>
-                                                          </ul>
-                                                          """);
-        try {
+    @KOTest
+    public void displayContentOfDerivedArray() throws Exception {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
+            <ul id='ul' data-bind='foreach: cmpResults'>
+              <li><b data-bind='text: $data'></b></li>
+            </ul>
+            """);
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.getResults().add("Ahoj");
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             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");
+        }).then((m) -> {
+            var cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Two children now, but was " + cnt);
-        } finally {
+        }).finalize((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
-    @KOTest public void displayContentOfArrayOfPeople() throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-                                                          <ul id='ul' 
data-bind='foreach: people'>
-                                                            <li 
data-bind='text: $data.firstName, click: $root.removePerson'></li>
-                                                          </ul>
-                                                          """);
-        try {
-            final BrwsrCtx c = newContext();
-            KnockoutModel m = Models.bind(new KnockoutModel(), c);
+    @KOTest
+    public void displayContentOfArrayOfPeople() throws Exception {
+        class Data {
+            final KnockoutModel m;
+            final BrwsrCtx c;
+            final Person first;
+
+            Data(KnockoutModel m, BrwsrCtx c, Person first) {
+                this.m = m;
+                this.c = c;
+                this.first = first;
+            }
+        }
+        PhaseExecutor.schedule(phases, () -> {
+                Object exp = Utils.exposeHTML(KnockoutTest.class, """
+            <ul id='ul' data-bind='foreach: people'>
+              <li data-bind='text: $data.firstName, click: 
$root.removePerson'></li>
+            </ul>
+            """);
+
+            var c = newContext();
+            var m = Models.bind(new KnockoutModel(), c);
 
-            final Person first = Models.bind(new Person(), c);
+            var first = Models.bind(new Person(), c);
             first.setFirstName("first");
             m.getPeople().add(first);
 
             m.applyBindings();
 
+            return new Data(m, c, first);
+        }).then((test) -> {
             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 1, "One child, but was " + cnt);
 
-            final Person second = Models.bind(new Person(), c);
+            final Person second = Models.bind(new Person(), test.c);
             second.setFirstName("second");
-            m.getPeople().add(second);
-
-            cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            test.m.getPeople().add(second);
+        }).then((test) -> {
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Two children now, but was " + cnt);
 
             triggerChildClick("ul", 1);
+        }).then((test) -> {
+            var m = test.m;
 
             assertEquals(1, m.getCallbackCount(), "One callback " + 
m.getCallbackCount());
 
-            cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            int 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");
+            test.first.setFirstName("changed");
 
             txt = childText("ul", 0);
             assertEquals("changed", txt, "Expecting 'changed': " + txt);
-        } finally {
+        }).finalize((test) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
     @ComputedProperty
@@ -754,39 +793,39 @@ public final class KnockoutTest {
     }
 
     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);
-
+        PhaseExecutor.schedule(phases, () -> {
+            KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
 
-        triggerChildClick("ul", 0);
+            final Person first = Models.bind(new Person(), newContext());
+            first.setFirstName("first");
+            first.setSex(Sex.MALE);
+            m.getPeople().add(first);
 
-        assertEquals(first.getSex(), Sex.FEMALE, "Transverted to female: " + 
first.getSex());
+            m.applyBindings();
+            int cnt = Utils.countChildren(KnockoutTest.class, "ul");
+            assertEquals(cnt, 1, "One child, but was " + cnt);
+            triggerChildClick("ul", 0);
+            return first;
+        }).then((first) -> {
+            assertEquals(first.getSex(), Sex.FEMALE, "Transverted to female: " 
+ first.getSex());
+        }).start();
     }
 
     @KOTest public void stringArrayModificationVisible() throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-                                                          <div>
-                                                          <ul id='ul' 
data-bind='foreach: results'>
-                                                            <li 
data-bind='text: $data'></li>
-                                                          </ul>
-                                                          </div>
-                                                          """);
-        try {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
+            <div>
+            <ul id='ul' data-bind='foreach: results'>
+              <li data-bind='text: $data'></li>
+            </ul>
+            </div>
+            """);
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.getResults().add("Ahoj");
             m.getResults().add("Hello");
             m.applyBindings();
-
+            return m;
+        }).then((data) -> {
             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Two children " + cnt);
 
@@ -795,30 +834,31 @@ public final class KnockoutTest {
             final int len = ((Object[])arr).length;
 
             assertEquals(len, 3, "Three elements in the array " + len);
-
+        }).then((m) -> {
             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 {
+        }).finalize((data) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
     @KOTest public void intArrayModificationVisible() throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-                                                          <div>
-                                                          <ul id='ul' 
data-bind='foreach: numbers'>
-                                                            <li 
data-bind='text: $data'></li>
-                                                          </ul>
-                                                          </div>
-                                                          """);
-        try {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
+            <div>
+            <ul id='ul' data-bind='foreach: numbers'>
+              <li data-bind='text: $data'></li>
+            </ul>
+            </div>
+            """);
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.getNumbers().add(1);
             m.getNumbers().add(31);
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Two children " + cnt);
 
@@ -827,31 +867,32 @@ public final class KnockoutTest {
             final int len = ((Object[])arr).length;
 
             assertEquals(len, 3, "Three elements in the array " + len);
-
+        }).then((m) -> {
             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 {
+        }).finalize((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
     @KOTest public void derivedIntArrayModificationVisible() throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-                                                          <div>
-                                                          <ul id='ul' 
data-bind='foreach: resultLengths'>
-                                                            <li 
data-bind='text: $data'></li>
-                                                          </ul>
-                                                          </div>
-                                                          """);
-        try {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
+            <div>
+            <ul id='ul' data-bind='foreach: resultLengths'>
+              <li data-bind='text: $data'></li>
+            </ul>
+            </div>
+            """);
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.getResults().add("Ahoj");
             m.getResults().add("Hello");
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 2, "Two children " + cnt);
 
@@ -860,29 +901,30 @@ public final class KnockoutTest {
             final int len = ((Object[])arr).length;
 
             assertEquals(len, 3, "Three elements in the array " + len);
-
+        }).then((m) -> {
             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 {
+        }).finalize((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
     @KOTest public void archetypeArrayModificationVisible() throws Exception {
-        Object exp = Utils.exposeHTML(KnockoutTest.class, """
-                                                          <div>
-                                                          <ul id='ul' 
data-bind='foreach: archetypes'>
-                                                            <li 
data-bind='text: artifactId'></li>
-                                                          </ul>
-                                                          </div>
-                                                          """);
-        try {
+        PhaseExecutor.schedule(phases, () -> {
+            Object exp = Utils.exposeHTML(KnockoutTest.class, """
+            <div>
+            <ul id='ul' data-bind='foreach: archetypes'>
+              <li data-bind='text: artifactId'></li>
+            </ul>
+            </div>
+            """);
             KnockoutModel m = Models.bind(new KnockoutModel(), newContext());
             m.applyBindings();
-
+            return m;
+        }).then((m) -> {
             int cnt = Utils.countChildren(KnockoutTest.class, "ul");
             assertEquals(cnt, 0, "No children " + cnt);
 
@@ -891,16 +933,16 @@ public final class KnockoutTest {
             final int len = ((Object[])arr).length;
 
             assertEquals(len, 1, "One element in the array " + len);
-
+        }).then((m) -> {
             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 {
+        }).finalize((m) -> {
             Utils.exposeHTML(KnockoutTest.class, "");
-        }
+        }).start();
     }
 
     @Function
diff --git 
a/json-tck/src/main/java/net/java/html/json/tests/ObtainAndComputeTest.java 
b/json-tck/src/main/java/net/java/html/json/tests/ObtainAndComputeTest.java
index a1b99c4..76e6e01 100644
--- a/json-tck/src/main/java/net/java/html/json/tests/ObtainAndComputeTest.java
+++ b/json-tck/src/main/java/net/java/html/json/tests/ObtainAndComputeTest.java
@@ -31,60 +31,58 @@ import static net.java.html.json.tests.Utils.assertEquals;
 import org.netbeans.html.json.tck.KOTest;
 
 public class ObtainAndComputeTest {
-    ObtainData m;
+    private PhaseExecutor[] phases = new PhaseExecutor[1];
 
     @KOTest
     public void obtainAndComputeTest() throws Throwable {
-        if (m == null) {
+        PhaseExecutor.schedule(phases, () -> {
             BrwsrCtx ctx = Utils.newContext(ObtainAndComputeTest.class);
-            Utils.exposeHTML(ObtainAndComputeTest.class,
-                "<input type=\"text\" data-bind=\"textInput: filter\">\n" +
-                "<ul id='list' data-bind=\"foreach: filteredUsers\">\n" +
-                "   <li><div data-bind=\"text: email\"></div></li>\n" +
-                "</ul>\n" +
-                "<button id='button' data-bind=\"click: 
nextUsers\">more...</button>"
-            );
-            String d = "{\n" +
-"    \"data\": [{\n" +
-"            \"id\": 1,\n" +
-"            \"email\": \"[email protected]\"\n" +
-"        }, {\n" +
-"            \"id\": 2,\n" +
-"            \"email\": \"[email protected]\"\n" +
-"        }, {\n" +
-"            \"id\": 3,\n" +
-"            \"email\": \"[email protected]\"\n" +
-"        }, {\n" +
-"            \"email\": \"[email protected]\"\n" +
-"        }, {\n" +
-"            \"email\": \"[email protected]\"\n" +
-"        }, {\n" +
-"            \"id\": 6,\n" +
-"            \"email\": \"[email protected]\"\n" +
-"        }]\n" +
-"}\n" +
-"";
+            Utils.exposeHTML(ObtainAndComputeTest.class, """
+            <input type="text" data-bind="textInput: filter">
+            <ul id='list' data-bind="foreach: filteredUsers">
+               <li><div data-bind="text: email"></div></li>
+            </ul>
+            <button id='button' data-bind="click: 
nextUsers">more...</button>""");
+            String d = """
+            {
+                "data": [{
+                        "id": 1,
+                        "email": "[email protected]"
+                    }, {
+                        "id": 2,
+                        "email": "[email protected]"
+                    }, {
+                        "id": 3,
+                        "email": "[email protected]"
+                    }, {
+                        "email": "[email protected]"
+                    }, {
+                        "email": "[email protected]"
+                    }, {
+                        "id": 6,
+                        "email": "[email protected]"
+                    }]
+            }
+            """;
             String url = Utils.prepareURL(ObtainAndComputeTest.class, d, 
"application/json");
 
-            m = Models.bind(new ObtainData("holt", url, false), ctx);
+            var m = Models.bind(new ObtainData("holt", url, false), ctx);
             m.applyBindings();
+            return m;
+        }).then((m) -> {
             int cnt = Utils.countChildren(ObtainAndComputeTest.class, "list");
             assertEquals(cnt, 0, "No filtered users so far: " + cnt);
             Utils.scheduleClick(ObtainAndComputeTest.class, "button", 100);
-        }
-
-        if (!m.isGotReply()) {
-            throw new InterruptedException();
-        }
-        
-        try {
+        }).then((m) -> {
+            Utils.assertTrue(m.isGotReply(), "Expecting got reply: " + m);
+        }).then((m) -> {
             assertEquals(6, m.getUsers().size(), "Expecting some users: " + m);
             assertEquals(1, m.getFilteredUsers().size(), "Only one holt 
matching filter: " + m);
             int cnt = Utils.countChildren(ObtainAndComputeTest.class, "list");
             assertEquals(cnt, 1, "Also one user: " + cnt);
-        } finally {
+        }).finalize((m) -> {
             Utils.exposeHTML(ObtainAndComputeTest.class, "");
-        }
+        }).start();
     }
 
     //
@@ -130,7 +128,6 @@ public class ObtainAndComputeTest {
         )
         public static class UserVMD {
         }
-
     }
 
 }
diff --git a/json-tck/src/main/java/net/java/html/json/tests/PhaseExecutor.java 
b/json-tck/src/main/java/net/java/html/json/tests/PhaseExecutor.java
new file mode 100644
index 0000000..c401d14
--- /dev/null
+++ b/json-tck/src/main/java/net/java/html/json/tests/PhaseExecutor.java
@@ -0,0 +1,111 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package net.java.html.json.tests;
+
+import java.util.List;
+import net.java.html.json.Models;
+import static net.java.html.json.tests.Utils.fail;
+
+final class PhaseExecutor<T> {
+    private final T data;
+    private int at = -1;
+    private int retry = 10;
+    private List<Action<T>> tasks = Models.asList();
+    private List<Action<T>> clean = Models.asList();
+
+
+    private PhaseExecutor(T data) {
+        this.data = data;
+    }
+    // check whether all PhaseExecutor instances are started:
+    /*
+        this.alloc = new RuntimeException("Allocated for " + data);
+        if (prev != null && prev.at == -1) {
+            throw prev.alloc;
+        }
+        prev = this;
+    }
+    private final RuntimeException alloc;
+    private static PhaseExecutor prev;
+    */
+    
+    static <T> PhaseExecutor<T> schedule(PhaseExecutor[] phases, Init<T> data) 
throws Exception {
+        if (phases[0] == null) {
+            phases[0] = new PhaseExecutor<T>(data.initialize());
+        } else {
+            if (phases[0].at == -1) {
+                fail("A PhaseExecutor hasn't been started! " + phases[0]);
+            }
+        }
+        return phases[0];
+    }
+
+    PhaseExecutor<T> then(Action<T> a) {
+        if (at == -1) {
+            tasks.add(a);
+        }
+        return this;
+    }
+
+    PhaseExecutor<T> finalize(Action<T> a) {
+        if (at == -1) {
+            clean.add(a);
+        }
+        return this;
+    }
+
+    void start() throws Exception {
+        if (at < 0) {
+            at = 0;
+        }
+
+        while (at < tasks.size()) {
+            Action a = tasks.get(at);
+            try {
+                a.run(data);
+            } catch (Exception | Error ex) {
+                if (retry-- == 0) {
+                    cleanUp();
+                    throw ex;
+                } else {
+                    throw (InterruptedException) new 
InterruptedException().initCause(ex);
+                }
+            }
+            at++;
+            retry = 10;
+        }
+        cleanUp();
+    }
+
+    private final void cleanUp() throws Exception {
+        for (Action a : clean) {
+            a.run(data);
+        }
+    }
+
+    @FunctionalInterface
+    public interface Init<T> {
+        public T initialize() throws Exception;
+    }
+
+    @FunctionalInterface
+    public interface Action<T> {
+        public void run(T data) throws Exception;
+    }
+}
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
index afcf9fe..dc657c8 100644
--- 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
@@ -135,7 +135,7 @@ public final class Utils {
         return ((Number) executeScript(caller, """
             var e = window.document.getElementById(arguments[0]);
             if (typeof e === 'undefined') return -2;
-             var list = e.childNodes;
+            var list = e.childNodes;
             var cnt = 0;
             for (var i = 0; i < list.length; i++) {
               if (list[i].nodeType == 1) cnt++;

---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists

Reply via email to