Title: [222782] trunk
Revision
222782
Author
[email protected]
Date
2017-10-03 09:12:26 -0700 (Tue, 03 Oct 2017)

Log Message

Web Inspector: Add View layout tests, make views more testable
https://bugs.webkit.org/show_bug.cgi?id=161274
<rdar://problem/28038615>

Reviewed by Devin Rousso.

Source/WebInspectorUI:

This patch adds support for View testing. Since view layouts are scheduled
using requestAnimationFrame, FrontendTestHarness now provides a timer-based
polyfill, to allow nonintrusive testing of the frontend View hierarchy.

* UserInterface/Test.html:
Make WI.View available to tests.

* UserInterface/Test/FrontendTestHarness.js:
(FrontendTestHarness.prototype.redirectRequestAnimationFrame):

* UserInterface/Views/View.js:
(WI.View.rootView):
(WI.View.prototype.replaceSubview):
(WI.View.prototype._didMoveToWindow):
(WI.View._cancelScheduledLayoutForView):
Fixed issues caught while writing tests for the expected View behavior.

LayoutTests:

Add tests for creating views, adding and removing subviews, and layout
operations. These tests rely on a mock requestAnimationFrame, which is
enabled with FrontendTestHarness.redirectRequestAnimationFrame.

* inspector/view/asynchronous-layout-expected.txt: Added.
* inspector/view/asynchronous-layout.html: Added.
* inspector/view/basics-expected.txt: Added.
* inspector/view/basics.html: Added.
* inspector/view/synchronous-layout-expected.txt: Added.
* inspector/view/synchronous-layout.html: Added.

* inspector/view/resources/test-view.js: Added.
(TestPage.registerInitializer.WI.TestView):
(TestPage.registerInitializer.WI.TestView.prototype.get initialLayoutCount):
(TestPage.registerInitializer.WI.TestView.prototype.get layoutCount):
(TestPage.registerInitializer.WI.TestView.prototype.evaluateAfterLayout):
(TestPage.registerInitializer.WI.TestView.prototype.initialLayout):
(TestPage.registerInitializer.WI.TestView.prototype.layout):
(TestPage.registerInitializer):
Register an instrumentation subclass of View. TestView counts calls to
protected methods and accepts callbacks to execute when a layout completes.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (222781 => 222782)


--- trunk/LayoutTests/ChangeLog	2017-10-03 16:05:30 UTC (rev 222781)
+++ trunk/LayoutTests/ChangeLog	2017-10-03 16:12:26 UTC (rev 222782)
@@ -1,3 +1,33 @@
+2017-10-03  Matt Baker  <[email protected]>
+
+        Web Inspector: Add View layout tests, make views more testable
+        https://bugs.webkit.org/show_bug.cgi?id=161274
+        <rdar://problem/28038615>
+
+        Reviewed by Devin Rousso.
+
+        Add tests for creating views, adding and removing subviews, and layout
+        operations. These tests rely on a mock requestAnimationFrame, which is
+        enabled with FrontendTestHarness.redirectRequestAnimationFrame.
+
+        * inspector/view/asynchronous-layout-expected.txt: Added.
+        * inspector/view/asynchronous-layout.html: Added.
+        * inspector/view/basics-expected.txt: Added.
+        * inspector/view/basics.html: Added.
+        * inspector/view/synchronous-layout-expected.txt: Added.
+        * inspector/view/synchronous-layout.html: Added.
+
+        * inspector/view/resources/test-view.js: Added.
+        (TestPage.registerInitializer.WI.TestView):
+        (TestPage.registerInitializer.WI.TestView.prototype.get initialLayoutCount):
+        (TestPage.registerInitializer.WI.TestView.prototype.get layoutCount):
+        (TestPage.registerInitializer.WI.TestView.prototype.evaluateAfterLayout):
+        (TestPage.registerInitializer.WI.TestView.prototype.initialLayout):
+        (TestPage.registerInitializer.WI.TestView.prototype.layout):
+        (TestPage.registerInitializer):
+        Register an instrumentation subclass of View. TestView counts calls to
+        protected methods and accepts callbacks to execute when a layout completes.
+
 2017-10-03  Ryan Haddad  <[email protected]>
 
         Update iOS TestExpectations for payment-request tests.

Added: trunk/LayoutTests/inspector/view/asynchronous-layout-expected.txt (0 => 222782)


--- trunk/LayoutTests/inspector/view/asynchronous-layout-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/view/asynchronous-layout-expected.txt	2017-10-03 16:12:26 UTC (rev 222782)
@@ -0,0 +1,34 @@
+Testing asynchronous View layout operations: needsLayout, cancelLayout.
+
+
+== Running test suite: View.AsynchronousLayout
+-- Running test case: View.automaticLayout
+PASS: View should have a pending layout once it is attached.
+Layout complete.
+PASS: View should do an initial layout.
+PASS: View should update its layout.
+PASS: View should not have a pending layout.
+
+-- Running test case: View.automaticLayout.cancelled
+PASS: View should have a pending layout once it is attached.
+PASS: View should not have a pending layout once it is detached.
+
+-- Running test case: View.needsLayout
+Flush pending layouts, then schedule an update.
+PASS: View should have a pending layout.
+Layout complete.
+PASS: View should update its layout.
+PASS: View should not have a pending layout.
+
+-- Running test case: View.needsLayout.propogateToSubview
+Schedule parent view update.
+Layout complete.
+PASS: Chlid view should do an initial layout.
+PASS: Child view should update its layout.
+
+-- Running test case: View.cancelLayout
+Cancel automatic layout.
+PASS: View should not have a pending layout.
+Cancel scheduled layout.
+PASS: View should not have a pending layout.
+

Added: trunk/LayoutTests/inspector/view/asynchronous-layout.html (0 => 222782)


--- trunk/LayoutTests/inspector/view/asynchronous-layout.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/view/asynchronous-layout.html	2017-10-03 16:12:26 UTC (rev 222782)
@@ -0,0 +1,108 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script src=""
+<script>
+function test()
+{
+    InspectorTest.redirectRequestAnimationFrame();
+
+    let suite = InspectorTest.createAsyncSuite("View.AsynchronousLayout");
+
+    suite.addTestCase({
+        name: "View.automaticLayout",
+        test(resolve, reject) {
+            let view = new WI.TestView;
+            WI.View.rootView().addSubview(view);
+            InspectorTest.expectThat(view.layoutPending, "View should have a pending layout once it is attached.");
+
+            view.evaluateAfterLayout(() => {
+                InspectorTest.log("Layout complete.");
+                InspectorTest.expectEqual(view.initialLayoutCount, 1, "View should do an initial layout.");
+                InspectorTest.expectEqual(view.layoutCount, 1, "View should update its layout.");
+                InspectorTest.expectFalse(view.layoutPending, "View should not have a pending layout.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.automaticLayout.cancelled",
+        test(resolve, reject) {
+            let view = new WI.TestView;
+
+            WI.View.rootView().addSubview(view);
+            InspectorTest.expectThat(view.layoutPending, "View should have a pending layout once it is attached.");
+
+            WI.View.rootView().removeSubview(view);
+            InspectorTest.expectFalse(view.layoutPending, "View should not have a pending layout once it is detached.");
+            resolve();
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.needsLayout",
+        test(resolve, reject) {
+            let view = new WI.TestView;
+            WI.View.rootView().addSubview(view);
+
+            InspectorTest.log("Flush pending layouts, then schedule an update.");
+            view.updateLayout();
+            view.needsLayout();
+            InspectorTest.expectThat(view.layoutPending, "View should have a pending layout.");
+
+            view.evaluateAfterLayout(() => {
+                InspectorTest.log("Layout complete.");
+                InspectorTest.expectEqual(view.layoutCount, 2, "View should update its layout.");
+                InspectorTest.expectFalse(view.layoutPending, "View should not have a pending layout.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.needsLayout.propogateToSubview",
+        test(resolve, reject) {
+            let parent = new WI.TestView;
+            let child = new WI.TestView;
+            WI.View.rootView().addSubview(parent);
+            parent.addSubview(child);
+            InspectorTest.log("Schedule parent view update.");
+            parent.needsLayout();
+
+            child.evaluateAfterLayout(() => {
+                InspectorTest.log("Layout complete.");
+                InspectorTest.expectEqual(child.initialLayoutCount, 1, "Chlid view should do an initial layout.");
+                InspectorTest.expectEqual(child.layoutCount, 1, "Child view should update its layout.");
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.cancelLayout",
+        test(resolve, reject) {
+            let view = new WI.TestView;
+            WI.View.rootView().addSubview(view);
+
+            InspectorTest.log("Cancel automatic layout.");
+            view.cancelLayout();
+            InspectorTest.expectFalse(view.layoutPending, "View should not have a pending layout.");
+
+            InspectorTest.log("Cancel scheduled layout.");
+            view.needsLayout();
+            view.cancelLayout();
+            InspectorTest.expectFalse(view.layoutPending, "View should not have a pending layout.");
+            resolve();
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+    <p>Testing asynchronous View layout operations: needsLayout, cancelLayout.</p>
+</body>
+</html>

Added: trunk/LayoutTests/inspector/view/basics-expected.txt (0 => 222782)


--- trunk/LayoutTests/inspector/view/basics-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/view/basics-expected.txt	2017-10-03 16:12:26 UTC (rev 222782)
@@ -0,0 +1,52 @@
+Testing basic View operations: root view access, view creation, and subview management.
+
+
+== Running test suite: View.Basics
+-- Running test case: View.rootView
+PASS: Root view should be attached by definition.
+PASS: Root view element should be document.body.
+PASS: Root view should not have a pending layout.
+PASS: Root view should not have subviews.
+PASS: View.rootView() should always return the same view.
+
+-- Running test case: View.constructor
+PASS: View should not be attached.
+PASS: View element should be a <div>.
+PASS: View element should not have child nodes.
+PASS: View should not have a pending layout.
+PASS: View should not have subviews.
+PASS: View should not have a parent.
+PASS: View should be created with passed in element.
+
+-- Running test case: View.addSubview
+PASS: Child should have the correct parent.
+PASS: Child should be a descendant of the parent.
+PASS: Child should be included in the parent's subviews.
+PASS: Adding a view multiple times should have no effect.
+PASS: Grandchild should be a descendant of it's grandparent.
+
+-- Running test case: View.removeSubview
+PASS: Removed view should not have a parent.
+PASS: Removed view should not be a descendant of the parent.
+PASS: Removed view should not be included in the parent's subviews.
+PASS: Removing a nonexistent view should have no effect.
+
+-- Running test case: View.insertSubviewBefore
+PASS: Inserting a view before `null` should append the view.
+PASS: child2 should be inserted before dhild1.
+PASS: child1 should be after child2.
+PASS: Inserting a view before a nonexistent view should have no effect.
+
+-- Running test case: View.replaceSubview
+PASS: Replaced view should not have a parent.
+PASS: New view should have the correct parent.
+PASS: Replacing a view with itself should have no effect.
+PASS: Replacing a nonexistent view should have no effect.
+
+-- Running test case: View.isAttached
+PASS: View added to the root should be attached.
+PASS: View removed from the root should not be attached.
+PASS: View added to a detached parent should not be attached.
+PASS: Attaching a view to the root causes descendent views to be attached.
+PASS: Detaching a view from the root causes descendent views to be detached.
+

Added: trunk/LayoutTests/inspector/view/basics.html (0 => 222782)


--- trunk/LayoutTests/inspector/view/basics.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/view/basics.html	2017-10-03 16:12:26 UTC (rev 222782)
@@ -0,0 +1,158 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script>
+function test()
+{
+    let suite = InspectorTest.createSyncSuite("View.Basics");
+
+    suite.addTestCase({
+        name: "View.rootView",
+        test() {
+            let rootView = WI.View.rootView();
+            InspectorTest.expectThat(rootView.isAttached, "Root view should be attached by definition.");
+            InspectorTest.expectEqual(rootView.element, document.body, "Root view element should be document.body.");
+            InspectorTest.expectFalse(rootView.layoutPending, "Root view should not have a pending layout.");
+            InspectorTest.expectEqual(rootView.subviews.length, 0, "Root view should not have subviews.");
+            InspectorTest.expectEqual(WI.View.rootView(), WI.View.rootView(), "View.rootView() should always return the same view.");
+
+            return true;
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.constructor",
+        test() {
+            let view = new WI.View;
+            InspectorTest.expectFalse(view.isAttached, "View should not be attached.");
+            InspectorTest.expectEqual(view.element.tagName, "DIV", "View element should be a <div>.");
+            InspectorTest.expectEqual(view.element.childNodes.length, 0, "View element should not have child nodes.");
+            InspectorTest.expectFalse(view.layoutPending, "View should not have a pending layout.");
+            InspectorTest.expectEqual(view.subviews.length, 0, "View should not have subviews.");
+            InspectorTest.expectNull(view.parentView, "View should not have a parent.");
+
+            let existingElement = document.createElement("ol");
+            let customView = new WI.View(existingElement);
+            InspectorTest.expectEqual(customView.element, existingElement, "View should be created with passed in element.");
+
+            return true;
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.addSubview",
+        test() {
+            let parent = new WI.View;
+            let child = new WI.View;
+            parent.addSubview(child);
+            InspectorTest.expectEqual(child.parentView, parent, "Child should have the correct parent.");
+            InspectorTest.expectThat(child.isDescendantOf(parent), "Child should be a descendant of the parent.");
+            InspectorTest.expectThat(parent.subviews.includes(child), "Child should be included in the parent's subviews.");
+
+            let previousSubviews = parent.subviews.slice();
+            parent.addSubview(child);
+            InspectorTest.expectShallowEqual(previousSubviews, parent.subviews, "Adding a view multiple times should have no effect.");
+
+            let grandchild = new WI.View;
+            child.addSubview(grandchild);
+            InspectorTest.expectThat(grandchild.isDescendantOf(child.parentView), "Grandchild should be a descendant of it's grandparent.");
+
+            return true;
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.removeSubview",
+        test() {
+            let parent = new WI.View;
+            let child = new WI.View;
+            parent.addSubview(child);
+            parent.removeSubview(child);
+            InspectorTest.expectNull(child.parentView, "Removed view should not have a parent.");
+            InspectorTest.expectThat(!child.isDescendantOf(parent), "Removed view should not be a descendant of the parent.");
+            InspectorTest.expectThat(!parent.subviews.includes(child), "Removed view should not be included in the parent's subviews.");
+
+            let previousSubviews = parent.subviews.slice();
+            parent.removeSubview(new WI.View);
+            InspectorTest.expectShallowEqual(previousSubviews, parent.subviews, "Removing a nonexistent view should have no effect.")
+
+            return true;
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.insertSubviewBefore",
+        test() {
+            let parent = new WI.View;
+            let child1 = new WI.View;
+            let child2 = new WI.View;
+            parent.insertSubviewBefore(child1);
+            InspectorTest.expectEqual(parent.subviews[0], child1, "Inserting a view before `null` should append the view.");
+
+            parent.insertSubviewBefore(child2, child1);
+            InspectorTest.expectEqual(parent.subviews[0], child2, "child2 should be inserted before dhild1.");
+            InspectorTest.expectEqual(parent.subviews[1], child1, "child1 should be after child2.");
+
+            let previousSubviews = parent.subviews.slice();
+            parent.insertSubviewBefore(child2, new WI.View);
+            InspectorTest.expectShallowEqual(parent.subviews, previousSubviews, "Inserting a view before a nonexistent view should have no effect.");
+
+            return true;
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.replaceSubview",
+        test() {
+            let parent = new WI.View;
+            let child1 = new WI.View;
+            let child2 = new WI.View;
+            parent.addSubview(child1);
+            parent.replaceSubview(child1, child2);
+            InspectorTest.expectNull(child1.parentView, "Replaced view should not have a parent.");
+            InspectorTest.expectEqual(child2.parentView, parent, "New view should have the correct parent.");
+
+            let previousSubviews = parent.subviews.slice();
+            parent.replaceSubview(child2, child2);
+            InspectorTest.expectShallowEqual(parent.subviews, previousSubviews, "Replacing a view with itself should have no effect.");
+
+            previousSubviews = parent.subviews;
+            parent.replaceSubview(new WI.View, child1);
+            InspectorTest.expectShallowEqual(parent.subviews, previousSubviews, "Replacing a nonexistent view should have no effect.");
+
+            return true;
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.isAttached",
+        test() {
+            let view = new WI.View;
+            WI.View.rootView().addSubview(view);
+            InspectorTest.expectThat(view.isAttached, "View added to the root should be attached.");
+
+            WI.View.rootView().removeSubview(view);
+            InspectorTest.expectFalse(view.isAttached, "View removed from the root should not be attached.");
+
+            let parent = new WI.View;
+            let child = new WI.View;
+            parent.addSubview(child);
+            InspectorTest.expectFalse(child.isAttached, "View added to a detached parent should not be attached.");
+            WI.View.rootView().addSubview(parent);
+            InspectorTest.expectThat(child.isAttached, "Attaching a view to the root causes descendent views to be attached.");
+            WI.View.rootView().removeSubview(parent);
+            InspectorTest.expectFalse(child.isAttached, "Detaching a view from the root causes descendent views to be detached.");
+
+            return true;
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+    <p>Testing basic View operations: root view access, view creation, and subview management.</p>
+</body>
+</html>

Added: trunk/LayoutTests/inspector/view/resources/test-view.js (0 => 222782)


--- trunk/LayoutTests/inspector/view/resources/test-view.js	                        (rev 0)
+++ trunk/LayoutTests/inspector/view/resources/test-view.js	2017-10-03 16:12:26 UTC (rev 222782)
@@ -0,0 +1,39 @@
+TestPage.registerInitializer(() => {
+    WI.TestView = class TestView extends WI.View
+    {
+        constructor()
+        {
+            super();
+
+            this._layoutCallbacks = [];
+            this._initialLayoutCount = 0;
+            this._layoutCount = 0;
+        }
+
+        // Public
+
+        get initialLayoutCount() { return this._initialLayoutCount; }
+        get layoutCount() { return this._layoutCount; }
+
+        evaluateAfterLayout(callback)
+        {
+            this._layoutCallbacks.push(callback);
+        }
+
+        // Protected
+
+        initialLayout()
+        {
+            this._initialLayoutCount++;
+        }
+
+        layout()
+        {
+            this._layoutCount++;
+            let callbacks = this._layoutCallbacks;
+            this._layoutCallbacks = [];
+            for (let callback of callbacks)
+                callback();
+        }
+    };
+});

Added: trunk/LayoutTests/inspector/view/synchronous-layout-expected.txt (0 => 222782)


--- trunk/LayoutTests/inspector/view/synchronous-layout-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/inspector/view/synchronous-layout-expected.txt	2017-10-03 16:12:26 UTC (rev 222782)
@@ -0,0 +1,24 @@
+Testing synchronous View layout operations: updateLayout, updateLayoutIfNeeded.
+
+
+== Running test suite: View.SynchronousLayout
+-- Running test case: View.updateLayout
+Update layout for attached view.
+PASS: View should do an initial layout.
+PASS: View should update its layout once.
+PASS: View should not have a pending layout.
+Update layout for detached view.
+PASS: View should do an initial layout.
+PASS: View should update its layout once.
+PASS: View should not have a pending layout.
+
+-- Running test case: View.updateLayout.propogateToSubviews
+Update parent view layout.
+PASS: Child view should do an initial layout.
+PASS: Child view should update its layout once.
+
+-- Running test case: View.updateLayoutIfNeeded
+PASS: View should update if an initial layout never happened.
+PASS: View should update if a layout is pending.
+PASS: View should not update if no layout is pending.
+

Added: trunk/LayoutTests/inspector/view/synchronous-layout.html (0 => 222782)


--- trunk/LayoutTests/inspector/view/synchronous-layout.html	                        (rev 0)
+++ trunk/LayoutTests/inspector/view/synchronous-layout.html	2017-10-03 16:12:26 UTC (rev 222782)
@@ -0,0 +1,81 @@
+<!doctype html>
+<html>
+<head>
+<script src=""
+<script src=""
+<script>
+function test()
+{
+    InspectorTest.redirectRequestAnimationFrame();
+
+    let suite = InspectorTest.createSyncSuite("View.SynchronousLayout");
+
+    suite.addTestCase({
+        name: "View.updateLayout",
+        test() {
+            let view1 = new WI.TestView;
+            WI.View.rootView().addSubview(view1);
+            InspectorTest.log("Update layout for attached view.");
+            view1.updateLayout();
+            InspectorTest.expectEqual(view1.initialLayoutCount, 1, "View should do an initial layout.");
+            InspectorTest.expectEqual(view1.layoutCount, 1, "View should update its layout once.");
+            InspectorTest.expectFalse(view1.layoutPending, "View should not have a pending layout.");
+
+            let view2 = new WI.TestView;
+            InspectorTest.log("Update layout for detached view.");
+            view2.updateLayout();
+            InspectorTest.expectEqual(view2.initialLayoutCount, 1, "View should do an initial layout.");
+            InspectorTest.expectEqual(view2.layoutCount, 1, "View should update its layout once.");
+            InspectorTest.expectFalse(view2.layoutPending, "View should not have a pending layout.");
+
+            return true;
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.updateLayout.propogateToSubviews",
+        test() {
+            let parent = new WI.TestView;
+            let child = new WI.TestView;
+
+            WI.View.rootView().addSubview(parent);
+            parent.addSubview(child);
+
+            InspectorTest.log("Update parent view layout.");
+            parent.updateLayout();
+            InspectorTest.expectEqual(child.initialLayoutCount, 1, "Child view should do an initial layout.");
+            InspectorTest.expectEqual(child.layoutCount, 1, "Child view should update its layout once.");
+
+            return true;
+        }
+    });
+
+    suite.addTestCase({
+        name: "View.updateLayoutIfNeeded",
+        test() {
+            let view1 = new WI.TestView;
+            view1.updateLayoutIfNeeded();
+            InspectorTest.expectEqual(view1.layoutCount, 1, "View should update if an initial layout never happened.");
+
+            let view2 = new WI.TestView;
+            view2.needsLayout();
+            view2.updateLayoutIfNeeded();
+            InspectorTest.expectEqual(view2.layoutCount, 1, "View should update if a layout is pending.");
+
+            let view3 = new WI.TestView;
+            view3.updateLayout();
+            view3.updateLayoutIfNeeded();
+            InspectorTest.expectEqual(view3.layoutCount, 1, "View should not update if no layout is pending.");
+
+            return true;
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body _onload_="runTest()">
+    <p>Testing synchronous View layout operations: updateLayout, updateLayoutIfNeeded.</p>
+</body>
+</html>

Modified: trunk/Source/WebInspectorUI/ChangeLog (222781 => 222782)


--- trunk/Source/WebInspectorUI/ChangeLog	2017-10-03 16:05:30 UTC (rev 222781)
+++ trunk/Source/WebInspectorUI/ChangeLog	2017-10-03 16:12:26 UTC (rev 222782)
@@ -1,3 +1,28 @@
+2017-10-03  Matt Baker  <[email protected]>
+
+        Web Inspector: Add View layout tests, make views more testable
+        https://bugs.webkit.org/show_bug.cgi?id=161274
+        <rdar://problem/28038615>
+
+        Reviewed by Devin Rousso.
+
+        This patch adds support for View testing. Since view layouts are scheduled
+        using requestAnimationFrame, FrontendTestHarness now provides a timer-based
+        polyfill, to allow nonintrusive testing of the frontend View hierarchy.
+
+        * UserInterface/Test.html:
+        Make WI.View available to tests.
+
+        * UserInterface/Test/FrontendTestHarness.js:
+        (FrontendTestHarness.prototype.redirectRequestAnimationFrame):
+
+        * UserInterface/Views/View.js:
+        (WI.View.rootView):
+        (WI.View.prototype.replaceSubview):
+        (WI.View.prototype._didMoveToWindow):
+        (WI.View._cancelScheduledLayoutForView):
+        Fixed issues caught while writing tests for the expected View behavior.
+
 2017-10-02  Joseph Pecoraro  <[email protected]>
 
         Web Inspector: Escape more characters in posix string conversion

Modified: trunk/Source/WebInspectorUI/UserInterface/Test/FrontendTestHarness.js (222781 => 222782)


--- trunk/Source/WebInspectorUI/UserInterface/Test/FrontendTestHarness.js	2017-10-03 16:05:30 UTC (rev 222781)
+++ trunk/Source/WebInspectorUI/UserInterface/Test/FrontendTestHarness.js	2017-10-03 16:12:26 UTC (rev 222782)
@@ -140,6 +140,46 @@
             });
     }
 
+    redirectRequestAnimationFrame()
+    {
+        console.assert(!this._originalRequestAnimationFrame);
+        if (this._originalRequestAnimationFrame)
+            return;
+
+        this._originalRequestAnimationFrame = window.requestAnimationFrame;
+        this._requestAnimationFrameCallbacks = new Map;
+        this._nextRequestIdentifier = 1;
+
+        window.requestAnimationFrame = (callback) => {
+            let requestIdentifier = this._nextRequestIdentifier++;
+            this._requestAnimationFrameCallbacks.set(requestIdentifier, callback);
+            if (this._requestAnimationFrameTimer)
+                return requestIdentifier;
+
+            let dispatchCallbacks = () => {
+                let callbacks = this._requestAnimationFrameCallbacks;
+                this._requestAnimationFrameCallbacks = new Map;
+                this._requestAnimationFrameTimer = undefined;
+                let timestamp = window.performance.now();
+                for (let callback of callbacks.values())
+                    callback(timestamp);
+            };
+
+            this._requestAnimationFrameTimer = setTimeout(dispatchCallbacks, 0);
+            return requestIdentifier;
+        };
+
+        window.cancelAnimationFrame = (requestIdentifier) => {
+            if (!this._requestAnimationFrameCallbacks.delete(requestIdentifier))
+                return;
+
+            if (!this._requestAnimationFrameCallbacks.size) {
+                clearTimeout(this._requestAnimationFrameTimer);
+                this._requestAnimationFrameTimer = undefined;
+            }
+        };
+    }
+
     redirectConsoleToTestOutput()
     {
         // We can't use arrow functions here because of 'arguments'. It might

Modified: trunk/Source/WebInspectorUI/UserInterface/Test.html (222781 => 222782)


--- trunk/Source/WebInspectorUI/UserInterface/Test.html	2017-10-03 16:05:30 UTC (rev 222781)
+++ trunk/Source/WebInspectorUI/UserInterface/Test.html	2017-10-03 16:12:26 UTC (rev 222782)
@@ -215,6 +215,7 @@
     <script src=""
     <script src=""
     <script src=""
+    <script src=""
 
     <script type="text/_javascript_">
         WI.sharedApp = new WI.TestAppController;

Modified: trunk/Source/WebInspectorUI/UserInterface/Views/View.js (222781 => 222782)


--- trunk/Source/WebInspectorUI/UserInterface/Views/View.js	2017-10-03 16:05:30 UTC (rev 222781)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/View.js	2017-10-03 16:12:26 UTC (rev 222782)
@@ -44,8 +44,12 @@
 
     static rootView()
     {
-        if (!WI.View._rootView)
+        if (!WI.View._rootView) {
+            // Since the root view is attached by definition, it does not go through the
+            // normal view attachment process. Simply mark it as attached.
             WI.View._rootView = new WI.View(document.body);
+            WI.View._rootView._isAttachedToRoot = true;
+        }
 
         return WI.View._rootView;
     }
@@ -129,6 +133,8 @@
     replaceSubview(oldView, newView)
     {
         console.assert(oldView !== newView, "Cannot replace subview with itself.");
+        if (oldView === newView)
+            return;
 
         this.insertSubviewBefore(newView, oldView);
         this.removeSubview(oldView);
@@ -234,10 +240,13 @@
 
         this._isAttachedToRoot = isAttachedToRoot;
         if (this._isAttachedToRoot) {
+            WI.View._scheduleLayoutForView(this);
             this.attached();
-            WI.View._scheduleLayoutForView(this);
-        } else
+        } else {
+            if (this._dirty)
+                this.cancelLayout();
             this.detached();
+        }
 
         for (let view of this._subviews)
             view._didMoveToWindow(isAttachedToRoot);
@@ -328,6 +337,8 @@
             parentView = parentView.parentView;
         }
 
+        view._dirty = false;
+
         if (!WI.View._scheduledLayoutUpdateIdentifier)
             return;
 
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to