Merge lp:~tpeeters/ubuntu-ui-toolkit/back into lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/multiColumnView
- back
- Merge into multiColumnView
Proposed by
Tim Peeters
on 2015-07-15
| Status: | Merged |
|---|---|
| Approved by: | Christian Dywan on 2015-07-20 |
| Approved revision: | 1578 |
| Merged at revision: | 1578 |
| Proposed branch: | lp:~tpeeters/ubuntu-ui-toolkit/back |
| Merge into: | lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/multiColumnView |
| Diff against target: |
770 lines (+413/-119) 7 files modified
examples/ubuntu-ui-toolkit-gallery/Template.qml (+0/-5) modules/Ubuntu/Components/1.2/PageWrapperUtils.js (+1/-2) modules/Ubuntu/Components/1.3/MultiColumnView.qml (+45/-10) modules/Ubuntu/Components/1.3/tree.js (+84/-23) modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml (+15/-5) tests/unit_x11/tst_components/tst_multicolumnheader.qml (+172/-13) tests/unit_x11/tst_components/tst_multicolumnview.qml (+96/-61) |
| To merge this branch: | bzr merge lp:~tpeeters/ubuntu-ui-toolkit/back |
| Related bugs: |
| Reviewer | Review Type | Date Requested | Status |
|---|---|---|---|
| Christian Dywan | 2015-07-15 | Approve on 2015-07-20 | |
|
Review via email:
|
|||
Commit Message
Automatic back button.
Description of the Change
To post a comment you must log in.
lp:~tpeeters/ubuntu-ui-toolkit/back
updated
on 2015-07-20
- 1569. By Tim Peeters on 2015-07-16
-
clean showcase
- 1570. By Tim Peeters on 2015-07-16
-
don't add page that was already added
- 1571. By Tim Peeters on 2015-07-16
-
update tests
- 1572. By Tim Peeters on 2015-07-17
-
clean
- 1573. By Tim Peeters on 2015-07-19
-
clean
- 1574. By Tim Peeters on 2015-07-20
-
add unit tests for single column on phone
- 1575. By Tim Peeters on 2015-07-20
-
tst_multicolumnview to work in single column
- 1576. By Tim Peeters on 2015-07-20
-
clean
lp:~tpeeters/ubuntu-ui-toolkit/back
updated
on 2015-07-20
- 1577. By Tim Peeters on 2015-07-20
-
review comment
- 1578. By Tim Peeters on 2015-07-20
-
tweak
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
| 1 | === modified file 'examples/ubuntu-ui-toolkit-gallery/Template.qml' |
| 2 | --- examples/ubuntu-ui-toolkit-gallery/Template.qml 2015-07-02 14:51:16 +0000 |
| 3 | +++ examples/ubuntu-ui-toolkit-gallery/Template.qml 2015-07-20 22:43:59 +0000 |
| 4 | @@ -20,11 +20,6 @@ |
| 5 | Page { |
| 6 | id: template |
| 7 | |
| 8 | - head.backAction: Action { |
| 9 | - iconName: 'back' |
| 10 | - onTriggered: columns.removePages(template) |
| 11 | - } |
| 12 | - |
| 13 | default property alias content: layout.children |
| 14 | property alias spacing: layout.spacing |
| 15 | |
| 16 | |
| 17 | === modified file 'modules/Ubuntu/Components/1.2/PageWrapperUtils.js' |
| 18 | --- modules/Ubuntu/Components/1.2/PageWrapperUtils.js 2015-04-30 08:32:44 +0000 |
| 19 | +++ modules/Ubuntu/Components/1.2/PageWrapperUtils.js 2015-07-20 22:43:59 +0000 |
| 20 | @@ -30,8 +30,7 @@ |
| 21 | if (pageWrapper.reference.createObject) { |
| 22 | // page reference is a component |
| 23 | pageComponent = pageWrapper.reference; |
| 24 | - } |
| 25 | - else if (typeof pageWrapper.reference == "string") { |
| 26 | + } else if (typeof pageWrapper.reference == "string") { |
| 27 | // page reference is a string (url) |
| 28 | pageComponent = Qt.createComponent(pageWrapper.reference); |
| 29 | } |
| 30 | |
| 31 | === modified file 'modules/Ubuntu/Components/1.3/MultiColumnView.qml' |
| 32 | --- modules/Ubuntu/Components/1.3/MultiColumnView.qml 2015-07-13 15:14:03 +0000 |
| 33 | +++ modules/Ubuntu/Components/1.3/MultiColumnView.qml 2015-07-20 22:43:59 +0000 |
| 34 | @@ -175,13 +175,18 @@ |
| 35 | |
| 36 | /*! |
| 37 | \qmlmethod Item addPageToNextColumn(Item sourcePage, var page[, var properties]) |
| 38 | - Same as \l addPageToCurrentColumn except that the \c page is added to the column |
| 39 | - next to the one the \c sourcePage resides. If \c sourcePage is null, the new |
| 40 | - page will be added to the leftmost column. If \c sourcePage is located in the |
| 41 | + Remove all previous pages from the next column (relative to the column that |
| 42 | + holds \c sourcePage) and all following columns, and then add \c page to the next column. |
| 43 | + If \c sourcePage is located in the |
| 44 | rightmost column, the new page will be pushed to the same column as \c sourcePage. |
| 45 | */ |
| 46 | function addPageToNextColumn(sourcePage, page, properties) { |
| 47 | - return d.addPageToColumn(d.columnForPage(sourcePage) + 1, sourcePage, page, properties); |
| 48 | + var nextColumn = d.columnForPage(sourcePage) + 1; |
| 49 | + d.tree.prune(nextColumn); |
| 50 | + for (var i = nextColumn; i < d.columns; i++) { |
| 51 | + d.updatePageForColumn(i); |
| 52 | + } |
| 53 | + return d.addPageToColumn(nextColumn, sourcePage, page, properties); |
| 54 | } |
| 55 | |
| 56 | /*! |
| 57 | @@ -263,7 +268,7 @@ |
| 58 | function getWrapper(page) { |
| 59 | if (page && page.hasOwnProperty("parentNode")) { |
| 60 | var w = page.parentNode; |
| 61 | - if (w.hasOwnProperty("object") && w.hasOwnProperty("reference")) { |
| 62 | + if (w && w.hasOwnProperty("object") && w.hasOwnProperty("reference")) { |
| 63 | if (w.object == page) { |
| 64 | return w; |
| 65 | } else { |
| 66 | @@ -300,11 +305,22 @@ |
| 67 | return; |
| 68 | } |
| 69 | |
| 70 | - var wrapper = d.createWrapper(page, properties); |
| 71 | - wrapper.parentPage = sourcePage; |
| 72 | - wrapper.column = column; |
| 73 | - d.addWrappedPage(wrapper); |
| 74 | - return wrapper.object; |
| 75 | + // Check that the Page was not already added. |
| 76 | + if (typeof page !== "string" && !page.createObject) { |
| 77 | + // page is neither a url or a Component so it must be a Page object. |
| 78 | + |
| 79 | + var oldWrapper = getWrapper(page); |
| 80 | + if (oldWrapper && d.tree.index(oldWrapper) !== -1) { |
| 81 | + console.warn("Cannot add a Page that was already added."); |
| 82 | + return null; |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + var newWrapper = d.createWrapper(page, properties); |
| 87 | + newWrapper.parentPage = sourcePage; |
| 88 | + newWrapper.column = column; |
| 89 | + d.addWrappedPage(newWrapper); |
| 90 | + return newWrapper.object; |
| 91 | } |
| 92 | |
| 93 | // update the page for the specified column |
| 94 | @@ -455,6 +471,25 @@ |
| 95 | property color panelColor: multiColumnView.__propagated.header.panelColor |
| 96 | |
| 97 | visible: holder.pageWrapper && holder.pageWrapper.active |
| 98 | + |
| 99 | + // The multiColumn, page and showBackButton properties are used in |
| 100 | + // PageHeadStyle to show/hide the back button. |
| 101 | + property var multiColumn: multiColumnView |
| 102 | + property var page: holder.pageWrapper ? holder.pageWrapper.object : null |
| 103 | + property bool showBackButton: { |
| 104 | + if (!page) { |
| 105 | + return false; |
| 106 | + } |
| 107 | + var parentWrapper; |
| 108 | + try { |
| 109 | + parentWrapper = d.tree.parent(holder.pageWrapper); |
| 110 | + } catch(err) { |
| 111 | + // Root node has no parent node. |
| 112 | + return false; |
| 113 | + } |
| 114 | + var nextInColumn = d.tree.top(holder.column, holder.column < d.columns - 1, 1); |
| 115 | + return parentWrapper === nextInColumn; |
| 116 | + } |
| 117 | } |
| 118 | |
| 119 | Rectangle { |
| 120 | |
| 121 | === modified file 'modules/Ubuntu/Components/1.3/tree.js' |
| 122 | --- modules/Ubuntu/Components/1.3/tree.js 2015-07-14 09:51:34 +0000 |
| 123 | +++ modules/Ubuntu/Components/1.3/tree.js 2015-07-20 22:43:59 +0000 |
| 124 | @@ -42,7 +42,6 @@ |
| 125 | if (this.index(newNode) !== -1) { |
| 126 | throw "Cannot add the same node twice to a tree."; |
| 127 | } |
| 128 | - var parentIndex = this.index(parentNode); |
| 129 | if (size === 0) { |
| 130 | // adding root node |
| 131 | if (parentNode !== null) { |
| 132 | @@ -53,17 +52,43 @@ |
| 133 | if (parentNode === null) { |
| 134 | throw "Only root node has parentNode null." |
| 135 | } |
| 136 | - if (parentIndex === -1) { |
| 137 | + if (this.index(parentNode) === -1) { |
| 138 | throw "Cannot add non-root node if parentNode is not in the tree."; |
| 139 | } |
| 140 | } |
| 141 | nodes.push(newNode); |
| 142 | stems.push(stem); |
| 143 | - parents.push(parentIndex); |
| 144 | + parents.push(parentNode); |
| 145 | size++; |
| 146 | } |
| 147 | |
| 148 | - // Chops all nodes with an index higher than the given node. |
| 149 | + // Remove all nodes from the specified stem and higher stems. |
| 150 | + // |
| 151 | + // Returns the removed nodes. |
| 152 | + this.prune = function(stem) { |
| 153 | + var newNodes = []; |
| 154 | + var newStems = []; |
| 155 | + var newParents = []; |
| 156 | + var removedNodes = []; |
| 157 | + for (var i = 0; i < nodes.length; i++) { |
| 158 | + if (stems[i] < stem) { |
| 159 | + newNodes.push(nodes[i]); |
| 160 | + newStems.push(stems[i]); |
| 161 | + newParents.push(parents[i]); |
| 162 | + } else { |
| 163 | + removedNodes.push(nodes[i]); |
| 164 | + } |
| 165 | + } |
| 166 | + nodes = newNodes; |
| 167 | + stems = newStems; |
| 168 | + parents = newParents; |
| 169 | + size = nodes.length; |
| 170 | + return removedNodes; |
| 171 | + } |
| 172 | + |
| 173 | + // Chops all nodes with an index higher than the given node which |
| 174 | + // are in the same stem or a higher stem. |
| 175 | + // |
| 176 | // If, and only if, (inclusive) then also chop the given node. |
| 177 | // |
| 178 | // Default values for node and inclusive are top() and true. |
| 179 | @@ -72,38 +97,72 @@ |
| 180 | node = typeof node !== 'undefined' ? node : this.top(); |
| 181 | inclusive = typeof inclusive !== 'undefined' ? inclusive : true |
| 182 | var nodeIndex = this.index(node); |
| 183 | - if (nodeIndex >= 0) { |
| 184 | - if (inclusive) { |
| 185 | - size = nodeIndex; |
| 186 | - } else { |
| 187 | - size = nodeIndex + 1; |
| 188 | - } |
| 189 | - var oldNodes = nodes; |
| 190 | - nodes = nodes.slice(0, size); |
| 191 | - stems = stems.slice(0, size); |
| 192 | - parents = parents.slice(0, size); |
| 193 | - return oldNodes.slice(size); |
| 194 | - } else { |
| 195 | - // given node is not in the tree |
| 196 | + if (nodeIndex < 0) { |
| 197 | + // given node is not in the tree. |
| 198 | return []; |
| 199 | } |
| 200 | + if (inclusive) { |
| 201 | + size = nodeIndex; |
| 202 | + } else { |
| 203 | + size = nodeIndex + 1; |
| 204 | + } |
| 205 | + |
| 206 | + // Nodes with index(node) >= nodeIndex && stem >= stems[nodeIndex]; |
| 207 | + var badNodes = []; // to fill below |
| 208 | + |
| 209 | + // Nodes with index(node) >= nodeIndex (any stem). |
| 210 | + // Potential bad nodes to be removed. |
| 211 | + var uglyNodes = nodes.slice(size); |
| 212 | + var uglyStems = stems.slice(size); // to check below |
| 213 | + var uglyParents = parents.slice(size); |
| 214 | + |
| 215 | + var stem = stems[nodeIndex]; |
| 216 | + // Good nodes, with index(node) < nodeIndex && stem < stems[nodeIndex]: |
| 217 | + nodes = nodes.slice(0, size); |
| 218 | + stems = stems.slice(0, size); |
| 219 | + parents = parents.slice(0, size); |
| 220 | + |
| 221 | + // Add nodes with index(node) > nodeIndex && stem < stems[nodeIndex] back: |
| 222 | + for (var i = 0; i < uglyNodes.length; i++) { |
| 223 | + if (uglyStems[i] < stem) { |
| 224 | + // Because the stem of the parentNode <= stem of the node, |
| 225 | + // the node that is added back has the parentNode in nodes. |
| 226 | + nodes.push(uglyNodes[i]); |
| 227 | + stems.push(uglyStems[i]); |
| 228 | + parents.push(uglyParents[i]); |
| 229 | + size++; |
| 230 | + } else { |
| 231 | + badNodes.push(uglyNodes[i]); |
| 232 | + } |
| 233 | + } |
| 234 | + return badNodes; |
| 235 | } |
| 236 | |
| 237 | - // If exactMatch, return the node on top of the specified stem. |
| 238 | - // If !exactMatch, return the node with the highest index for stem <= the returned node stem |
| 239 | + // Returns the n'th node when traversing one or more stems from the |
| 240 | + // top down. When exactMatch, only the specified stem is traversed, and |
| 241 | + // when !exactMatch the specified stem and all higher stems are traversed. |
| 242 | + // |
| 243 | + // Returns null if no matching node was found. |
| 244 | // |
| 245 | // Default value for stem: 0 |
| 246 | // Default value for exactMatch: false |
| 247 | + // Default value for n: 0 (first node) |
| 248 | // |
| 249 | - // Returns null if no matching node was found. |
| 250 | - this.top = function(stem, exactMatch) { |
| 251 | + // Calling top() with no parameters returns top(0, false, 0) which is the |
| 252 | + // last node that was added to the tree. |
| 253 | + this.top = function(stem, exactMatch, n) { |
| 254 | stem = typeof stem !== 'undefined' ? stem : 0 |
| 255 | exactMatch = typeof exactMatch !== 'undefined' ? exactMatch : false |
| 256 | + n = typeof n !== 'undefined' ? n : 0 |
| 257 | |
| 258 | var st; |
| 259 | - for (var i = size-1; i >= 0; i--) { |
| 260 | + var count = n; |
| 261 | + for (var i = size - 1; i >= 0; i--) { |
| 262 | st = stems[i]; |
| 263 | if ((exactMatch && st === stem) || (!exactMatch && st >= stem)) { |
| 264 | + count--; |
| 265 | + } |
| 266 | + if (count < 0) { |
| 267 | return nodes[i]; |
| 268 | } |
| 269 | } |
| 270 | @@ -115,7 +174,9 @@ |
| 271 | var i = nodes.indexOf(node); |
| 272 | if (i === -1) { |
| 273 | throw "Specified node not found in tree."; |
| 274 | + } else if (i === 0) { |
| 275 | + throw "Root node has no parent node."; |
| 276 | } |
| 277 | - return nodes[parents[i]]; |
| 278 | + return parents[i]; |
| 279 | } |
| 280 | } |
| 281 | |
| 282 | === modified file 'modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml' |
| 283 | --- modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml 2015-06-25 23:49:33 +0000 |
| 284 | +++ modules/Ubuntu/Components/Themes/Ambiance/1.3/PageHeadStyle.qml 2015-07-20 22:43:59 +0000 |
| 285 | @@ -268,16 +268,26 @@ |
| 286 | objectName: "backButton" |
| 287 | |
| 288 | iconName: "back" |
| 289 | - visible: styledItem.pageStack !== null && |
| 290 | - styledItem.pageStack !== undefined && |
| 291 | - styledItem.pageStack.depth > 1 && |
| 292 | - !headerStyle.config.backAction |
| 293 | + property bool stackBack: styledItem.pageStack !== null && |
| 294 | + styledItem.pageStack !== undefined && |
| 295 | + styledItem.pageStack.depth > 1 |
| 296 | + |
| 297 | + // MultiColumnView adds the following properties: multiColumn, page, showBackButton. |
| 298 | + property bool treeBack: styledItem.hasOwnProperty("multiColumn") && |
| 299 | + styledItem.showBackButton |
| 300 | + |
| 301 | + visible: !headerStyle.config.backAction && (stackBack || treeBack) |
| 302 | |
| 303 | text: "back" |
| 304 | color: headerStyle.config.foregroundColor |
| 305 | |
| 306 | onTriggered: { |
| 307 | - styledItem.pageStack.pop(); |
| 308 | + if (stackBack) { |
| 309 | + styledItem.pageStack.pop(); |
| 310 | + } else { |
| 311 | + // treeBack |
| 312 | + styledItem.multiColumn.removePages(styledItem.page); |
| 313 | + } |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | |
| 318 | === modified file 'tests/unit_x11/tst_components/tst_multicolumnheader.qml' |
| 319 | --- tests/unit_x11/tst_components/tst_multicolumnheader.qml 2015-07-07 14:21:26 +0000 |
| 320 | +++ tests/unit_x11/tst_components/tst_multicolumnheader.qml 2015-07-20 22:43:59 +0000 |
| 321 | @@ -23,6 +23,9 @@ |
| 322 | width: units.gu(120) |
| 323 | height: units.gu(71) |
| 324 | |
| 325 | + // 2 on desktop, 1 on phone. |
| 326 | + property int columns: width >= units.gu(80) ? 2 : 1 |
| 327 | + |
| 328 | MultiColumnView { |
| 329 | id: multiColumnView |
| 330 | width: parent.width |
| 331 | @@ -69,6 +72,11 @@ |
| 332 | margins: units.gu(2) |
| 333 | } |
| 334 | color: "orange" |
| 335 | + Button { |
| 336 | + anchors.centerIn: parent |
| 337 | + text: "right" |
| 338 | + onTriggered: multiColumnView.addPageToNextColumn(leftPage, rightPage) |
| 339 | + } |
| 340 | } |
| 341 | } |
| 342 | Page { |
| 343 | @@ -80,6 +88,11 @@ |
| 344 | margins: units.gu(2) |
| 345 | } |
| 346 | color: "green" |
| 347 | + Button { |
| 348 | + anchors.centerIn: parent |
| 349 | + text: "Another page!" |
| 350 | + onTriggered: multiColumnView.addPageToCurrentColumn(rightPage, sectionsPage) |
| 351 | + } |
| 352 | } |
| 353 | } |
| 354 | Page { |
| 355 | @@ -96,23 +109,33 @@ |
| 356 | } |
| 357 | } |
| 358 | } |
| 359 | + |
| 360 | UbuntuTestCase { |
| 361 | when: windowShown |
| 362 | |
| 363 | + function resize_single_column_width() { |
| 364 | + multiColumnView.width = units.gu(40); |
| 365 | + } |
| 366 | + |
| 367 | + // resize to use the full window width |
| 368 | + function resize_full_width() { |
| 369 | + multiColumnView.width = root.width; |
| 370 | + } |
| 371 | + |
| 372 | function get_number_of_columns() { |
| 373 | var body = findChild(multiColumnView, "body"); |
| 374 | return body.children.length; |
| 375 | } |
| 376 | |
| 377 | - function get_header(index) { |
| 378 | - return findChild(multiColumnView, "Header" + index); |
| 379 | + function get_header(column) { |
| 380 | + return findChild(multiColumnView, "Header" + column); |
| 381 | } |
| 382 | |
| 383 | function get_number_of_headers() { |
| 384 | // FIXME: With only one column, revert to using the AppHeader |
| 385 | - // so multiColumnView sill not include any headers. |
| 386 | + // so multiColumnView will not include any headers. |
| 387 | var numHeaders = 0; |
| 388 | - var header = findChild(multiColumnView, "Header0"); |
| 389 | + var header = get_header(0); |
| 390 | verify(header !== null, "No header found!"); |
| 391 | while (header !== null) { |
| 392 | numHeaders++; |
| 393 | @@ -121,22 +144,37 @@ |
| 394 | return numHeaders; |
| 395 | } |
| 396 | |
| 397 | + function get_back_button_visible(column) { |
| 398 | + var header = get_header(column); |
| 399 | + var back_button = findChild(header, "backButton"); |
| 400 | + return back_button.visible; |
| 401 | + } |
| 402 | + |
| 403 | function cleanup() { |
| 404 | - multiColumnView.width = root.width; |
| 405 | - multiColumnView.height = root.height; |
| 406 | multiColumnView.removePages(rootPage); |
| 407 | + resize_full_width(); |
| 408 | } |
| 409 | |
| 410 | - function test_number_of_headers_equals_number_of_columns() { |
| 411 | - multiColumnView.width = units.gu(40); |
| 412 | - compare(get_number_of_columns(), 1, "Number of columns is not 1."); |
| 413 | - compare(get_number_of_headers(), 1, "Number of headers is not 1."); |
| 414 | - multiColumnView.width = root.width; |
| 415 | + function test_number_of_headers_equals_number_of_columns_wide() { |
| 416 | + if (root.columns !== 2) { |
| 417 | + skip("Only for wide view."); |
| 418 | + } |
| 419 | compare(get_number_of_columns(), 2, "Number of columns is not 2."); |
| 420 | compare(get_number_of_headers(), 2, "Number of headers is not 2."); |
| 421 | } |
| 422 | |
| 423 | - function test_header_configuration_equals_column_page_configuration() { |
| 424 | + function test_number_of_headers_equals_number_of_columns_narrow() { |
| 425 | + if (root.columns !== 1) { |
| 426 | + resize_single_column_width(); |
| 427 | + } |
| 428 | + compare(get_number_of_columns(), 1, "Number of columns is not 1 on narrow screen."); |
| 429 | + compare(get_number_of_headers(), 1, "Number of headers is not 1 on narrow screen."); |
| 430 | + } |
| 431 | + |
| 432 | + function test_header_configuration_equals_column_page_configuration_wide() { |
| 433 | + if (root.columns !== 2) { |
| 434 | + skip("Only for wide view."); |
| 435 | + } |
| 436 | compare(get_number_of_headers(), 2, "Number of headers is not 2 initially."); |
| 437 | compare(get_header(0).config, rootPage.head, |
| 438 | "First column header is not initialized with primaryPage header config."); |
| 439 | @@ -162,9 +200,33 @@ |
| 440 | "Second column header is not reverted properly."); |
| 441 | } |
| 442 | |
| 443 | + function test_header_configuration_equals_column_page_configuration_narrow() { |
| 444 | + if (root.columns !== 1) { |
| 445 | + resize_single_column_width(); |
| 446 | + } |
| 447 | + compare(get_number_of_headers(), 1, "Number of headers is not 1."); |
| 448 | + compare(get_header(0).config, rootPage.head, |
| 449 | + "First column header is not initialized with primaryPage header config."); |
| 450 | + |
| 451 | + multiColumnView.addPageToCurrentColumn(rootPage, leftPage); |
| 452 | + compare(get_header(0).config, leftPage.head, |
| 453 | + "Single column header is not updated properly."); |
| 454 | + multiColumnView.removePages(leftPage); |
| 455 | + compare(get_header(0).config, rootPage.head, |
| 456 | + "Single column header is not reverted properly."); |
| 457 | + |
| 458 | + multiColumnView.addPageToNextColumn(rootPage, rightPage); |
| 459 | + compare(get_header(0).config, rightPage.head, |
| 460 | + "Single column header is not updated properly when adding to next column."); |
| 461 | + multiColumnView.removePages(rightPage); |
| 462 | + compare(get_header(0).config, rootPage.head, |
| 463 | + "Single column header is not reverted properly after adding to next column."); |
| 464 | + } |
| 465 | + |
| 466 | function test_header_title_for_external_page() { |
| 467 | multiColumnView.addPageToNextColumn(rootPage, Qt.resolvedUrl("MyExternalPage.qml")); |
| 468 | - compare(get_header(1).config.title, "Page from QML file", |
| 469 | + var n = root.columns === 2 ? 1 : 0 |
| 470 | + compare(get_header(n).config.title, "Page from QML file", |
| 471 | "Adding external Page does not update the header title."); |
| 472 | } |
| 473 | |
| 474 | @@ -196,5 +258,102 @@ |
| 475 | " height is not correctly reverted after removing Page with sections."); |
| 476 | } |
| 477 | } |
| 478 | + |
| 479 | + function test_back_button_wide() { |
| 480 | + if (root.columns !== 2) { |
| 481 | + skip("Only for wide view."); |
| 482 | + } |
| 483 | + // A is the first column, B is the second column. |
| 484 | + // A:i, B:j = i pages in A, j pages in B. |
| 485 | + |
| 486 | + // primary page has no back button |
| 487 | + // A:1, B:0 |
| 488 | + compare(get_back_button_visible(0), false, |
| 489 | + "Back button is visible for primary page."); |
| 490 | + multiColumnView.addPageToCurrentColumn(rootPage, leftPage); |
| 491 | + // A:2, B:0 |
| 492 | + compare(get_back_button_visible(0), true, |
| 493 | + "Adding page 2 to column A does not show back button."); |
| 494 | + |
| 495 | + multiColumnView.removePages(leftPage); |
| 496 | + // A:1, B:0 |
| 497 | + compare(get_back_button_visible(0), false, |
| 498 | + "Removing page 2 from column A does not hide back button."); |
| 499 | + |
| 500 | + multiColumnView.addPageToNextColumn(rootPage, rightPage); |
| 501 | + // A:1, B:1 |
| 502 | + compare(get_back_button_visible(0), false, |
| 503 | + "Adding page 1 to column B shows back button in column A."); |
| 504 | + compare(get_back_button_visible(1), false, |
| 505 | + "Adding page 1 to column B shows back button in column B."); |
| 506 | + |
| 507 | + multiColumnView.addPageToCurrentColumn(rootPage, leftPage); |
| 508 | + // A:2, B:1 |
| 509 | + compare(get_back_button_visible(0), true, |
| 510 | + "Adding page 2 to column A not show back button when column B has a page."); |
| 511 | + compare(get_back_button_visible(1), false, |
| 512 | + "Adding page 2 to column A shows back button in column B."); |
| 513 | + multiColumnView.removePages(leftPage); |
| 514 | + // A:1, B:1 |
| 515 | + |
| 516 | + multiColumnView.addPageToCurrentColumn(rightPage, sectionsPage); |
| 517 | + // A:1, B:2 |
| 518 | + compare(get_back_button_visible(0), false, |
| 519 | + "Adding page 2 to column B shows back button in column A."); |
| 520 | + compare(get_back_button_visible(1), true, |
| 521 | + "Adding page 2 to column B does not show back button in column B."); |
| 522 | + |
| 523 | + multiColumnView.addPageToCurrentColumn(rootPage, leftPage); |
| 524 | + // A:2, B:2 |
| 525 | + compare(get_back_button_visible(0), true, |
| 526 | + "Adding page 2 to column A does not show back button in column A when column B has 2 pages."); |
| 527 | + compare(get_back_button_visible(1), true, |
| 528 | + "Adding page 2 to column A hides back button in column B."); |
| 529 | + |
| 530 | + multiColumnView.removePages(sectionsPage); |
| 531 | + // A:2, B:1 |
| 532 | + compare(get_back_button_visible(0), true, |
| 533 | + "Removing page 2 from column B hides back button in column A."); |
| 534 | + compare(get_back_button_visible(1), false, |
| 535 | + "Removing page 2 from column B does not hide back button when column A has 2 pages."); |
| 536 | + |
| 537 | + // A weird case that I encountered with manual testing: |
| 538 | + multiColumnView.removePages(rootPage); |
| 539 | + // A:1, B:0 |
| 540 | + multiColumnView.addPageToNextColumn(rootPage, Qt.resolvedUrl("MyExternalPage.qml")); |
| 541 | + // A:1, B:1 |
| 542 | + multiColumnView.addPageToCurrentColumn(rootPage, leftPage); |
| 543 | + // A:2, B:1 |
| 544 | + multiColumnView.addPageToNextColumn(rootPage, rightPage); |
| 545 | + // A:2, B:2 |
| 546 | + multiColumnView.addPageToCurrentColumn(rightPage, sectionsPage); |
| 547 | + // A:2, B:3 |
| 548 | + compare(get_back_button_visible(0), true, |
| 549 | + "No back button on the left with multiple pages per column."); |
| 550 | + compare(get_back_button_visible(1), true, |
| 551 | + "No back button on the right with multiple pages per column."); |
| 552 | + } |
| 553 | + |
| 554 | + function test_back_button_narrow() { |
| 555 | + if (root.columns !== 1) { |
| 556 | + resize_single_column_width(); |
| 557 | + } |
| 558 | + |
| 559 | + compare(get_back_button_visible(0), false, |
| 560 | + "Back button is visible for primary page."); |
| 561 | + multiColumnView.addPageToCurrentColumn(rootPage, leftPage); |
| 562 | + compare(get_back_button_visible(0), true, |
| 563 | + "No back button visible with two pages in single column."); |
| 564 | + multiColumnView.removePages(leftPage); |
| 565 | + compare(get_back_button_visible(0), false, |
| 566 | + "Back button remains visible after removing second page from column."); |
| 567 | + |
| 568 | + multiColumnView.addPageToNextColumn(rootPage, rightPage); |
| 569 | + compare(get_back_button_visible(0), true, |
| 570 | + "No back button visible after pushing to next column when viewing single column."); |
| 571 | + multiColumnView.removePages(rightPage); |
| 572 | + compare(get_back_button_visible(0), false, |
| 573 | + "Back button remains visible after removing page from following column."); |
| 574 | + } |
| 575 | } |
| 576 | } |
| 577 | |
| 578 | === modified file 'tests/unit_x11/tst_components/tst_multicolumnview.qml' |
| 579 | --- tests/unit_x11/tst_components/tst_multicolumnview.qml 2015-07-14 09:42:53 +0000 |
| 580 | +++ tests/unit_x11/tst_components/tst_multicolumnview.qml 2015-07-20 22:43:59 +0000 |
| 581 | @@ -20,12 +20,15 @@ |
| 582 | import Ubuntu.Components 1.3 |
| 583 | |
| 584 | MainView { |
| 585 | - id: test |
| 586 | + id: root |
| 587 | width: units.gu(120) |
| 588 | height: units.gu(71) |
| 589 | |
| 590 | + // 2 on desktop, 1 on phone. |
| 591 | + property int columns: width >= units.gu(80) ? 2 : 1 |
| 592 | + |
| 593 | MultiColumnView { |
| 594 | - id: testView |
| 595 | + id: mcv |
| 596 | width: parent.width |
| 597 | height: parent.height |
| 598 | |
| 599 | @@ -34,6 +37,18 @@ |
| 600 | Page { |
| 601 | id: page1 |
| 602 | title: "Page1" |
| 603 | + Button { |
| 604 | + anchors.centerIn: parent |
| 605 | + text: "Page 2 left" |
| 606 | + } |
| 607 | + |
| 608 | + Column { |
| 609 | + width: parent.width |
| 610 | + Button { |
| 611 | + text: "Page 2" |
| 612 | + onTriggered: mcv.addPageToCurrentColumn(page1, page2); |
| 613 | + } |
| 614 | + } |
| 615 | } |
| 616 | Page { |
| 617 | id: page2 |
| 618 | @@ -56,73 +71,93 @@ |
| 619 | UbuntuTestCase { |
| 620 | when: windowShown |
| 621 | |
| 622 | + function resize_single_column() { |
| 623 | + mcv.width = units.gu(40); |
| 624 | + } |
| 625 | + |
| 626 | + // resize to use the full window width |
| 627 | + function resize_multiple_columns() { |
| 628 | + mcv.width = root.width; |
| 629 | + } |
| 630 | + |
| 631 | function cleanup() { |
| 632 | -// testView.columns = Qt.binding(function() { |
| 633 | -// return test.width > units.gu(100) ? 3 : (test.width > units.gu(80) ? 2 : 1); |
| 634 | -// }); |
| 635 | - testView.width = test.width; |
| 636 | - testView.height = test.height; |
| 637 | - // remove allpages |
| 638 | - testView.removePages(page1); |
| 639 | + resize_multiple_columns(); |
| 640 | + mcv.removePages(page1); |
| 641 | } |
| 642 | |
| 643 | function test_0_API() { |
| 644 | compare(defaults.primaryPage, undefined, "primaryPage not undefined by default"); |
| 645 | } |
| 646 | |
| 647 | - function test_add_to_first_column_data() { |
| 648 | - return [ |
| 649 | - {tag: "null sourcePage, fail", sourcePage: null, page: page2, failMsg: "No sourcePage specified. Page will not be added."}, |
| 650 | - {tag: "valid sourcePage, pass", sourcePage: page1, page: page2, failMsg: ""}, |
| 651 | - ] |
| 652 | - } |
| 653 | - function test_add_to_first_column(data) { |
| 654 | - if (data.failMsg != "") { |
| 655 | - ignoreWarning(data.failMsg); |
| 656 | - } |
| 657 | - |
| 658 | - testView.addPageToCurrentColumn(data.sourcePage, data.page); |
| 659 | - var firstColumn = findChild(testView, "ColumnHolder0"); |
| 660 | - verify(firstColumn); |
| 661 | - if (data.failMsg != "") { |
| 662 | - expectFail(data.tag, "Fail"); |
| 663 | - } |
| 664 | - compare(firstColumn.pageWrapper.object, data.page); |
| 665 | - } |
| 666 | - |
| 667 | - function test_add_to_next_column_data() { |
| 668 | - return [ |
| 669 | - {tag: "null sourcePage, fail", sourcePage: null, page: page2, failMsg: "No sourcePage specified. Page will not be added."}, |
| 670 | - {tag: "valid sourcePage, pass", sourcePage: page1, page: page2, failMsg: ""}, |
| 671 | - ] |
| 672 | - } |
| 673 | - function test_add_to_next_column(data) { |
| 674 | - if (data.failMsg != "") { |
| 675 | - ignoreWarning(data.failMsg); |
| 676 | - } |
| 677 | - |
| 678 | - testView.addPageToNextColumn(data.sourcePage, data.page); |
| 679 | - var secondColumn = findChild(testView, "ColumnHolder1"); |
| 680 | - verify(secondColumn); |
| 681 | - if (data.failMsg != "") { |
| 682 | - expectFail(data.tag, "Fail"); |
| 683 | - } |
| 684 | - verify(secondColumn.pageWrapper); |
| 685 | - } |
| 686 | - |
| 687 | - function test_change_primaryPage() { |
| 688 | + function test_zzz_change_primaryPage() { |
| 689 | + // this prints the warning but still changes the primary page, |
| 690 | + // so the test must be executed last not to mess up the other tests. |
| 691 | ignoreWarning("Cannot change primaryPage after completion."); |
| 692 | - testView.primaryPage = page3; |
| 693 | - } |
| 694 | - |
| 695 | - function test_add_to_same_column_when_source_page_not_in_stack() { |
| 696 | - ignoreWarning("sourcePage must be added to the view to add new page."); |
| 697 | - testView.addPageToCurrentColumn(page2, page3); |
| 698 | - } |
| 699 | - |
| 700 | - function test_add_to_next_column_when_source_page_not_in_stack() { |
| 701 | - ignoreWarning("sourcePage must be added to the view to add new page."); |
| 702 | - testView.addPageToNextColumn(page2, page3); |
| 703 | + mcv.primaryPage = page3; |
| 704 | + } |
| 705 | + |
| 706 | + function test_add_page_when_source_page_not_in_stack() { |
| 707 | + ignoreWarning("sourcePage must be added to the view to add new page."); |
| 708 | + mcv.addPageToCurrentColumn(page2, page3); |
| 709 | + ignoreWarning("sourcePage must be added to the view to add new page."); |
| 710 | + mcv.addPageToNextColumn(page2, page3); |
| 711 | + } |
| 712 | + |
| 713 | + function test_add_page_with_null_sourcePage() { |
| 714 | + ignoreWarning("No sourcePage specified. Page will not be added.") |
| 715 | + mcv.addPageToCurrentColumn(null, page1); |
| 716 | + ignoreWarning("No sourcePage specified. Page will not be added.") |
| 717 | + mcv.addPageToNextColumn(null, page2); |
| 718 | + } |
| 719 | + |
| 720 | + function test_add_same_page_twice() { |
| 721 | + mcv.addPageToCurrentColumn(page1, page2); |
| 722 | + mcv.addPageToCurrentColumn(page2, page3); |
| 723 | + ignoreWarning("Cannot add a Page that was already added."); |
| 724 | + mcv.addPageToCurrentColumn(page3, page2); |
| 725 | + ignoreWarning("Cannot add a Page that was already added."); |
| 726 | + mcv.addPageToNextColumn(page3, page2); |
| 727 | + } |
| 728 | + |
| 729 | + function test_page_visible() { |
| 730 | + // Two columns on desktop, one on phone |
| 731 | + compare(page1.visible, true, "Primary page not initially visible."); |
| 732 | + compare(page2.visible, false, "Page 2 visible before it was added."); |
| 733 | + compare(page3.visible, false, "Page 3 visible before it was added."); |
| 734 | + |
| 735 | + mcv.addPageToCurrentColumn(page1, page2); |
| 736 | + compare(page1.visible, false, "Page still visible after adding new page in current column."); |
| 737 | + compare(page2.visible, true, "Page invisible after adding it to current column."); |
| 738 | + mcv.addPageToNextColumn(page2, page3); |
| 739 | + if (root.columns === 2) { |
| 740 | + compare(page2.visible, true, "Page in first column became invisible after adding to next column."); |
| 741 | + } else { // root.columns === 1 |
| 742 | + compare(page2.visible, false, "Page in single column still visible after adding page to next column."); |
| 743 | + } |
| 744 | + compare(page3.visible, true, "Page invisible after adding it to next column."); |
| 745 | + |
| 746 | + // One column |
| 747 | + resize_single_column(); |
| 748 | + compare(page3.visible, true, "Top page in last column invisible when resizing to one column."); |
| 749 | + compare(page2.visible, false, "Top page in first column visible when resizing to one column."); |
| 750 | + |
| 751 | + mcv.removePages(page3); |
| 752 | + compare(page3.visible, false, "Page 3 visible after it was removed."); |
| 753 | + compare(page2.visible, true, "New top page in single column not visible."); |
| 754 | + |
| 755 | + mcv.removePages(page1); |
| 756 | + compare(page1.visible, true, "Primary page not visible in single column."); |
| 757 | + compare(page2.visible, false, "Page 2 visible while it is not added."); |
| 758 | + mcv.addPageToNextColumn(page1, page4); |
| 759 | + compare(page1.visible, false, "Page remains visible after adding to next column in single column view."); |
| 760 | + compare(page4.visible, true, "Page added to next column with single column view is not visible."); |
| 761 | + |
| 762 | + // Two columns on desktop, one on phone |
| 763 | + resize_multiple_columns(); |
| 764 | + if (root.columns === 2) { |
| 765 | + compare(page1.visible, true, "Page in left column did not become visible when switching to multi-column view."); |
| 766 | + compare(page4.visible, true, "Page in right column became invisible when switching to multi-column view."); |
| 767 | + } |
| 768 | } |
| 769 | } |
| 770 | } |

I appreciate the extensive back button test, it probably wasn't easy to get sorted, but it's looking good. And you added a check for adding the same Page with tests, which I like a lot.
400 + function test_number_ of_headers_ equals_ number_ of_columns_ wide() {
401 + if (root.columns !== 2) {
402 + skip("Only for wide view.");
403 + }
Can we resize the window on an environment where there's no enforced fullscreen and run both narrow and wide tests that way? Otherwise you could only run all tests by changing the window size by hand.