Merge lp:~fboucault/ubuntu-ui-toolkit/fix_tabs_ordering into lp:ubuntu-ui-toolkit

Proposed by Florian Boucault
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
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.

To post a comment you must log in.
Revision history for this message
Florian Boucault (fboucault) wrote :

Unit tests are being added as we speak. Hold off on merging.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote :

missing semicolons ;)

199 + tabsModel.updateTabList(children)

239 + return (item && item.hasOwnProperty("itemAdded"))

Revision history for this message
Tim Peeters (tpeeters) wrote :

224 + for (var i = 0; i <= children.length; i++) {

i < children.length

Revision history for this message
Tim Peeters (tpeeters) wrote :

Unit tests pass on my laptop, except for TextFieldAPI which always fails for me.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Tim Peeters (tpeeters) wrote :

Loading tests from: /home/tim/dev/ubuntu-ui-toolkit/fix_tabs_ordering/tests/autopilot

Tests running...

Ran 103 tests in 390.700s
OK

Revision history for this message
Tim Peeters (tpeeters) wrote :

None of the core apps use the removed API

tim@ideapad:~/dev/coreapps$ for qmlfile in $(find . -name "*.qml"); do grep Tab\( $qmlfile; done
            refreshSavedTab()
    function refreshSavedTab() {

Revision history for this message
Tim Peeters (tpeeters) wrote :

All UITK autopilot tests passed on maguro with trusty-proposed #29: https://pastebin.canonical.com/101018/

review: Approve
Revision history for this message
Tim Peeters (tpeeters) wrote :

CI test failed in an unrelated test that doesn't have any tabs in it.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'components.api'
--- components.api 2013-11-23 00:11:29 +0000
+++ components.api 2013-11-26 00:06:03 +0000
@@ -225,10 +225,6 @@
225 default property list<Item> tabChildren225 default property list<Item> tabChildren
226 readonly property int count226 readonly property int count
227 signal modelChanged()227 signal modelChanged()
228 function addTab(title, component, params)
229 function insertTab(index, title, component, params)
230 function moveTab(from, to)
231 function removeTab(index)
232modules/Ubuntu/Components/TextArea.qml228modules/Ubuntu/Components/TextArea.qml
233StyledItem229StyledItem
234 property bool highlighted230 property bool highlighted
235231
=== modified file 'modules/Ubuntu/Components/Tabs.qml'
--- modules/Ubuntu/Components/Tabs.qml 2013-11-22 14:49:12 +0000
+++ modules/Ubuntu/Components/Tabs.qml 2013-11-26 00:06:03 +0000
@@ -200,139 +200,6 @@
200 signal modelChanged()200 signal modelChanged()
201201
202 /*!202 /*!
203 Appends a Tab dynamically to the list of tabs. The \a title specifies the
204 title of the Tab. The \a component can be either a Component, a URL to
205 the Tab component to be loaded or an instance of a pre-declared tab that
206 has been previously removed. The Tab's title will be replaced with the given
207 \a title, unless if the given value is empty string or undefined. The optional
208 \a params defines parameters passed to the Tab.
209 Returns the instance of the added Tab.
210 */
211 function addTab(title, component, params) {
212 return insertTab(count, title, component, params);
213 }
214
215 /*!
216 Inserts a Tab at the given index. If the \a index is less or equal than 0,
217 the Tab will be added to the front, and to the end of the tab stack if the
218 \a index is greater than \l count. \a title, \a component and \a params
219 are used in the same way as in \l addTab(). Returns the instance of the
220 inserted Tab.
221 */
222 function insertTab(index, title, component, params) {
223 // check if the given component is a Tab instance
224 var tab = null;
225 if (component && component.hasOwnProperty("page") && component.hasOwnProperty("__protected")) {
226 // dynamically added components are destroyed upon removal, so
227 // in case we get a Tab as parameter, we can only have a predeclared one
228 // therefore we simply restore the default state of the removedFromTabs property
229 // and return the instance
230 if (!component.__protected.removedFromTabs) {
231 // exit if the Tab is not removed
232 return null;
233 }
234
235 component.__protected.removedFromTabs = false;
236 tab = component;
237 } else {
238 var tabComponent = null;
239 if (typeof component === "string") {
240 tabComponent = Qt.createComponent(component);
241 } else {
242 tabComponent = component;
243 }
244 if (tabComponent.status === Component.Error) {
245 console.error(tabComponent.errorString());
246 return null;
247 }
248 tab = tabComponent.createObject();
249 tab.__protected.dynamic = true;
250 }
251
252 // fix title
253 if (title !== undefined && title !== "") {
254 tab.title = title;
255 }
256
257 // insert the created tab into the model
258 index = MathUtils.clamp(index, 0, count);
259 tab.__protected.inserted = true;
260 tab.__protected.index = index;
261 tabsModel.insert(index, tabsModel.listModel(tab));
262 tabsModel.reindex(index);
263 tab.parent = tabStack;
264 if (tabs.selectedTabIndex >= index) {
265 // move the selected index to the next index
266 tabs.selectedTabIndex += 1;
267 } else {
268 internal.sync();
269 }
270 return tab;
271 }
272
273 /*!
274 Moves the tab from the given \a from position to the position given in \a to.
275 Returns true if the indexes were in 0..\l count - 1 boundary and if the operation
276 succeeds, and false otherwise. The \l selectedTabIndex is updated if it is
277 affected by the move (it is equal with \a from or falls between \a from and
278 \a to indexes).
279 */
280 function moveTab(from, to) {
281 if (from < 0 || from >= count || to < 0 || to >= count || from === to) return false;
282 var tabFrom = tabsModel.get(from).tab;
283 var tabTo = tabsModel.get(to).tab;
284
285 // move tab
286 tabsModel.move(from, to, 1);
287 tabsModel.reindex();
288
289 // fix selected tab
290 if (selectedTabIndex === from) {
291 selectedTabIndex = to;
292 } else if (selectedTabIndex <= to && selectedTabIndex >= from) {
293 selectedTabIndex -= 1;
294 } else {
295 internal.sync();
296 }
297
298 return true;
299 }
300
301 /*!
302 Removes the Tab from the given \a index. Returns true if the \a index falls
303 into 0..\l count - 1 boundary and the operation succeeds, and false on error.
304 The function removes also the pre-declared tabs. These can be added back using
305 \l addTab or \l insertTab by specifying the instance of the Tab to be added as
306 component. The \l selectedTabIndex is updated if is affected by the removal
307 (it is identical or greater than the tab index to be removed).
308 */
309 function removeTab(index) {
310 if (index < 0 || index >= count) return false;
311 var tab = tabsModel.get(index).tab;
312 var activeIndex = (selectedTabIndex >= index) ? MathUtils.clamp(selectedTabIndex, 0, count - 2) : -1;
313
314 tabsModel.remove(index);
315 tabsModel.reindex();
316 // move active tab if needed
317 if (activeIndex >= 0) {
318 selectedTabIndex = activeIndex;
319 }
320
321 if (tab.__protected.dynamic) {
322 tab.destroy();
323 } else {
324 // pre-declared tab, mark it as removed, so we don't update it next time
325 // the tabs stack children is updated
326 tab.__protected.removedFromTabs = true;
327 }
328
329 if (activeIndex < 0) {
330 internal.sync();
331 }
332 return true;
333 }
334
335 /*!
336 \internal203 \internal
337 required by TabsStyle204 required by TabsStyle
338 */205 */
@@ -344,19 +211,30 @@
344 }211 }
345212
346 function updateTabList(tabsList) {213 function updateTabList(tabsList) {
214 var offset = 0;
215 var tabIndex;
347 for (var i in tabsList) {216 for (var i in tabsList) {
348 var tab = tabsList[i];217 var tab = tabsList[i];
349 if (internal.isTab(tab)) {218 if (internal.isTab(tab)) {
219 tabIndex = i - offset;
350 // make sure we have the right parent220 // make sure we have the right parent
351 tab.parent = tabStack;221 tab.parent = tabStack;
352222
353 if (!tab.__protected.inserted) {223 if (!tab.__protected.inserted) {
354 tab.__protected.index = count;224 tab.__protected.index = tabIndex;
355 tab.__protected.inserted = true;225 tab.__protected.inserted = true;
356 append(listModel(tab));226 insert(tabIndex, listModel(tab));
357 } else if (!tab.__protected.removedFromTabs && tabsModel.count > tab.index) {227 } else if (!tab.__protected.removedFromTabs && tabsModel.count > tab.index) {
358 get(tab.index).title = tab.title;228 get(tab.index).title = tab.title;
359 }229 }
230
231 // always makes sure that tabsModel has the same order as tabsList
232 move(tab.__protected.index, tabIndex, 1);
233 reindex();
234 } else {
235 // keep track of children that are not tabs so that we compute
236 // the right index for actual tabs
237 offset += 1;
360 }238 }
361 }239 }
362 internal.sync();240 internal.sync();
@@ -379,7 +257,40 @@
379 anchors.fill: parent257 anchors.fill: parent
380 id: tabStack258 id: tabStack
381259
382 onChildrenChanged: tabsModel.updateTabList(children)260 onChildrenChanged: {
261 connectToRepeaters();
262 tabsModel.updateTabList(children)
263 }
264
265 /* When inserting a delegate into its parent the Repeater does it in 3
266 steps:
267 1) sets the parent of the delegate thus inserting it in the list of
268 children in a position that does not correspond to the position of
269 the corresponding item in the model. At that point the
270 childrenChanged() signal is emitted.
271 2) reorder the delegate to match the position of the corresponding item
272 in the model.
273 3) emits the itemAdded() signal.
274
275 We need to update the list of tabs (tabsModel) when the children are in the
276 adequate order hence the workaround below. It connects to the itemAdded()
277 signal of any repeater it finds and triggers an update of the tabsModel.
278
279 Somewhat related Qt bug report:
280 https://bugreports.qt-project.org/browse/QTBUG-32438
281 */
282 function updateTabsModel() {
283 tabsModel.updateTabList(children);
284 }
285
286 function connectToRepeaters() {
287 for (var i = 0; i < children.length; i++) {
288 var child = children[i];
289 if (internal.isRepeater(child)) {
290 child.itemAdded.connect(tabStack.updateTabsModel);
291 }
292 }
293 }
383 }294 }
384295
385 QtObject {296 QtObject {
@@ -396,6 +307,10 @@
396 }307 }
397 }308 }
398309
310 function isRepeater(item) {
311 return (item && item.hasOwnProperty("itemAdded"));
312 }
313
399 function sync() {314 function sync() {
400 if (tabBar && tabBar.__styleInstance && tabBar.__styleInstance.hasOwnProperty("sync")) {315 if (tabBar && tabBar.__styleInstance && tabBar.__styleInstance.hasOwnProperty("sync")) {
401 tabBar.__styleInstance.sync();316 tabBar.__styleInstance.sync();
402317
=== modified file 'tests/resources/navigation/Tabs.qml'
--- tests/resources/navigation/Tabs.qml 2013-11-22 14:49:12 +0000
+++ tests/resources/navigation/Tabs.qml 2013-11-26 00:06:03 +0000
@@ -22,27 +22,6 @@
22 width: 80022 width: 800
23 height: 60023 height: 600
2424
25 Component {
26 id: dynamicTab
27 Tab {
28 title: "Original Title #" + index
29 page: Page {
30 Row {
31 anchors.centerIn: parent
32 spacing: 5
33 Button {
34 text: "Delete"
35 onClicked: tabs.removeTab(index)
36 }
37 Button {
38 text: "Move to 0"
39 onClicked: tabs.moveTab(index, 0)
40 }
41 }
42 }
43 }
44 }
45
46 Tabs {25 Tabs {
47 id: tabs26 id: tabs
48 selectedTabIndex: 027 selectedTabIndex: 0
@@ -77,29 +56,6 @@
77 iconSource: "call_icon.png"56 iconSource: "call_icon.png"
78 onTriggered: print("action triggered")57 onTriggered: print("action triggered")
79 }58 }
80 ToolbarButton {
81 text: "ADD"
82 iconSource: "call_icon.png"
83 onTriggered: tabs.addTab("", dynamicTab)
84 }
85 ToolbarButton {
86 text: "INSERT"
87 iconSource: "call_icon.png"
88 onTriggered: tabs.insertTab(1, "INSERTED", dynamicTab)
89 }
90 ToolbarButton {
91 text: "move me next"
92 iconSource: "call_icon.png"
93 onTriggered: {
94 var i = simpleTab.index;
95 tabs.moveTab(i, i + 1);
96 }
97 }
98 ToolbarButton {
99 text: "delete 0"
100 iconSource: "call_icon.png"
101 onTriggered: tabs.removeTab(0)
102 }
103 }59 }
104 }60 }
105 }61 }
10662
=== removed file 'tests/unit_x11/tst_components/ExternalTab.qml'
--- tests/unit_x11/tst_components/ExternalTab.qml 2013-11-13 10:18:28 +0000
+++ tests/unit_x11/tst_components/ExternalTab.qml 1970-01-01 00:00:00 +0000
@@ -1,21 +0,0 @@
1/*
2 * Copyright 2012 Canonical Ltd.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; version 3.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU Lesser General Public License for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16
17import QtQuick 2.0
18import Ubuntu.Components 0.1
19
20Tab {
21}
220
=== modified file 'tests/unit_x11/tst_components/tst_tabs.qml'
--- tests/unit_x11/tst_components/tst_tabs.qml 2013-11-13 10:18:28 +0000
+++ tests/unit_x11/tst_components/tst_tabs.qml 2013-11-26 00:06:03 +0000
@@ -26,13 +26,6 @@
26 id: emptyTabs26 id: emptyTabs
27 }27 }
2828
29 Component {
30 id: dynamicTab
31 Tab{
32 title: "OriginalTitle"
33 }
34 }
35
36 MainView {29 MainView {
37 id: mainView30 id: mainView
38 anchors.fill: parent31 anchors.fill: parent
@@ -107,12 +100,40 @@
107 }100 }
108 }101 }
109102
110103 Tabs {
104 id: tabsWithRepeater
105 ListModel {
106 id: inputModel
107 Component.onCompleted: {
108 append({ "name": "tab 1" });
109 insert(0, { "name": "tab 0" });
110 append({ "name": "tab 3" });
111 insert(2, { "name": "tab 2" });
112 }
113 }
114 Repeater {
115 id: tabsRepeater
116 model: inputModel
117 Tab {
118 title: name
119 }
120 }
121 }
111122
112 TestCase {123 TestCase {
113 name: "TabsAPI"124 name: "TabsAPI"
114 when: windowShown125 when: windowShown
115126
127 function test_tabOrder_bug1253804() {
128 var tabsModel = tabsWithRepeater.tabBar.model;
129
130 compare(tabsRepeater.count, inputModel.count, "Incorrect number of tabs in Tabs");
131 compare(tabsModel.count, tabsRepeater.count, "Incorrect number of tabs in TabBar");
132 for (var i=0; i < tabsModel.count; i++) {
133 compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i);
134 }
135 }
136
116 function test_emptyTabs() {137 function test_emptyTabs() {
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");
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");
@@ -191,153 +212,5 @@
191 mouseClick(button, units.gu(1), units.gu(1), Qt.LeftButton);212 mouseClick(button, units.gu(1), units.gu(1), Qt.LeftButton);
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");
193 }214 }
194
195 function test_z_addTab() {
196 var newTab = tabs.addTab("Dynamic Tab", dynamicTab);
197 compare((newTab !== null), true, "tab added");
198 compare(newTab.active, false, "the inserted tab is inactive");
199 compare(newTab.index, tabs.count - 1, "the tab is the last one");
200 }
201
202 function test_z_addExternalTab() {
203 var newTab = tabs.addTab("External Tab", Qt.resolvedUrl("ExternalTab.qml"));
204 compare((newTab !== null), true, "tab added");
205 compare(newTab.active, false, "the inserted tab is inactive");
206 compare(newTab.index, tabs.count - 1, "the tab is the last one");
207 }
208
209 function test_z_addTabWithDefaultTitle() {
210 var newTab = tabs.addTab("", dynamicTab);
211 compare((newTab !== null), true, "tab added");
212 compare(newTab.title, "OriginalTitle", "tab created with original title");
213 }
214
215 function test_z_insertTab() {
216 var tabIndex = Math.ceil(tabs.count / 2);
217 var newTab = tabs.insertTab(tabIndex, "Inserted tab", dynamicTab);
218 compare((newTab !== null), true, "tab inserted");
219 compare(newTab.index, tabIndex, "this is the first tab");
220 compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
221 }
222
223 function test_z_insertExternalTab() {
224 var tabIndex = Math.ceil(tabs.count / 2);
225 var newTab = tabs.insertTab(tabIndex, "Inserted External tab", Qt.resolvedUrl("ExternalTab.qml"));
226 compare((newTab !== null), true, "tab inserted");
227 compare(newTab.index, tabIndex, "this is the first tab");
228 compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
229 }
230
231 function test_z_insertTabAtSelectedIndex() {
232 tabs.selectedTabIndex = 1;
233 var tabIndex = tabs.selectedTabIndex - 1;
234 var newTab = tabs.insertTab(tabIndex, "InsertedAtSelected tab", dynamicTab);
235 compare((newTab !== null), true, "tab inserted");
236 compare(newTab.index, tabIndex, "inserted at selected tab");
237 compare(tabs.selectedTabIndex != (tabIndex + 1), true, "it is not the selected tab");
238 }
239
240 function test_z_insertTabFront() {
241 var newTab = tabs.insertTab(-1, "PreTab", dynamicTab);
242 compare(newTab !== null, true, "pre-tab inserted");
243 compare(newTab.index, 0, "this is the new first tab");
244 compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
245 }
246
247 function test_z_insertTabEnd() {
248 var newTab = tabs.insertTab(tabs.count, "PostTab", dynamicTab);
249 compare(newTab !== null, true, "post-tab inserted");
250 compare(newTab.index, tabs.count - 1, "thsi is the new last tab");
251 compare(tabs.selectedTab !== newTab, true, "the new tab is not the active one");
252 }
253
254 function test_z_insertTabAndActivate() {
255 var newTab = tabs.addTab("Inserted tab", dynamicTab);
256 compare((newTab !== null), true, "tab inserted");
257 compare(newTab.index, tabs.count - 1, "the tab is the last one");
258 tabs.selectedTabIndex = newTab.index;
259 compare(tabs.selectedTab, newTab, "the inserted tab is selected");
260 compare(newTab.active, true, "the new tab is active");
261 }
262
263 function test_z_moveTab() {
264 var selectedIndex = tabs.count - 1;
265 tabs.selectedTabIndex = selectedIndex;
266 compare(tabs.moveTab(0, selectedIndex), true, "first tab moved to last");
267 compare(tabs.selectedTabIndex, selectedIndex - 1, "the selected index moved backwards");
268 tabs.selectedTabIndex = selectedIndex = 0;
269 compare(tabs.moveTab(selectedIndex, selectedIndex + 1), true, "selected tab moved as next");
270 compare(tabs.selectedTabIndex, selectedIndex + 1, "the selected index moved forewards");
271 }
272
273 function test_z_moveSelectedTab() {
274 tabs.selectedTabIndex = 0;
275 tabs.moveTab(0, 1);
276 compare(tabs.selectedTabIndex, 1, "selected tab moved");
277 }
278
279 function test_z_moveTabFail() {
280 compare(tabs.moveTab(-1, tabs.count - 1), false, "from-parameter out of range");
281 compare(tabs.moveTab(0, tabs.count), false, "to-parameter out of range");
282 }
283
284 function test_z_removeTab() {
285 compare(tabs.removeTab(tabs.count - 1), true, "last tab removed");
286 tabs.selectedTabIndex = 0;
287 compare(tabs.removeTab(0), true, "active tab removed");
288 compare(tabs.selectedTabIndex, 0, "the next tab is selected")
289 }
290
291 function test_z_removeTabAfterActiveTab() {
292 var activeTab = tabs.count - 2;
293 tabs.selectedTabIndex = activeTab;
294 compare(tabs.removeTab(tabs.count - 1), true, "last tab removed");
295 compare(tabs.selectedTabIndex, activeTab, "the selected tab wasn't moved");
296 }
297
298 function test_z_removeTabBeforeActiveTab() {
299 var activeTab = tabs.count - 1;
300 tabs.selectedTabIndex = activeTab;
301 compare(tabs.removeTab(0), true, "first tab removed");
302 compare(tabs.selectedTabIndex, activeTab - 1, "the selected tab index decreased");
303 }
304
305 function test_z_removeActiveTab() {
306 tabs.selectedTabIndex = 1;
307 compare(tabs.removeTab(1), true, "selected tab removed");
308 compare(tabs.selectedTabIndex, 1, "selected tab is next");
309
310 tabs.selectedTabIndex = tabs.count - 1;
311 compare(tabs.removeTab(tabs.count - 1), true, "last tab removed");
312 compare(tabs.selectedTabIndex, tabs.count - 1, "selected tab moved to last item");
313 }
314
315 function test_zz_addTabAfterCleaningUpTabs() {
316 while (tabs.count > 1) {
317 tabs.removeTab(0);
318 }
319 compare(tabs.selectedTabIndex, 0, "the only tab is the selected one");
320 // add a new tab anc check the count (default added tas should not be added anymore
321 tabs.addTab("Second tab", dynamicTab);
322 compare(tabs.count, 2, "we have two tabs only");
323 }
324
325 function test_zz_addPredeclaredTab() {
326 while (tabs.count > 1) {
327 tabs.removeTab(0);
328 }
329 compare(tabs.selectedTabIndex, 0, "the only tab is the selected one");
330
331 // add a predeclared tab back with original title
332 compare(tabs.addTab("", tab1), tab1, "tab1 was added back");
333 compare(tab1.title, "tab 1", "with the original title");
334
335 // add a predeclared tab back with new title
336 compare(tabs.addTab("Original tab", tab2), tab2, "tab2 was added back");
337 compare(tab2.title, "Original tab", "with modified title");
338
339 // add a predeclared tab which was added already
340 compare(tabs.addTab("", tab1), null, "tab1 is already in tabs");
341 }
342 }215 }
343}216}

Subscribers

People subscribed via source and target branches

to status/vote changes: