Title: [250307] trunk/Tools
Revision
250307
Author
zhifei_f...@apple.com
Date
2019-09-24 10:48:20 -0700 (Tue, 24 Sep 2019)

Log Message

Add tests for Ref.js.
https://bugs.webkit.org/show_bug.cgi?id=201778.

Reviewed by Jonathan Bedard.

* resultsdbpy/resultsdbpy/view/static/library/js/Ref.js:
(applyStateDiff): null, 0, false are all valid state
(Ref.prototype.setState): We should do nothing for undefined stateDiff
* resultsdbpy/resultsdbpy/view/static/library/js/Test.js: Added.
(AssertFailedError):
(Expect): Perform assertions during unit tests.
(Expect.prototype.isType):
(Expect.prototype.equalToValue):
(Expect.prototype.equalToHtmlWithoutRef):
(Expect.prototype.notEqualToValue):
(Expect.prototype.greaterThan):
(Expect.prototype.greaterThanOrEqualTo):
(Expect.prototype.lessThan):
(Expect.prototype.lessThanOrEqualTo):
(TestSuite): Common TestSuite class for user to extend.
(TestSuite.prototype.expect):
(TestSuite.prototype.sleep): Test will sleep for certain ms
(TestSuite.prototype.waitForSignal): Wait until we receive a certain signal with timeout
(TestSuite.prototype.waitForRefMounted): Wait until we receive ref object's onElementMount signal with timeout
(TestSuite.prototype.waitForRefUnmounted): Wait until we receive ref object's onElementUnmount signal with timeout
(TestSuite.prototype.waitForStateUpdated):Wait until we receive ref object's onStateUpdate signal with timeout
(TestSuite.prototype.async.setup): Common interface for setup a test suite
(TestSuite.prototype.async.clearUp): Common interface for clear up a test suite
(getTestFucntionNames): Collect all the test method of a test instance.
(TestResult):
(TestResult.prototype.catchException):
(async.getTestResult): Run the test and generate a TestResult object
(TestController):
(TestController.prototype.addResultHandler): Test controller will send notification of a test result to result handler
(TestController.prototype.addSetupArgs): This gives some additional args for the common setup method for each test class,
it is the best place to setup something like a root element, a fake data source, etc
(TestController.prototype.collect): This method used for collect the test classes.
(TestController.prototype.async.collectFile): It will import the file as a module dynamicly and collect all the test classes that module export
(TestController.prototype.async.runTest): It will run a test method of a test class
(TestController.prototype.async.run): It will run all test or a test class or a test method
* resultsdbpy/resultsdbpy/view/static/library/js/components/TestComponents.js: components for test app.
* resultsdbpy/resultsdbpy/view/static/library/js/test/RefTest.js: Ref.js test cases.
* resultsdbpy/resultsdbpy/view/static/library/js/test/index.html: Test app entry.

Modified Paths

Added Paths

Diff

Modified: trunk/Tools/ChangeLog (250306 => 250307)


--- trunk/Tools/ChangeLog	2019-09-24 17:25:05 UTC (rev 250306)
+++ trunk/Tools/ChangeLog	2019-09-24 17:48:20 UTC (rev 250307)
@@ -1,3 +1,49 @@
+2019-09-24  Zhifei Fang  <zhifei_f...@apple.com>
+
+        Add tests for Ref.js.
+        https://bugs.webkit.org/show_bug.cgi?id=201778.
+
+        Reviewed by Jonathan Bedard.
+
+        * resultsdbpy/resultsdbpy/view/static/library/js/Ref.js:
+        (applyStateDiff): null, 0, false are all valid state
+        (Ref.prototype.setState): We should do nothing for undefined stateDiff
+        * resultsdbpy/resultsdbpy/view/static/library/js/Test.js: Added.
+        (AssertFailedError):
+        (Expect): Perform assertions during unit tests.
+        (Expect.prototype.isType):
+        (Expect.prototype.equalToValue):
+        (Expect.prototype.equalToHtmlWithoutRef):
+        (Expect.prototype.notEqualToValue):
+        (Expect.prototype.greaterThan):
+        (Expect.prototype.greaterThanOrEqualTo):
+        (Expect.prototype.lessThan):
+        (Expect.prototype.lessThanOrEqualTo):
+        (TestSuite): Common TestSuite class for user to extend. 
+        (TestSuite.prototype.expect):
+        (TestSuite.prototype.sleep): Test will sleep for certain ms
+        (TestSuite.prototype.waitForSignal): Wait until we receive a certain signal with timeout
+        (TestSuite.prototype.waitForRefMounted): Wait until we receive ref object's onElementMount signal with timeout
+        (TestSuite.prototype.waitForRefUnmounted): Wait until we receive ref object's onElementUnmount signal with timeout
+        (TestSuite.prototype.waitForStateUpdated):Wait until we receive ref object's onStateUpdate signal with timeout
+        (TestSuite.prototype.async.setup): Common interface for setup a test suite
+        (TestSuite.prototype.async.clearUp): Common interface for clear up a test suite
+        (getTestFucntionNames): Collect all the test method of a test instance.
+        (TestResult):
+        (TestResult.prototype.catchException):
+        (async.getTestResult): Run the test and generate a TestResult object
+        (TestController):
+        (TestController.prototype.addResultHandler): Test controller will send notification of a test result to result handler
+        (TestController.prototype.addSetupArgs): This gives some additional args for the common setup method for each test class, 
+        it is the best place to setup something like a root element, a fake data source, etc
+        (TestController.prototype.collect): This method used for collect the test classes.
+        (TestController.prototype.async.collectFile): It will import the file as a module dynamicly and collect all the test classes that module export
+        (TestController.prototype.async.runTest): It will run a test method of a test class
+        (TestController.prototype.async.run): It will run all test or a test class or a test method
+        * resultsdbpy/resultsdbpy/view/static/library/js/components/TestComponents.js: components for test app.
+        * resultsdbpy/resultsdbpy/view/static/library/js/test/RefTest.js: Ref.js test cases.
+        * resultsdbpy/resultsdbpy/view/static/library/js/test/index.html: Test app entry.
+
 2019-09-24  Wenson Hsieh  <wenson_hs...@apple.com>
 
         FocusPreservationTests.ChangingFocusedNodeResetsFocusPreservationState triggers a debug assertion

Modified: trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/Ref.js (250306 => 250307)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/Ref.js	2019-09-24 17:25:05 UTC (rev 250306)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/Ref.js	2019-09-24 17:48:20 UTC (rev 250307)
@@ -217,7 +217,7 @@
 }
 
 function applyStateDiff(state, diff) {
-    if (!diff)
+    if (diff === undefined)
         return state;
     if (!isMergeableState(diff))
         return diff;
@@ -275,6 +275,8 @@
         return this.key;
     }
     setState(stateDiff) {
+        if (stateDiff === undefined)
+            return;
         if (TRACE_STATE)
             this.stateTracer.push(stateDiff);
         else

Added: trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/Test.js (0 => 250307)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/Test.js	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/Test.js	2019-09-24 17:48:20 UTC (rev 250307)
@@ -0,0 +1,241 @@
+import {EventStream} from './Ref.js';
+
+class AssertFailedError extends Error {
+    constructor(msg) {
+        super(msg)
+    }
+}
+
+function stripRef(html) {
+    return html.replace(/ref="[\w\d\-]+"/g, "")
+}
+
+class Expect {
+    constructor(valueA) {
+        this.valueA = valueA;
+        this.valueB = null;
+        return this;
+    }
+
+    isType(type) {
+        if (type === null) {
+            if (this.valueA !== null)
+                throw new AssertFailedError(`${this.valueA} should be null`);
+        } else if (Number.isNaN(type) || type === "NaN") {
+            if (!Number.isNaN(this.valueA))
+                throw new AssertFailedError(`${this.valueA} should be NaN`);    
+        } else if (type === "Array" || type === "array" || type === Array) {
+            if (!Array.isArray(this.valueA))
+                throw new AssertFailedError(`${this.valueA} should be an Array`);
+        } else if (typeof this.valueA !== type && false === this.valueA instanceof type)
+            throw new AssertFailedError(`${this.valueA} should be type ${type}`);
+        return this;
+    }
+
+    equalToValue(valueB) {
+        if (this.valueA !== valueB)
+            throw new AssertFailedError(`${this.valueA} should equal to ${valueB}`);
+    }
+
+    equalToHtmlWithoutRef(html) {
+        return this.equalToValue(stripRef(this.valueA), stripRef(html));
+    }
+
+    equalToArray(array, compare = (x, y) => expect(x).equalToValue(y)) {
+        expect(this.valueA).isType("Array");
+        expect(array).isType("Array");
+        expect(this.valueA.length).equalToValue(array.length);
+        for (let i = 0; i < this.valueA.length; i++) {
+            compare(this.valueA[i], array[i]);
+        }
+    }
+
+    notEqualToValue(valueB) {
+        if (this.valueA === valueB)
+            throw new AssertFailedError(`${this.valueA} should not equal to ${valueB}`);
+    }
+
+    greaterThan(valueB) {
+        if (this.valueA <= valueB)
+            throw new AssertFailedError(`${this.valueA} should greater than ${valueB}`);
+    }
+
+    greaterThanOrEqualTo(valueB) {
+        if (this.valueA < valueB)
+            throw new AssertFailedError(`${this.valueA} should greater than or equal to ${valueB}`);
+    }
+
+    lessThan(valueB) {
+        if (this.valueA >= valueB)
+            throw new AssertFailedError(`${this.valueA} should less than ${valueB}`);
+    }
+
+    lessThanOrEqualTo(valueB) {
+        if (this.valueA > valueB)
+            throw new AssertFailedError(`${this.valueA} should less than or equal to ${valueB}`);
+    }
+}
+
+function expect(value) {
+    return new Expect(value);
+}
+
+class TestSuite {
+    expect(value) {
+        return expect(value);
+    }
+
+    sleep(milliseconds) {
+        return new Promise((resolve) => {
+            setTimeout(resolve, milliseconds);
+        });
+    }
+
+    waitForSignal(singal, name, timeout=1000) {
+        return new Promise((resolve, reject) => {
+            const handler = () => {
+                clearTimeout(timeoutHandler);
+                resolve();
+                singal.removeListener(handler);
+            };
+            const timeoutHandler = setTimeout(() => {
+                singal.removeListener(handler);
+                reject(new AssertFailedError(`Cannot get the ${name} signal after ${timeout} ms`));
+            }, timeout);
+            singal.addListener(handler);
+        });
+    }
+
+    waitForRefMounted(ref, timeout=1000) {
+        return this.waitForSignal(ref.onElementMount, "mount", timeout);
+    }
+
+    waitForRefUnmounted(ref, timeout=1000) {
+        return this.waitForSignal(ref.onElementUnmount, "unmount", timeout);
+    }
+
+    waitForStateUpdated(ref, timeout=1000) {
+        return this.waitForSignal(ref.onStateUpdate, "state update", timeout);
+    }
+
+    async setup() {}
+    async clearUp() {}
+}
+
+
+function getTestFucntionNames(testObj) {
+    const fixedMethods = new Set(Object.getOwnPropertyNames(TestSuite.prototype));
+    const testObjMethods = Object.getOwnPropertyNames(testObj.constructor.prototype);
+    const testMethods = [];
+    for (let method of testObjMethods) {
+        if (!fixedMethods.has(method))
+            testMethods.push(method);
+    }
+    return testMethods;
+}
+
+const TEST_RESULT_TYPE = Object.freeze({
+    Success: Symbol("Success"),
+    Error: Symbol("Error"),
+    Failed: Symbol("Failed"),
+});
+
+class TestResult {
+    constructor(className, fnName) {
+        this.className = className;
+        this.fnName = fnName;
+        this.exception = null;
+        this.type = TEST_RESULT_TYPE.Success;
+    }
+    
+    catchException(e) {
+        this.exception = e;
+        console.error(e);
+        if (e instanceof AssertFailedError)
+            this.type = TEST_RESULT_TYPE.Failed;
+        else
+            this.type = TEST_RESULT_TYPE.Error;
+    }
+}
+
+async function getTestResult(obj, fnName, args = []) {
+    const result = new TestResult(obj.constructor.name, fnName);
+    try {
+        await obj[fnName](...args);
+    } catch (e) {
+        result.catchException(e);
+    }
+    return result;
+}
+
+class TestController {
+    constructor(setupArgs) {
+        this.allTests = {}
+        this.resultsEs = new EventStream();
+        this.setupArgs = Array.isArray(setupArgs) ? setupArgs : [];
+    }
+    
+    addResultHandler(handler) {
+        this.resultsEs.action(handler);
+    }
+    
+    addSetupArgs(args) {
+        this.setupArgs = this.setupArgs.concat(args);
+    }
+
+    collect(testSuiteClass) {
+        const testInstance = new testSuiteClass();
+        const testName = testInstance.constructor.name;
+        if (testName in this.allTests) {
+            throw new Error(`${testName} has already been collected`);
+        }
+        this.allTests[testName] = testInstance;
+    }
+    
+    async collectFile(filePath) {
+        const testModule = await import(filePath);
+        Object.keys(testModule).forEach(className => this.collect(testModule[className]));
+    }
+
+    async runTest(testName, fnName) {
+        let haveError = false;
+        const testInstance = this.allTests[testName];
+        const testMethods = getTestFucntionNames(testInstance);
+        let result = await getTestResult(testInstance, "setup", this.setupArgs);
+        this.resultsEs.add(result);
+        if (result.type === TEST_RESULT_TYPE.Success) {
+            for (let testMethodName of testMethods) {
+                if (fnName && fnName !== testMethodName) 
+                    return;
+                result = await getTestResult(testInstance, testMethodName);
+                this.resultsEs.add(result);
+                if (result.type !== TEST_RESULT_TYPE.Success)
+                    haveError = true;
+            }
+        } else 
+            haveError = true;
+        result = await getTestResult(testInstance, "clearUp");
+        this.resultsEs.add(result);
+        if (result.type !== TEST_RESULT_TYPE.Success)
+            haveError = true;
+        return haveError;
+    }
+
+    async run(testSuiteClassName, testFnName) {
+        let haveError = false;
+        if (testSuiteClassName)
+            haveError = await this.runTest(testSuiteClassName, testFnName);
+        else {
+            for(let testSuiteClassName of Object.keys(this.allTests))
+                haveError |= await this.runTest(testSuiteClassName);
+        }
+        const finalResult = new TestResult("", "");
+        if (!haveError)
+            finalResult.type = TEST_RESULT_TYPE.Success;
+        else
+            finalResult.type = TEST_RESULT_TYPE.Failed;
+        this.resultsEs.add(finalResult);
+    }
+}
+
+export {TestSuite, TestController, TEST_RESULT_TYPE}

Added: trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/components/TestComponents.js (0 => 250307)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/components/TestComponents.js	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/components/TestComponents.js	2019-09-24 17:48:20 UTC (rev 250307)
@@ -0,0 +1,85 @@
+import {DOM, REF} from "../Ref.js"
+import {TestController, TEST_RESULT_TYPE} from "../Test.js"
+
+function TestApp(testController) {
+    return (
+        `<div class="row content">
+            <div class="col-6">
+                ${TestResultConsole(testController)}
+            </div>
+            <div class="col-6">
+                ${TestRenderArea(testController)}
+            </div>
+        </div>`
+    );
+}
+
+function TestResult(testResult) {
+    let statusClass = "success";
+    let description = testResult.className ? "PASS" : "ALL PASS";
+    switch (testResult.type) {
+        case TEST_RESULT_TYPE.Failed:
+            statusClass = "failed";
+            description = "failed"
+        break;
+        case TEST_RESULT_TYPE.Error:
+            statusClass = "error";
+            description = "raise an error";
+        break;
+    }
+    return (
+        `<div class="text">
+         ${testResult.className ? "" : "<hr>"}
+            <div class="text block">
+                <a href=""
+            </div>
+            <div class="text block">
+                <a href=""
+            </div>
+            <div class="text block ${statusClass}">
+                ${description}
+            </div>
+            ${testResult.exception ? `
+                <div class="text ${statusClass}">
+                    <pre>${testResult.exception.message}\n${testResult.exception.stack}</pre>
+                </div>` : ""}
+        </div>`
+    );
+}
+
+function TestResultConsole(testController) {
+    const ref = REF.createRef({
+        onStateUpdate: (element, stateDiff) => {
+            if (stateDiff.addTestResult) {
+                DOM.append(element, TestResult(stateDiff.addTestResult));
+                if (!stateDiff.addTestResult.className) {
+                    let final_res = "ALL PASS";
+                    if (stateDiff.addTestResult.type !== TEST_RESULT_TYPE.Success)
+                        final_res = "FAILED";
+                    document.title = `${final_res}`;
+                }
+            }
+        }
+    });
+    testController.addResultHandler(testResult => ref.setState({addTestResult: testResult}));
+    return `<div ref="${ref}"></div>`;
+}
+
+
+function TestRenderArea(testController) {
+    const ref = REF.createRef({
+        onElementMount: (element) => {
+            testController.addSetupArgs([element]);
+            const url = "" URL(window.location);
+            const searchParam = new URLSearchParams(url.search);
+            const className = searchParam.get("className");
+            if (className)
+                document.title = className;
+            const fnName = searchParam.get("fnName");
+            testController.run(className, fnName)
+        } 
+    });
+    return `<div ref="${ref}"></div>`;
+}
+
+export {TestApp};

Added: trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/test/RefTest.js (0 => 250307)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/test/RefTest.js	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/test/RefTest.js	2019-09-24 17:48:20 UTC (rev 250307)
@@ -0,0 +1,302 @@
+import {TestSuite} from '../Test.js';
+import {REF, DOM, diff, FP, EventStream} from '../Ref.js';
+
+class DiffTest extends TestSuite {
+    testArrayDiff() {
+        let removed = [];
+        let newArray = [];
+        diff([1, 2, 3], [4, 5, 6], item => removed.push(item), item => newArray.push(item));
+        this.expect(removed).equalToArray([1, 2, 3]);
+        this.expect(newArray).equalToArray([4, 5, 6]);
+
+        removed = [];
+        newArray = [];
+        diff([1, 2, 3], [2, 3, 4], item => removed.push(item), item => newArray.push(item));
+        this.expect(removed).equalToArray([1]);
+        this.expect(newArray).equalToArray([2, 3, 4]);
+
+        removed = [];
+        newArray = [];
+        diff([2], [2, 3, 4], item => removed.push(item), item => newArray.push(item));
+        this.expect(removed).equalToArray([]);
+        this.expect(newArray).equalToArray([2, 3, 4]);
+
+        removed = [];
+        newArray = [];
+        diff([], [2, 3, 4], item => removed.push(item), item => newArray.push(item));
+        this.expect(removed).equalToArray([]);
+        this.expect(newArray).equalToArray([2, 3, 4]);
+        
+        removed = [];
+        newArray = [];
+        diff([4, 3, 2], [2, 3, 4], item => removed.push(item), item => newArray.push(item));
+        this.expect(removed).equalToArray([]);
+        this.expect(newArray).equalToArray([2, 3, 4]);
+        
+        removed = [];
+        newArray = [];
+        diff([4, 3, 2, 5, 6], [2, 3, 4], item => removed.push(item), item => newArray.push(item));
+        this.expect(removed).equalToArray([5, 6]);
+        this.expect(newArray).equalToArray([2, 3, 4]);
+        
+        removed = [];
+        newArray = [];
+        diff([], [], item => removed.push(item), item => newArray.push(item));
+        this.expect(removed).equalToArray([]);
+        this.expect(newArray).equalToArray([]);
+    }
+}
+
+class DomTest extends TestSuite {
+    setup(rootElement) {
+        this.rootElement = rootElement;
+    }
+
+    testInject() {
+        this.rootElement.innerHTML = "";
+        const injector = `<div id="${Math.random()}"></div>`;
+        DOM.inject(this.rootElement, injector);
+        this.expect(this.rootElement.innerHTML).equalToValue(injector);
+        this.rootElement.innerHTML = "";
+    }
+
+    testBefore() {
+        let initial = '<div id="1"></div><div id="2"></div><div id="3"></div>'; 
+        this.rootElement.innerHTML = initial;
+        let injector = `<div id="${Math.random()}"></div>`;
+        DOM.before(this.rootElement.children[0], injector);
+        this.expect(this.rootElement.innerHTML).equalToValue(`${injector}${initial}`);
+        this.rootElement.innerHTML = initial;
+        DOM.before(this.rootElement.children[1], injector);
+        this.expect(this.rootElement.innerHTML).equalToValue(`<div id="1"></div>${injector}<div id="2"></div><div id="3"></div>`);
+        this.rootElement.innerHTML = initial;
+        DOM.before(this.rootElement.children[2], injector);
+        this.expect(this.rootElement.innerHTML).equalToValue(`<div id="1"></div><div id="2"></div>${injector}<div id="3"></div>`);
+    }
+
+    testAfter() {
+        let initial = '<div id="1"></div><div id="2"></div><div id="3"></div>'; 
+        this.rootElement.innerHTML = initial;
+        let injector = `<div id="${Math.random()}"></div>`;
+        DOM.after(this.rootElement.children[0], injector);
+        this.expect(this.rootElement.innerHTML).equalToValue(`<div id="1"></div>${injector}<div id="2"></div><div id="3"></div>`);
+        this.rootElement.innerHTML = initial;
+        DOM.after(this.rootElement.children[1], injector);
+        this.expect(this.rootElement.innerHTML).equalToValue(`<div id="1"></div><div id="2"></div>${injector}<div id="3"></div>`);
+        this.rootElement.innerHTML = initial;
+        DOM.after(this.rootElement.children[2], injector);
+        this.expect(this.rootElement.innerHTML).equalToValue(`${initial}${injector}`);
+    }
+
+    testPrepend() {
+        let initial = '<div id="1"></div><div id="2"></div><div id="3"></div>'; 
+        this.rootElement.innerHTML = initial;
+        let injector = `<div id="${Math.random()}"></div>`;
+        DOM.prepend(this.rootElement, injector);
+        this.expect(this.rootElement.innerHTML).equalToValue(`${injector}${initial}`);
+    }
+
+    testAppend() {
+        let initial = '<div id="1"></div><div id="2"></div><div id="3"></div>'; 
+        this.rootElement.innerHTML = initial;
+        let injector = `<div id="${Math.random()}"></div>`;
+        DOM.append(this.rootElement, injector);
+        this.expect(this.rootElement.innerHTML).equalToValue(`${initial}${injector}`);
+    }
+    
+    testReplace() {
+        let initial = '<div id="1"></div><div id="2"></div><div id="3"></div>'; 
+        this.rootElement.innerHTML = initial;
+        let injector = `<div id="${Math.random()}"></div>`;
+        DOM.replace(this.rootElement.children[0], injector);
+        this.expect(this.rootElement.innerHTML).equalToValue(`${injector}<div id="2"></div><div id="3"></div>`);
+    }
+    
+    testRemove() {
+        let initial = '<div id="1"></div><div id="2"></div><div id="3"></div>'; 
+        this.rootElement.innerHTML = initial;
+        DOM.remove(this.rootElement.children[0]);
+        this.expect(this.rootElement.innerHTML).equalToValue(`<div id="2"></div><div id="3"></div>`);
+    }
+}
+
+class RefTest extends TestSuite {
+    setup(rootElement) {
+        this.rootElement = rootElement;
+    }
+
+    async testOnElementMount() {
+        let triggered = false;
+        let currentRef = null;
+        const creatAComponent = () => {
+            let ref = REF.createRef({
+                onElementMount: (element) => {
+                    triggered = true;
+                }
+            });
+            currentRef = ref;
+            return `<div ref="${ref}"></div>`;
+        };
+            
+        const firstComp = creatAComponent();
+        DOM.inject(this.rootElement, firstComp);
+        await this.waitForRefMounted(currentRef);
+        this.expect(triggered).equalToValue(true);
+        this.expect(currentRef.element.outerHTML).equalToValue(firstComp);
+
+        triggered = false;
+        const secondComp = creatAComponent();
+        DOM.replace(this.rootElement.children[0], secondComp);
+        await this.waitForRefMounted(currentRef);
+        this.expect(triggered).equalToValue(true);
+        this.expect(currentRef.element.outerHTML).equalToValue(secondComp);
+    }
+
+    async testOnElementUnmount() {
+        let triggered = false;
+        let currentRef = null;
+        const creatAComponent = () => {
+            let ref = REF.createRef({
+                onElementUnmount: (element) => {
+                    triggered = true;
+                }
+            });
+            currentRef = ref;
+            return `<div ref="${ref}"></div>`;
+        };
+
+        const firstComp = creatAComponent();
+        DOM.inject(this.rootElement, firstComp);
+        DOM.replace(this.rootElement.children[0], "<div></div>");
+        await this.waitForRefUnmounted(currentRef);
+        this.expect(triggered).equalToValue(true);
+        this.expect(currentRef.element.parentElement).equalToValue(null);
+
+        triggered = false;
+        DOM.inject(this.rootElement, firstComp);
+        DOM.remove(this.rootElement.children[0]);
+        let expectedE = null;
+        try {
+            await this.waitForRefUnmounted(currentRef);
+        } catch (e) {
+            // destoried ref won't be triggered
+            this.expect(triggered).equalToValue(false);
+            expectedE = e;
+        }
+        this.expect(expectedE).notEqualToValue(null);
+        
+        triggered = false;
+        const secondComp = creatAComponent();
+        DOM.inject(this.rootElement, secondComp);
+        DOM.remove(this.rootElement.children[0]);
+        await this.waitForRefUnmounted(currentRef);
+        this.expect(triggered).equalToValue(true);
+        this.expect(currentRef.element.parentElement).equalToValue(null);
+    }
+    
+    async testOnComplexStateUpdate() {
+        let verifier = null;
+        let triggered = false;
+        let expectedE = null;
+        let initialState = {
+            initialState: Math.random()
+        };
+        let ref = REF.createRef({
+            onStateUpdate:(element, stateDiff, state) => {
+                if (verifier) verifier(element, stateDiff, state);
+            }
+        });
+        ref.setState(initialState);
+        try {
+            await this.waitForStateUpdated(ref);
+        } catch (e) {
+            expectedE = e;
+        }
+        // before element mount, we don't trigger state update callback, we just save the state
+        this.expect(triggered).equalToValue(false);
+        this.expect(expectedE).notEqualToValue(null);
+        this.expect(ref.state.initialState).equalToValue(initialState.initialState);
+        // Each time it will create a new state object
+        this.expect(ref.state).notEqualToValue(initialState);
+        
+        DOM.inject(this.rootElement, `<div ref="${ref}"></div>`);
+        verifier = (element, stateDiff, state) => {
+            triggered = true;
+            this.expect(stateDiff.initialState).equalToValue(initialState.initialState);
+            this.expect(state.fakeState).equalToValue(undefined);
+        };
+        await this.waitForStateUpdated(ref);
+        this.expect(triggered).equalToValue(true);
+        
+        triggered = false;
+        let fakeState = {
+            fakeState: Math.random()
+        };
+        verifier = (element, stateDiff, state) => {
+            triggered = true;
+            this.expect(stateDiff.fakeState).equalToValue(fakeState.fakeState);
+            this.expect(state.fakeState).equalToValue(undefined);
+            
+            this.expect(state.initialState).equalToValue(initialState.initialState);
+            this.expect(stateDiff.initialState).equalToValue(undefined);
+        };
+        ref.setState(fakeState);
+        await this.waitForStateUpdated(ref);
+        this.expect(ref.state.fakeState).equalToValue(fakeState.fakeState);
+        this.expect(ref.state.initialState).equalToValue(initialState.initialState);
+        this.expect(triggered).equalToValue(true);
+    }
+    
+    async testOnValueStateUpdate() {
+        let triggered = false;
+        let verifier = null;
+        const initialState = Math.random();
+        const ref = REF.createRef({
+            state: initialState,
+            onStateUpdate:(element, stateDiff, state) => {
+                if (verifier) verifier(element, stateDiff, state);
+            }
+        });
+        verifier = (element, stateDiff, state) => {
+            triggered = true;
+            this.expect(element).notEqualToValue(null);
+            this.expect(stateDiff).equalToValue(initialState);
+        }
+        DOM.inject(this.rootElement, `<div ref="${ref}"></div>`);
+        await this.waitForStateUpdated(ref);
+        this.expect(triggered).equalToValue(true);
+
+        const newState = null;
+        triggered = false;
+        verifier = (element, stateDiff, state) => {
+            triggered = true;
+            this.expect(stateDiff).equalToValue(newState);
+            this.expect(state).equalToValue(initialState);
+        }
+        ref.setState(newState);
+        await this.waitForStateUpdated(ref);
+        this.expect(triggered).equalToValue(true);
+        
+        const newState1 = 0;
+        triggered = false;
+        verifier = (element, stateDiff, state) => {
+            triggered = true;
+            this.expect(stateDiff).equalToValue(newState1);
+            this.expect(state).equalToValue(newState);
+        }
+        ref.setState(newState1);
+        await this.waitForStateUpdated(ref);
+        this.expect(triggered).equalToValue(true);
+        
+        const newState2 = undefined;
+        let expectedE = null;
+        ref.setState(newState1);
+        try {
+            await this.waitForStateUpdated(ref);
+        } catch(e) {
+            expectedE = e;
+        }
+        this.expect(expectedE).notEqualToValue(null);
+    }
+}
+export {DiffTest, DomTest, RefTest};

Added: trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/test/index.html (0 => 250307)


--- trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/test/index.html	                        (rev 0)
+++ trunk/Tools/resultsdbpy/resultsdbpy/view/static/library/js/test/index.html	2019-09-24 17:48:20 UTC (rev 250307)
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>Test</title>
+        <link rel="stylesheet" href=""
+        <link rel="shortcut icon" sizes="32x32" type="image/x-icon" href=""
+        <meta content="width=device-width, initial-scale=1, viewport-fit=cover" name="viewport">
+        <meta charset="UTF-8">
+    </head>
+    <body>
+        <div id="app"></div>
+        <script type="module">
+            import {DOM} from "../Ref.js";
+            import {TestController} from "../Test.js";
+            import {TestApp} from "../components/TestComponents.js";
+            const testController = new TestController();
+            async function main() {
+                // Test file list: Root Path will be Test.js's folder
+                await testController.collectFile("./test/RefTest.js");
+                DOM.inject(document.getElementById("app"), TestApp(testController));
+            };
+            main();
+        </script>
+    </body>
+</html>
_______________________________________________
webkit-changes mailing list
webkit-changes@lists.webkit.org
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to