Merge lp:~fboucault/ubuntu-ui-toolkit/fix_tabs_ordering into lp:ubuntu-ui-toolkit
- fix_tabs_ordering
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Robert Bruce Park |
Approved revision: | 866 |
Merged at revision: | 859 |
Proposed branch: | lp:~fboucault/ubuntu-ui-toolkit/fix_tabs_ordering |
Merge into: | lp:ubuntu-ui-toolkit |
Diff against target: |
546 lines (+80/-361) 5 files modified
components.api (+0/-4) modules/Ubuntu/Components/Tabs.qml (+51/-136) tests/resources/navigation/Tabs.qml (+0/-44) tests/unit_x11/tst_components/ExternalTab.qml (+0/-21) tests/unit_x11/tst_components/tst_tabs.qml (+29/-156) |
To merge this branch: | bzr merge lp:~fboucault/ubuntu-ui-toolkit/fix_tabs_ordering |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Needs Fixing | |
Tim Peeters | Approve | ||
Review via email: mp+196619@code.launchpad.net |
Commit message
[Tabs] Fix critical regression where tabs are badly ordered when defined with a Repeater.
Make sure the ordering of the tabs is identical to the ordering of Tabs' children.
Reverted recently introduced Tabs APIs: addTab, insertTab, moveTab, removeTab.
Added workaround for use of Tabs with a Repeater.
Description of the change
Florian Boucault (fboucault) wrote : | # |
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:859
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:861
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
FAILURE: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Tim Peeters (tpeeters) wrote : | # |
missing semicolons ;)
199 + tabsModel.
239 + return (item && item.hasOwnProp
Tim Peeters (tpeeters) wrote : | # |
224 + for (var i = 0; i <= children.length; i++) {
i < children.length
Tim Peeters (tpeeters) wrote : | # |
Unit tests pass on my laptop, except for TextFieldAPI which always fails for me.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:862
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Tim Peeters (tpeeters) wrote : | # |
Loading tests from: /home/tim/
Tests running...
Ran 103 tests in 390.700s
OK
Tim Peeters (tpeeters) wrote : | # |
None of the core apps use the removed API
tim@ideapad:
function refreshSavedTab() {
Tim Peeters (tpeeters) wrote : | # |
All UITK autopilot tests passed on maguro with trusty-proposed #29: https:/
Tim Peeters (tpeeters) wrote : | # |
CI test failed in an unrelated test that doesn't have any tabs in it.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:865
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
More details in the following jenkins job:
http://
Executed test runs:
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
FAILURE: http://
Preview Diff
1 | === modified file 'components.api' | |||
2 | --- components.api 2013-11-23 00:11:29 +0000 | |||
3 | +++ components.api 2013-11-26 00:06:03 +0000 | |||
4 | @@ -225,10 +225,6 @@ | |||
5 | 225 | default property list<Item> tabChildren | 225 | default property list<Item> tabChildren |
6 | 226 | readonly property int count | 226 | readonly property int count |
7 | 227 | signal modelChanged() | 227 | signal modelChanged() |
8 | 228 | function addTab(title, component, params) | ||
9 | 229 | function insertTab(index, title, component, params) | ||
10 | 230 | function moveTab(from, to) | ||
11 | 231 | function removeTab(index) | ||
12 | 232 | modules/Ubuntu/Components/TextArea.qml | 228 | modules/Ubuntu/Components/TextArea.qml |
13 | 233 | StyledItem | 229 | StyledItem |
14 | 234 | property bool highlighted | 230 | property bool highlighted |
15 | 235 | 231 | ||
16 | === modified file 'modules/Ubuntu/Components/Tabs.qml' | |||
17 | --- modules/Ubuntu/Components/Tabs.qml 2013-11-22 14:49:12 +0000 | |||
18 | +++ modules/Ubuntu/Components/Tabs.qml 2013-11-26 00:06:03 +0000 | |||
19 | @@ -200,139 +200,6 @@ | |||
20 | 200 | signal modelChanged() | 200 | signal modelChanged() |
21 | 201 | 201 | ||
22 | 202 | /*! | 202 | /*! |
23 | 203 | Appends a Tab dynamically to the list of tabs. The \a title specifies the | ||
24 | 204 | title of the Tab. The \a component can be either a Component, a URL to | ||
25 | 205 | the Tab component to be loaded or an instance of a pre-declared tab that | ||
26 | 206 | has been previously removed. The Tab's title will be replaced with the given | ||
27 | 207 | \a title, unless if the given value is empty string or undefined. The optional | ||
28 | 208 | \a params defines parameters passed to the Tab. | ||
29 | 209 | Returns the instance of the added Tab. | ||
30 | 210 | */ | ||
31 | 211 | function addTab(title, component, params) { | ||
32 | 212 | return insertTab(count, title, component, params); | ||
33 | 213 | } | ||
34 | 214 | |||
35 | 215 | /*! | ||
36 | 216 | Inserts a Tab at the given index. If the \a index is less or equal than 0, | ||
37 | 217 | the Tab will be added to the front, and to the end of the tab stack if the | ||
38 | 218 | \a index is greater than \l count. \a title, \a component and \a params | ||
39 | 219 | are used in the same way as in \l addTab(). Returns the instance of the | ||
40 | 220 | inserted Tab. | ||
41 | 221 | */ | ||
42 | 222 | function insertTab(index, title, component, params) { | ||
43 | 223 | // check if the given component is a Tab instance | ||
44 | 224 | var tab = null; | ||
45 | 225 | if (component && component.hasOwnProperty("page") && component.hasOwnProperty("__protected")) { | ||
46 | 226 | // dynamically added components are destroyed upon removal, so | ||
47 | 227 | // in case we get a Tab as parameter, we can only have a predeclared one | ||
48 | 228 | // therefore we simply restore the default state of the removedFromTabs property | ||
49 | 229 | // and return the instance | ||
50 | 230 | if (!component.__protected.removedFromTabs) { | ||
51 | 231 | // exit if the Tab is not removed | ||
52 | 232 | return null; | ||
53 | 233 | } | ||
54 | 234 | |||
55 | 235 | component.__protected.removedFromTabs = false; | ||
56 | 236 | tab = component; | ||
57 | 237 | } else { | ||
58 | 238 | var tabComponent = null; | ||
59 | 239 | if (typeof component === "string") { | ||
60 | 240 | tabComponent = Qt.createComponent(component); | ||
61 | 241 | } else { | ||
62 | 242 | tabComponent = component; | ||
63 | 243 | } | ||
64 | 244 | if (tabComponent.status === Component.Error) { | ||
65 | 245 | console.error(tabComponent.errorString()); | ||
66 | 246 | return null; | ||
67 | 247 | } | ||
68 | 248 | tab = tabComponent.createObject(); | ||
69 | 249 | tab.__protected.dynamic = true; | ||
70 | 250 | } | ||
71 | 251 | |||
72 | 252 | // fix title | ||
73 | 253 | if (title !== undefined && title !== "") { | ||
74 | 254 | tab.title = title; | ||
75 | 255 | } | ||
76 | 256 | |||
77 | 257 | // insert the created tab into the model | ||
78 | 258 | index = MathUtils.clamp(index, 0, count); | ||
79 | 259 | tab.__protected.inserted = true; | ||
80 | 260 | tab.__protected.index = index; | ||
81 | 261 | tabsModel.insert(index, tabsModel.listModel(tab)); | ||
82 | 262 | tabsModel.reindex(index); | ||
83 | 263 | tab.parent = tabStack; | ||
84 | 264 | if (tabs.selectedTabIndex >= index) { | ||
85 | 265 | // move the selected index to the next index | ||
86 | 266 | tabs.selectedTabIndex += 1; | ||
87 | 267 | } else { | ||
88 | 268 | internal.sync(); | ||
89 | 269 | } | ||
90 | 270 | return tab; | ||
91 | 271 | } | ||
92 | 272 | |||
93 | 273 | /*! | ||
94 | 274 | Moves the tab from the given \a from position to the position given in \a to. | ||
95 | 275 | Returns true if the indexes were in 0..\l count - 1 boundary and if the operation | ||
96 | 276 | succeeds, and false otherwise. The \l selectedTabIndex is updated if it is | ||
97 | 277 | affected by the move (it is equal with \a from or falls between \a from and | ||
98 | 278 | \a to indexes). | ||
99 | 279 | */ | ||
100 | 280 | function moveTab(from, to) { | ||
101 | 281 | if (from < 0 || from >= count || to < 0 || to >= count || from === to) return false; | ||
102 | 282 | var tabFrom = tabsModel.get(from).tab; | ||
103 | 283 | var tabTo = tabsModel.get(to).tab; | ||
104 | 284 | |||
105 | 285 | // move tab | ||
106 | 286 | tabsModel.move(from, to, 1); | ||
107 | 287 | tabsModel.reindex(); | ||
108 | 288 | |||
109 | 289 | // fix selected tab | ||
110 | 290 | if (selectedTabIndex === from) { | ||
111 | 291 | selectedTabIndex = to; | ||
112 | 292 | } else if (selectedTabIndex <= to && selectedTabIndex >= from) { | ||
113 | 293 | selectedTabIndex -= 1; | ||
114 | 294 | } else { | ||
115 | 295 | internal.sync(); | ||
116 | 296 | } | ||
117 | 297 | |||
118 | 298 | return true; | ||
119 | 299 | } | ||
120 | 300 | |||
121 | 301 | /*! | ||
122 | 302 | Removes the Tab from the given \a index. Returns true if the \a index falls | ||
123 | 303 | into 0..\l count - 1 boundary and the operation succeeds, and false on error. | ||
124 | 304 | The function removes also the pre-declared tabs. These can be added back using | ||
125 | 305 | \l addTab or \l insertTab by specifying the instance of the Tab to be added as | ||
126 | 306 | component. The \l selectedTabIndex is updated if is affected by the removal | ||
127 | 307 | (it is identical or greater than the tab index to be removed). | ||
128 | 308 | */ | ||
129 | 309 | function removeTab(index) { | ||
130 | 310 | if (index < 0 || index >= count) return false; | ||
131 | 311 | var tab = tabsModel.get(index).tab; | ||
132 | 312 | var activeIndex = (selectedTabIndex >= index) ? MathUtils.clamp(selectedTabIndex, 0, count - 2) : -1; | ||
133 | 313 | |||
134 | 314 | tabsModel.remove(index); | ||
135 | 315 | tabsModel.reindex(); | ||
136 | 316 | // move active tab if needed | ||
137 | 317 | if (activeIndex >= 0) { | ||
138 | 318 | selectedTabIndex = activeIndex; | ||
139 | 319 | } | ||
140 | 320 | |||
141 | 321 | if (tab.__protected.dynamic) { | ||
142 | 322 | tab.destroy(); | ||
143 | 323 | } else { | ||
144 | 324 | // pre-declared tab, mark it as removed, so we don't update it next time | ||
145 | 325 | // the tabs stack children is updated | ||
146 | 326 | tab.__protected.removedFromTabs = true; | ||
147 | 327 | } | ||
148 | 328 | |||
149 | 329 | if (activeIndex < 0) { | ||
150 | 330 | internal.sync(); | ||
151 | 331 | } | ||
152 | 332 | return true; | ||
153 | 333 | } | ||
154 | 334 | |||
155 | 335 | /*! | ||
156 | 336 | \internal | 203 | \internal |
157 | 337 | required by TabsStyle | 204 | required by TabsStyle |
158 | 338 | */ | 205 | */ |
159 | @@ -344,19 +211,30 @@ | |||
160 | 344 | } | 211 | } |
161 | 345 | 212 | ||
162 | 346 | function updateTabList(tabsList) { | 213 | function updateTabList(tabsList) { |
163 | 214 | var offset = 0; | ||
164 | 215 | var tabIndex; | ||
165 | 347 | for (var i in tabsList) { | 216 | for (var i in tabsList) { |
166 | 348 | var tab = tabsList[i]; | 217 | var tab = tabsList[i]; |
167 | 349 | if (internal.isTab(tab)) { | 218 | if (internal.isTab(tab)) { |
168 | 219 | tabIndex = i - offset; | ||
169 | 350 | // make sure we have the right parent | 220 | // make sure we have the right parent |
170 | 351 | tab.parent = tabStack; | 221 | tab.parent = tabStack; |
171 | 352 | 222 | ||
172 | 353 | if (!tab.__protected.inserted) { | 223 | if (!tab.__protected.inserted) { |
174 | 354 | tab.__protected.index = count; | 224 | tab.__protected.index = tabIndex; |
175 | 355 | tab.__protected.inserted = true; | 225 | tab.__protected.inserted = true; |
177 | 356 | append(listModel(tab)); | 226 | insert(tabIndex, listModel(tab)); |
178 | 357 | } else if (!tab.__protected.removedFromTabs && tabsModel.count > tab.index) { | 227 | } else if (!tab.__protected.removedFromTabs && tabsModel.count > tab.index) { |
179 | 358 | get(tab.index).title = tab.title; | 228 | get(tab.index).title = tab.title; |
180 | 359 | } | 229 | } |
181 | 230 | |||
182 | 231 | // always makes sure that tabsModel has the same order as tabsList | ||
183 | 232 | move(tab.__protected.index, tabIndex, 1); | ||
184 | 233 | reindex(); | ||
185 | 234 | } else { | ||
186 | 235 | // keep track of children that are not tabs so that we compute | ||
187 | 236 | // the right index for actual tabs | ||
188 | 237 | offset += 1; | ||
189 | 360 | } | 238 | } |
190 | 361 | } | 239 | } |
191 | 362 | internal.sync(); | 240 | internal.sync(); |
192 | @@ -379,7 +257,40 @@ | |||
193 | 379 | anchors.fill: parent | 257 | anchors.fill: parent |
194 | 380 | id: tabStack | 258 | id: tabStack |
195 | 381 | 259 | ||
197 | 382 | onChildrenChanged: tabsModel.updateTabList(children) | 260 | onChildrenChanged: { |
198 | 261 | connectToRepeaters(); | ||
199 | 262 | tabsModel.updateTabList(children) | ||
200 | 263 | } | ||
201 | 264 | |||
202 | 265 | /* When inserting a delegate into its parent the Repeater does it in 3 | ||
203 | 266 | steps: | ||
204 | 267 | 1) sets the parent of the delegate thus inserting it in the list of | ||
205 | 268 | children in a position that does not correspond to the position of | ||
206 | 269 | the corresponding item in the model. At that point the | ||
207 | 270 | childrenChanged() signal is emitted. | ||
208 | 271 | 2) reorder the delegate to match the position of the corresponding item | ||
209 | 272 | in the model. | ||
210 | 273 | 3) emits the itemAdded() signal. | ||
211 | 274 | |||
212 | 275 | We need to update the list of tabs (tabsModel) when the children are in the | ||
213 | 276 | adequate order hence the workaround below. It connects to the itemAdded() | ||
214 | 277 | signal of any repeater it finds and triggers an update of the tabsModel. | ||
215 | 278 | |||
216 | 279 | Somewhat related Qt bug report: | ||
217 | 280 | https://bugreports.qt-project.org/browse/QTBUG-32438 | ||
218 | 281 | */ | ||
219 | 282 | function updateTabsModel() { | ||
220 | 283 | tabsModel.updateTabList(children); | ||
221 | 284 | } | ||
222 | 285 | |||
223 | 286 | function connectToRepeaters() { | ||
224 | 287 | for (var i = 0; i < children.length; i++) { | ||
225 | 288 | var child = children[i]; | ||
226 | 289 | if (internal.isRepeater(child)) { | ||
227 | 290 | child.itemAdded.connect(tabStack.updateTabsModel); | ||
228 | 291 | } | ||
229 | 292 | } | ||
230 | 293 | } | ||
231 | 383 | } | 294 | } |
232 | 384 | 295 | ||
233 | 385 | QtObject { | 296 | QtObject { |
234 | @@ -396,6 +307,10 @@ | |||
235 | 396 | } | 307 | } |
236 | 397 | } | 308 | } |
237 | 398 | 309 | ||
238 | 310 | function isRepeater(item) { | ||
239 | 311 | return (item && item.hasOwnProperty("itemAdded")); | ||
240 | 312 | } | ||
241 | 313 | |||
242 | 399 | function sync() { | 314 | function sync() { |
243 | 400 | if (tabBar && tabBar.__styleInstance && tabBar.__styleInstance.hasOwnProperty("sync")) { | 315 | if (tabBar && tabBar.__styleInstance && tabBar.__styleInstance.hasOwnProperty("sync")) { |
244 | 401 | tabBar.__styleInstance.sync(); | 316 | tabBar.__styleInstance.sync(); |
245 | 402 | 317 | ||
246 | === modified file 'tests/resources/navigation/Tabs.qml' | |||
247 | --- tests/resources/navigation/Tabs.qml 2013-11-22 14:49:12 +0000 | |||
248 | +++ tests/resources/navigation/Tabs.qml 2013-11-26 00:06:03 +0000 | |||
249 | @@ -22,27 +22,6 @@ | |||
250 | 22 | width: 800 | 22 | width: 800 |
251 | 23 | height: 600 | 23 | height: 600 |
252 | 24 | 24 | ||
253 | 25 | Component { | ||
254 | 26 | id: dynamicTab | ||
255 | 27 | Tab { | ||
256 | 28 | title: "Original Title #" + index | ||
257 | 29 | page: Page { | ||
258 | 30 | Row { | ||
259 | 31 | anchors.centerIn: parent | ||
260 | 32 | spacing: 5 | ||
261 | 33 | Button { | ||
262 | 34 | text: "Delete" | ||
263 | 35 | onClicked: tabs.removeTab(index) | ||
264 | 36 | } | ||
265 | 37 | Button { | ||
266 | 38 | text: "Move to 0" | ||
267 | 39 | onClicked: tabs.moveTab(index, 0) | ||
268 | 40 | } | ||
269 | 41 | } | ||
270 | 42 | } | ||
271 | 43 | } | ||
272 | 44 | } | ||
273 | 45 | |||
274 | 46 | Tabs { | 25 | Tabs { |
275 | 47 | id: tabs | 26 | id: tabs |
276 | 48 | selectedTabIndex: 0 | 27 | selectedTabIndex: 0 |
277 | @@ -77,29 +56,6 @@ | |||
278 | 77 | iconSource: "call_icon.png" | 56 | iconSource: "call_icon.png" |
279 | 78 | onTriggered: print("action triggered") | 57 | onTriggered: print("action triggered") |
280 | 79 | } | 58 | } |
281 | 80 | ToolbarButton { | ||
282 | 81 | text: "ADD" | ||
283 | 82 | iconSource: "call_icon.png" | ||
284 | 83 | onTriggered: tabs.addTab("", dynamicTab) | ||
285 | 84 | } | ||
286 | 85 | ToolbarButton { | ||
287 | 86 | text: "INSERT" | ||
288 | 87 | iconSource: "call_icon.png" | ||
289 | 88 | onTriggered: tabs.insertTab(1, "INSERTED", dynamicTab) | ||
290 | 89 | } | ||
291 | 90 | ToolbarButton { | ||
292 | 91 | text: "move me next" | ||
293 | 92 | iconSource: "call_icon.png" | ||
294 | 93 | onTriggered: { | ||
295 | 94 | var i = simpleTab.index; | ||
296 | 95 | tabs.moveTab(i, i + 1); | ||
297 | 96 | } | ||
298 | 97 | } | ||
299 | 98 | ToolbarButton { | ||
300 | 99 | text: "delete 0" | ||
301 | 100 | iconSource: "call_icon.png" | ||
302 | 101 | onTriggered: tabs.removeTab(0) | ||
303 | 102 | } | ||
304 | 103 | } | 59 | } |
305 | 104 | } | 60 | } |
306 | 105 | } | 61 | } |
307 | 106 | 62 | ||
308 | === removed file 'tests/unit_x11/tst_components/ExternalTab.qml' | |||
309 | --- tests/unit_x11/tst_components/ExternalTab.qml 2013-11-13 10:18:28 +0000 | |||
310 | +++ tests/unit_x11/tst_components/ExternalTab.qml 1970-01-01 00:00:00 +0000 | |||
311 | @@ -1,21 +0,0 @@ | |||
312 | 1 | /* | ||
313 | 2 | * Copyright 2012 Canonical Ltd. | ||
314 | 3 | * | ||
315 | 4 | * This program is free software; you can redistribute it and/or modify | ||
316 | 5 | * it under the terms of the GNU Lesser General Public License as published by | ||
317 | 6 | * the Free Software Foundation; version 3. | ||
318 | 7 | * | ||
319 | 8 | * This program is distributed in the hope that it will be useful, | ||
320 | 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
321 | 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
322 | 11 | * GNU Lesser General Public License for more details. | ||
323 | 12 | * | ||
324 | 13 | * You should have received a copy of the GNU Lesser General Public License | ||
325 | 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
326 | 15 | */ | ||
327 | 16 | |||
328 | 17 | import QtQuick 2.0 | ||
329 | 18 | import Ubuntu.Components 0.1 | ||
330 | 19 | |||
331 | 20 | Tab { | ||
332 | 21 | } | ||
333 | 22 | 0 | ||
334 | === modified file 'tests/unit_x11/tst_components/tst_tabs.qml' | |||
335 | --- tests/unit_x11/tst_components/tst_tabs.qml 2013-11-13 10:18:28 +0000 | |||
336 | +++ tests/unit_x11/tst_components/tst_tabs.qml 2013-11-26 00:06:03 +0000 | |||
337 | @@ -26,13 +26,6 @@ | |||
338 | 26 | id: emptyTabs | 26 | id: emptyTabs |
339 | 27 | } | 27 | } |
340 | 28 | 28 | ||
341 | 29 | Component { | ||
342 | 30 | id: dynamicTab | ||
343 | 31 | Tab{ | ||
344 | 32 | title: "OriginalTitle" | ||
345 | 33 | } | ||
346 | 34 | } | ||
347 | 35 | |||
348 | 36 | MainView { | 29 | MainView { |
349 | 37 | id: mainView | 30 | id: mainView |
350 | 38 | anchors.fill: parent | 31 | anchors.fill: parent |
351 | @@ -107,12 +100,40 @@ | |||
352 | 107 | } | 100 | } |
353 | 108 | } | 101 | } |
354 | 109 | 102 | ||
356 | 110 | 103 | Tabs { | |
357 | 104 | id: tabsWithRepeater | ||
358 | 105 | ListModel { | ||
359 | 106 | id: inputModel | ||
360 | 107 | Component.onCompleted: { | ||
361 | 108 | append({ "name": "tab 1" }); | ||
362 | 109 | insert(0, { "name": "tab 0" }); | ||
363 | 110 | append({ "name": "tab 3" }); | ||
364 | 111 | insert(2, { "name": "tab 2" }); | ||
365 | 112 | } | ||
366 | 113 | } | ||
367 | 114 | Repeater { | ||
368 | 115 | id: tabsRepeater | ||
369 | 116 | model: inputModel | ||
370 | 117 | Tab { | ||
371 | 118 | title: name | ||
372 | 119 | } | ||
373 | 120 | } | ||
374 | 121 | } | ||
375 | 111 | 122 | ||
376 | 112 | TestCase { | 123 | TestCase { |
377 | 113 | name: "TabsAPI" | 124 | name: "TabsAPI" |
378 | 114 | when: windowShown | 125 | when: windowShown |
379 | 115 | 126 | ||
380 | 127 | function test_tabOrder_bug1253804() { | ||
381 | 128 | var tabsModel = tabsWithRepeater.tabBar.model; | ||
382 | 129 | |||
383 | 130 | compare(tabsRepeater.count, inputModel.count, "Incorrect number of tabs in Tabs"); | ||
384 | 131 | compare(tabsModel.count, tabsRepeater.count, "Incorrect number of tabs in TabBar"); | ||
385 | 132 | for (var i=0; i < tabsModel.count; i++) { | ||
386 | 133 | compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i); | ||
387 | 134 | } | ||
388 | 135 | } | ||
389 | 136 | |||
390 | 116 | function test_emptyTabs() { | 137 | function test_emptyTabs() { |
391 | 117 | compare(emptyTabs.selectedTabIndex, -1, "The default value for selectedTabIndex is -1 when there are no tabs"); | 138 | compare(emptyTabs.selectedTabIndex, -1, "The default value for selectedTabIndex is -1 when there are no tabs"); |
392 | 118 | compare(emptyTabs.selectedTab, null, "The default selected tab is null when there are no tabs"); | 139 | compare(emptyTabs.selectedTab, null, "The default selected tab is null when there are no tabs"); |
393 | @@ -191,153 +212,5 @@ | |||
394 | 191 | mouseClick(button, units.gu(1), units.gu(1), Qt.LeftButton); | 212 | mouseClick(button, units.gu(1), units.gu(1), Qt.LeftButton); |
395 | 192 | compare(tabs.tabBar.selectionMode, false, "Tab bar deactivated by interacting with the page contents"); | 213 | compare(tabs.tabBar.selectionMode, false, "Tab bar deactivated by interacting with the page contents"); |
396 | 193 | } | 214 | } |
397 | 194 | |||
398 | 195 | function test_z_addTab() { | ||
399 | 196 | var newTab = tabs.addTab("Dynamic Tab", dynamicTab); | ||
400 | 197 | compare((newTab !== null), true, "tab added"); | ||
401 | 198 | compare(newTab.active, false, "the inserted tab is inactive"); | ||
402 | 199 | compare(newTab.index, tabs.count - 1, "the tab is the last one"); | ||
403 | 200 | } | ||
404 | 201 | |||
405 | 202 | function test_z_addExternalTab() { | ||
406 | 203 | var newTab = tabs.addTab("External Tab", Qt.resolvedUrl("ExternalTab.qml")); | ||
407 | 204 | compare((newTab !== null), true, "tab added"); | ||
408 | 205 | compare(newTab.active, false, "the inserted tab is inactive"); | ||
409 | 206 | compare(newTab.index, tabs.count - 1, "the tab is the last one"); | ||
410 | 207 | } | ||
411 | 208 | |||
412 | 209 | function test_z_addTabWithDefaultTitle() { | ||
413 | 210 | var newTab = tabs.addTab("", dynamicTab); | ||
414 | 211 | compare((newTab !== null), true, "tab added"); | ||
415 | 212 | compare(newTab.title, "OriginalTitle", "tab created with original title"); | ||
416 | 213 | } | ||
417 | 214 | |||
418 | 215 | function test_z_insertTab() { | ||
419 | 216 | var tabIndex = Math.ceil(tabs.count / 2); | ||
420 | 217 | var newTab = tabs.insertTab(tabIndex, "Inserted tab", dynamicTab); | ||
421 | 218 | compare((newTab !== null), true, "tab inserted"); | ||
422 | 219 | compare(newTab.index, tabIndex, "this is the first tab"); | ||
423 | 220 | compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); | ||
424 | 221 | } | ||
425 | 222 | |||
426 | 223 | function test_z_insertExternalTab() { | ||
427 | 224 | var tabIndex = Math.ceil(tabs.count / 2); | ||
428 | 225 | var newTab = tabs.insertTab(tabIndex, "Inserted External tab", Qt.resolvedUrl("ExternalTab.qml")); | ||
429 | 226 | compare((newTab !== null), true, "tab inserted"); | ||
430 | 227 | compare(newTab.index, tabIndex, "this is the first tab"); | ||
431 | 228 | compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); | ||
432 | 229 | } | ||
433 | 230 | |||
434 | 231 | function test_z_insertTabAtSelectedIndex() { | ||
435 | 232 | tabs.selectedTabIndex = 1; | ||
436 | 233 | var tabIndex = tabs.selectedTabIndex - 1; | ||
437 | 234 | var newTab = tabs.insertTab(tabIndex, "InsertedAtSelected tab", dynamicTab); | ||
438 | 235 | compare((newTab !== null), true, "tab inserted"); | ||
439 | 236 | compare(newTab.index, tabIndex, "inserted at selected tab"); | ||
440 | 237 | compare(tabs.selectedTabIndex != (tabIndex + 1), true, "it is not the selected tab"); | ||
441 | 238 | } | ||
442 | 239 | |||
443 | 240 | function test_z_insertTabFront() { | ||
444 | 241 | var newTab = tabs.insertTab(-1, "PreTab", dynamicTab); | ||
445 | 242 | compare(newTab !== null, true, "pre-tab inserted"); | ||
446 | 243 | compare(newTab.index, 0, "this is the new first tab"); | ||
447 | 244 | compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); | ||
448 | 245 | } | ||
449 | 246 | |||
450 | 247 | function test_z_insertTabEnd() { | ||
451 | 248 | var newTab = tabs.insertTab(tabs.count, "PostTab", dynamicTab); | ||
452 | 249 | compare(newTab !== null, true, "post-tab inserted"); | ||
453 | 250 | compare(newTab.index, tabs.count - 1, "thsi is the new last tab"); | ||
454 | 251 | compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one"); | ||
455 | 252 | } | ||
456 | 253 | |||
457 | 254 | function test_z_insertTabAndActivate() { | ||
458 | 255 | var newTab = tabs.addTab("Inserted tab", dynamicTab); | ||
459 | 256 | compare((newTab !== null), true, "tab inserted"); | ||
460 | 257 | compare(newTab.index, tabs.count - 1, "the tab is the last one"); | ||
461 | 258 | tabs.selectedTabIndex = newTab.index; | ||
462 | 259 | compare(tabs.selectedTab, newTab, "the inserted tab is selected"); | ||
463 | 260 | compare(newTab.active, true, "the new tab is active"); | ||
464 | 261 | } | ||
465 | 262 | |||
466 | 263 | function test_z_moveTab() { | ||
467 | 264 | var selectedIndex = tabs.count - 1; | ||
468 | 265 | tabs.selectedTabIndex = selectedIndex; | ||
469 | 266 | compare(tabs.moveTab(0, selectedIndex), true, "first tab moved to last"); | ||
470 | 267 | compare(tabs.selectedTabIndex, selectedIndex - 1, "the selected index moved backwards"); | ||
471 | 268 | tabs.selectedTabIndex = selectedIndex = 0; | ||
472 | 269 | compare(tabs.moveTab(selectedIndex, selectedIndex + 1), true, "selected tab moved as next"); | ||
473 | 270 | compare(tabs.selectedTabIndex, selectedIndex + 1, "the selected index moved forewards"); | ||
474 | 271 | } | ||
475 | 272 | |||
476 | 273 | function test_z_moveSelectedTab() { | ||
477 | 274 | tabs.selectedTabIndex = 0; | ||
478 | 275 | tabs.moveTab(0, 1); | ||
479 | 276 | compare(tabs.selectedTabIndex, 1, "selected tab moved"); | ||
480 | 277 | } | ||
481 | 278 | |||
482 | 279 | function test_z_moveTabFail() { | ||
483 | 280 | compare(tabs.moveTab(-1, tabs.count - 1), false, "from-parameter out of range"); | ||
484 | 281 | compare(tabs.moveTab(0, tabs.count), false, "to-parameter out of range"); | ||
485 | 282 | } | ||
486 | 283 | |||
487 | 284 | function test_z_removeTab() { | ||
488 | 285 | compare(tabs.removeTab(tabs.count - 1), true, "last tab removed"); | ||
489 | 286 | tabs.selectedTabIndex = 0; | ||
490 | 287 | compare(tabs.removeTab(0), true, "active tab removed"); | ||
491 | 288 | compare(tabs.selectedTabIndex, 0, "the next tab is selected") | ||
492 | 289 | } | ||
493 | 290 | |||
494 | 291 | function test_z_removeTabAfterActiveTab() { | ||
495 | 292 | var activeTab = tabs.count - 2; | ||
496 | 293 | tabs.selectedTabIndex = activeTab; | ||
497 | 294 | compare(tabs.removeTab(tabs.count - 1), true, "last tab removed"); | ||
498 | 295 | compare(tabs.selectedTabIndex, activeTab, "the selected tab wasn't moved"); | ||
499 | 296 | } | ||
500 | 297 | |||
501 | 298 | function test_z_removeTabBeforeActiveTab() { | ||
502 | 299 | var activeTab = tabs.count - 1; | ||
503 | 300 | tabs.selectedTabIndex = activeTab; | ||
504 | 301 | compare(tabs.removeTab(0), true, "first tab removed"); | ||
505 | 302 | compare(tabs.selectedTabIndex, activeTab - 1, "the selected tab index decreased"); | ||
506 | 303 | } | ||
507 | 304 | |||
508 | 305 | function test_z_removeActiveTab() { | ||
509 | 306 | tabs.selectedTabIndex = 1; | ||
510 | 307 | compare(tabs.removeTab(1), true, "selected tab removed"); | ||
511 | 308 | compare(tabs.selectedTabIndex, 1, "selected tab is next"); | ||
512 | 309 | |||
513 | 310 | tabs.selectedTabIndex = tabs.count - 1; | ||
514 | 311 | compare(tabs.removeTab(tabs.count - 1), true, "last tab removed"); | ||
515 | 312 | compare(tabs.selectedTabIndex, tabs.count - 1, "selected tab moved to last item"); | ||
516 | 313 | } | ||
517 | 314 | |||
518 | 315 | function test_zz_addTabAfterCleaningUpTabs() { | ||
519 | 316 | while (tabs.count > 1) { | ||
520 | 317 | tabs.removeTab(0); | ||
521 | 318 | } | ||
522 | 319 | compare(tabs.selectedTabIndex, 0, "the only tab is the selected one"); | ||
523 | 320 | // add a new tab anc check the count (default added tas should not be added anymore | ||
524 | 321 | tabs.addTab("Second tab", dynamicTab); | ||
525 | 322 | compare(tabs.count, 2, "we have two tabs only"); | ||
526 | 323 | } | ||
527 | 324 | |||
528 | 325 | function test_zz_addPredeclaredTab() { | ||
529 | 326 | while (tabs.count > 1) { | ||
530 | 327 | tabs.removeTab(0); | ||
531 | 328 | } | ||
532 | 329 | compare(tabs.selectedTabIndex, 0, "the only tab is the selected one"); | ||
533 | 330 | |||
534 | 331 | // add a predeclared tab back with original title | ||
535 | 332 | compare(tabs.addTab("", tab1), tab1, "tab1 was added back"); | ||
536 | 333 | compare(tab1.title, "tab 1", "with the original title"); | ||
537 | 334 | |||
538 | 335 | // add a predeclared tab back with new title | ||
539 | 336 | compare(tabs.addTab("Original tab", tab2), tab2, "tab2 was added back"); | ||
540 | 337 | compare(tab2.title, "Original tab", "with modified title"); | ||
541 | 338 | |||
542 | 339 | // add a predeclared tab which was added already | ||
543 | 340 | compare(tabs.addTab("", tab1), null, "tab1 is already in tabs"); | ||
544 | 341 | } | ||
545 | 342 | } | 215 | } |
546 | 343 | } | 216 | } |
Unit tests are being added as we speak. Hold off on merging.