Merge lp:~uriboni/webbrowser-app/qml-tabs-model into lp:webbrowser-app
- qml-tabs-model
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~uriboni/webbrowser-app/qml-tabs-model |
Merge into: | lp:webbrowser-app |
Diff against target: |
1675 lines (+581/-877) 14 files modified
src/app/webbrowser/Browser.qml (+7/-11) src/app/webbrowser/CMakeLists.txt (+0/-1) src/app/webbrowser/TabsBar.qml (+7/-6) src/app/webbrowser/TabsList.qml (+6/-4) src/app/webbrowser/TabsModel.qml (+137/-0) src/app/webbrowser/tabs-model.cpp (+0/-254) src/app/webbrowser/tabs-model.h (+0/-84) src/app/webbrowser/webbrowser-app.cpp (+0/-2) tests/unittests/CMakeLists.txt (+0/-1) tests/unittests/qml/CMakeLists.txt (+0/-1) tests/unittests/qml/tst_QmlTests.cpp (+0/-2) tests/unittests/qml/tst_TabsModel.qml (+424/-0) tests/unittests/tabs-model/CMakeLists.txt (+0/-18) tests/unittests/tabs-model/tst_TabsModelTests.cpp (+0/-493) |
To merge this branch: | bzr merge lp:~uriboni/webbrowser-app/qml-tabs-model |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
system-apps-ci-bot | continuous-integration | Needs Fixing | |
PS Jenkins bot | continuous-integration | Approve | |
Olivier Tilloy | Needs Fixing | ||
Review via email: mp+270408@code.launchpad.net |
Commit message
Reimplement the tabs model in QML.
Description of the change
Reimplement the tabs model in QML.
The behaviour is not 100% the same as the previous model, due to differences in what can be done in QML and C++, but the application can be easily adapted to cope (which this MR does too), with no changes in app functionality.
PS Jenkins bot (ps-jenkins) wrote : | # |
- 1176. By Ugo Riboni
-
Merge changes from trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1176
http://
Executed test runs:
UNSTABLE: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1176
http://
Executed test runs:
UNSTABLE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
UNSTABLE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
- 1177. By Ugo Riboni
-
Partial merge from trunk
- 1178. By Ugo Riboni
-
Partial merge from trunk
- 1179. By Ugo Riboni
-
Partial merge from trunk
- 1180. By Ugo Riboni
-
Partial merge from trunk
- 1181. By Ugo Riboni
-
Partial merge from trunk
- 1182. By Ugo Riboni
-
Partial merge from trunk
- 1183. By Ugo Riboni
-
Partial merge from trunk
- 1184. By Ugo Riboni
-
Partial merge from trunk
- 1185. By Ugo Riboni
-
Partial merge from trunk
- 1186. By Ugo Riboni
-
Partial merge from trunk
- 1187. By Ugo Riboni
-
Merge all remaining changes from trunk
- 1188. By Ugo Riboni
-
Fix style issue
- 1189. By Ugo Riboni
-
Remove mistakenly added merge conflict file
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1189
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Olivier Tilloy (osomon) wrote : | # |
Overall this looks good to me, getting rid of custom C++ code in favour of QML with (supposedly) no performance hit is always good, thanks for working on this.
I have a few minor comments:
56 - return tabComponent.
57 + var tab = tabComponent.
58 + return tab
that change is unnecessary, can it be reverted?
Is QTBUG-26357 the correct bug report for the lack of a way to override methods and call their base implementation? It doesn’t look like it from the description.
TabsModel could be a QtObject instead of an Item (you could move the updateCurrentTab() function to inside the ListModel).
There’s a couple of lines of JS code in TabsModel.qml that end with a semicolon (and one line in test_shouldRetu
What is the point of allowing currentIndex to be invalid? Shouldn’t it be reset to a valid value?
Related to the above, what is the point of allowing the second parameter of insert() to be invalid, and have the model clamp it? Wouldn’t it be better to spit out an error if the index is not valid?
Because these tests don’t exercise user input, the width and height of the top-level item in tst_TabsModel.qml don’t need to be that large (they can be set to 1px).
Camel-casing on test_shouldUpda
The first two compare() in test_shouldSetC
- 1190. By Ugo Riboni
-
Make TabsModel a QtObject. Fix the reference to the QML bug that prevents calling base class methods. Remove semicolons and a pointless change.
- 1191. By Ugo Riboni
-
No need for the window to be large as there is no input being tested
- 1192. By Ugo Riboni
-
Fix test name case
- 1193. By Ugo Riboni
-
Remove useless checks, as that code path is already tested elsewhere
- 1194. By Ugo Riboni
-
Do not allow inserting outside of valid boundaries
- 1195. By Ugo Riboni
-
Do not allow invalid current indexes, except for -1
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1194
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1195
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Olivier Tilloy (osomon) wrote : | # |
There is one extraneous blank line in updateCurrentTab, can it be removed?
In insert(), if index is invalid, shouldn’t the fuction return early after printing out the warning? And in test_shouldNotI
Should we really allow setting currentIndex to -1 when there’s at least one tab in the model? What real-world use case does this cover?
- 1196. By Ugo Riboni
-
Append when insert index is undefined, and return immediately when index is specified and out of bounds. Add unit test to verify the undefined index behavior.
- 1197. By Ugo Riboni
-
Prevent the currentIndex from being -1 unless the model is empty
- 1198. By Ugo Riboni
-
Merge changes from trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1197
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Olivier Tilloy (osomon) wrote : | # |
webbrowser_
> And in test_shouldNotI
> that currentIndex and currentTab remain unchanged.
This hasn’t been done.
846 + // verify that when the model is not empty -1 is not a valid
847 + // current index anymore
s/anymore// : -1 never was a valid value for currentIndex, at least not in the current implementation in trunk
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1198
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Ugo Riboni (uriboni) wrote : | # |
> webbrowser_
> desktop
It is really odd that this was working on jenkins, but not when we run it on our machines.
Fixed in any case
> > And in test_shouldNotI
> > that currentIndex and currentTab remain unchanged.
>
> This hasn’t been done.
Fixed
> 846 + // verify that when the model is not empty -1 is not a valid
> 847 + // current index anymore
>
> s/anymore// : -1 never was a valid value for currentIndex, at least not in the
> current implementation in trunk
I mean that -1 stops being a valid index when the model is not empty. But removed "anymore" to avoid confusion anyway.
- 1199. By Ugo Riboni
-
Give back focus to the address bar if we close a tab and we land on a new tab view. This caused some AP tests to fail on desktops (but not in CI, oddly).
- 1200. By Ugo Riboni
-
Check some additional things in unit tests and clarify a comment
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1200
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Olivier Tilloy (osomon) wrote : | # |
> It is really odd that this was working on jenkins, but not when we run
> it on our machines. Fixed in any case
As I explained in a comment on another merge request, CI didn’t complain because it doesn’t run autopilot tests on desktop, only on a phone.
> I mean that -1 stops being a valid index when the model is not empty.
> But removed "anymore" to avoid confusion anyway.
Right, I had misunderstood the comment. It was slightly confusing (or maybe it was just me who was confused). Thanks for clarifying!
system-apps-ci-bot (system-apps-ci-bot) wrote : | # |
FAILED: Continuous integration, rev:1200
https:/
Executed test runs:
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
FAILURE: https:/
Click here to trigger a rebuild:
https:/
Unmerged revisions
- 1200. By Ugo Riboni
-
Check some additional things in unit tests and clarify a comment
- 1199. By Ugo Riboni
-
Give back focus to the address bar if we close a tab and we land on a new tab view. This caused some AP tests to fail on desktops (but not in CI, oddly).
- 1198. By Ugo Riboni
-
Merge changes from trunk
- 1197. By Ugo Riboni
-
Prevent the currentIndex from being -1 unless the model is empty
- 1196. By Ugo Riboni
-
Append when insert index is undefined, and return immediately when index is specified and out of bounds. Add unit test to verify the undefined index behavior.
- 1195. By Ugo Riboni
-
Do not allow invalid current indexes, except for -1
- 1194. By Ugo Riboni
-
Do not allow inserting outside of valid boundaries
- 1193. By Ugo Riboni
-
Remove useless checks, as that code path is already tested elsewhere
- 1192. By Ugo Riboni
-
Fix test name case
- 1191. By Ugo Riboni
-
No need for the window to be large as there is no input being tested
Preview Diff
1 | === modified file 'src/app/webbrowser/Browser.qml' |
2 | --- src/app/webbrowser/Browser.qml 2015-11-03 11:56:01 +0000 |
3 | +++ src/app/webbrowser/Browser.qml 2015-11-08 23:11:15 +0000 |
4 | @@ -62,14 +62,11 @@ |
5 | target: tabsModel |
6 | onCurrentIndexChanged: { |
7 | // Remove focus from the address bar when the current tab |
8 | - // changes to ensure that its contents are updated. |
9 | + // changes to ensure that its contents are updated... |
10 | tabContainer.forceActiveFocus() |
11 | - |
12 | - // In narrow mode, the tabslist is a stack: |
13 | - // the current tab is always at the top. |
14 | - if (!browser.wide) { |
15 | - tabsModel.move(tabsModel.currentIndex, 0) |
16 | - } |
17 | + // ...then give focus to the either the tab or the address bar |
18 | + // depending if we are on a new tab view or on a page |
19 | + internal.resetFocus() |
20 | } |
21 | } |
22 | |
23 | @@ -711,9 +708,6 @@ |
24 | onWideChanged: { |
25 | if (wide) { |
26 | recentView.reset() |
27 | - } else { |
28 | - // In narrow mode, the tabslist is a stack: the current tab is always at the top. |
29 | - tabsModel.move(tabsModel.currentIndex, 0) |
30 | } |
31 | } |
32 | |
33 | @@ -946,6 +940,7 @@ |
34 | |
35 | TabsModel { |
36 | id: publicTabsModel |
37 | + behaveAsStack: !browser.wide |
38 | } |
39 | |
40 | Loader { |
41 | @@ -957,6 +952,7 @@ |
42 | id: privateTabsModelComponent |
43 | |
44 | TabsModel { |
45 | + behaveAsStack: !browser.wide |
46 | Component.onDestruction: { |
47 | while (count > 0) { |
48 | var tab = remove(count - 1) |
49 | @@ -1485,7 +1481,7 @@ |
50 | internal.addTab(tab, i == 0) |
51 | } |
52 | } |
53 | - if ('currentIndex' in state) { |
54 | + if ('currentIndex' in state && publicTabsModel.validIndex(state.currentIndex)) { |
55 | publicTabsModel.currentIndex = state.currentIndex |
56 | } |
57 | } |
58 | |
59 | === modified file 'src/app/webbrowser/CMakeLists.txt' |
60 | --- src/app/webbrowser/CMakeLists.txt 2015-10-18 19:21:48 +0000 |
61 | +++ src/app/webbrowser/CMakeLists.txt 2015-11-08 23:11:15 +0000 |
62 | @@ -21,7 +21,6 @@ |
63 | history-model.cpp |
64 | history-timeframe-model.cpp |
65 | limit-proxy-model.cpp |
66 | - tabs-model.cpp |
67 | text-search-filter-model.cpp |
68 | top-sites-model.cpp |
69 | ) |
70 | |
71 | === modified file 'src/app/webbrowser/TabsBar.qml' |
72 | --- src/app/webbrowser/TabsBar.qml 2015-09-24 14:07:47 +0000 |
73 | +++ src/app/webbrowser/TabsBar.qml 2015-11-08 23:11:15 +0000 |
74 | @@ -24,7 +24,7 @@ |
75 | Item { |
76 | id: root |
77 | |
78 | - property alias model: repeater.model |
79 | + property var model |
80 | |
81 | property real minTabWidth: 0 //units.gu(6) |
82 | property real maxTabWidth: units.gu(20) |
83 | @@ -88,7 +88,7 @@ |
84 | Action { |
85 | objectName: "tab_action_reload" |
86 | text: i18n.tr("Reload") |
87 | - enabled: menu.tab.url.toString().length > 0 |
88 | + enabled: menu.tab && menu.tab.url.toString().length > 0 |
89 | onTriggered: menu.tab.reload() |
90 | } |
91 | Action { |
92 | @@ -118,6 +118,7 @@ |
93 | |
94 | property bool reordering: false |
95 | |
96 | + model: root.model.listModel |
97 | delegate: MouseArea { |
98 | id: tabDelegate |
99 | objectName: "tabDelegate" |
100 | @@ -145,8 +146,8 @@ |
101 | active: tabIndex === root.model.currentIndex |
102 | hoverable: true |
103 | incognito: root.incognito |
104 | - title: model.title ? model.title : (model.url.toString() ? model.url : i18n.tr("New tab")) |
105 | - icon: model.icon |
106 | + title: model.tab.title ? model.tab.title : (model.tab.url.toString() ? model.tab.url : i18n.tr("New tab")) |
107 | + icon: model.tab.icon |
108 | |
109 | rightMargin: tabDelegate.rightMargin |
110 | |
111 | @@ -171,9 +172,9 @@ |
112 | onXChanged: { |
113 | if (!dragging) return |
114 | if (x < (index * width - width / 2)) { |
115 | - root.model.move(index, index - 1) |
116 | + root.model.move(index, index - 1, 1) |
117 | } else if ((x > (index * width + width / 2)) && (index < (root.model.count - 1))) { |
118 | - root.model.move(index + 1, index) |
119 | + root.model.move(index + 1, index, 1) |
120 | } |
121 | } |
122 | |
123 | |
124 | === modified file 'src/app/webbrowser/TabsList.qml' |
125 | --- src/app/webbrowser/TabsList.qml 2015-09-04 17:48:37 +0000 |
126 | +++ src/app/webbrowser/TabsList.qml 2015-11-08 23:11:15 +0000 |
127 | @@ -24,7 +24,7 @@ |
128 | |
129 | property real delegateHeight |
130 | property real chromeOffset |
131 | - property alias model: repeater.model |
132 | + property var model |
133 | readonly property int count: repeater.count |
134 | property bool incognito |
135 | |
136 | @@ -62,6 +62,7 @@ |
137 | Repeater { |
138 | id: repeater |
139 | |
140 | + model: tabslist.model.listModel |
141 | delegate: Loader { |
142 | id: delegate |
143 | |
144 | @@ -91,8 +92,8 @@ |
145 | } |
146 | } |
147 | |
148 | - readonly property string title: model.title ? model.title : (model.url.toString() ? model.url : i18n.tr("New tab")) |
149 | - readonly property string icon: model.icon |
150 | + readonly property string title: model.tab.title ? model.tab.title : (model.tab.url.toString() ? model.tab.url : i18n.tr("New tab")) |
151 | + readonly property string icon: model.tab.icon |
152 | |
153 | readonly property bool needsInstance: (index >= 0) && ((flickable.contentY + flickable.height + delegateHeight / 2) >= (index * delegateHeight)) |
154 | sourceComponent: needsInstance ? tabPreviewComponent : undefined |
155 | @@ -103,13 +104,14 @@ |
156 | id: tabPreviewComponent |
157 | |
158 | TabPreview { |
159 | + id: preview |
160 | title: delegate.title |
161 | icon: delegate.icon |
162 | incognito: tabslist.incognito |
163 | tab: model.tab |
164 | chromeHeight: firstItemChromeBackground.height |
165 | showContent: (index > 0) || (delegate.y > flickable.contentY) || |
166 | - !(tab.webview && tab.webview.visible) |
167 | + !(preview.tab.webview && preview.tab.webview.visible) |
168 | |
169 | onSelected: tabslist.selectAndAnimateTab(index) |
170 | onClosed: tabslist.tabClosed(index) |
171 | |
172 | === added file 'src/app/webbrowser/TabsModel.qml' |
173 | --- src/app/webbrowser/TabsModel.qml 1970-01-01 00:00:00 +0000 |
174 | +++ src/app/webbrowser/TabsModel.qml 2015-11-08 23:11:15 +0000 |
175 | @@ -0,0 +1,137 @@ |
176 | +/* |
177 | + * Copyright 2015 Canonical Ltd. |
178 | + * |
179 | + * This file is part of webbrowser-app. |
180 | + * |
181 | + * webbrowser-app is free software; you can redistribute it and/or modify |
182 | + * it under the terms of the GNU General Public License as published by |
183 | + * the Free Software Foundation; version 3. |
184 | + * |
185 | + * webbrowser-app is distributed in the hope that it will be useful, |
186 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
187 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
188 | + * GNU General Public License for more details. |
189 | + * |
190 | + * You should have received a copy of the GNU General Public License |
191 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
192 | + */ |
193 | + |
194 | +import QtQuick 2.4 |
195 | + |
196 | +QtObject { |
197 | + property int currentIndex: -1 |
198 | + property var currentTab: null |
199 | + property bool behaveAsStack: false |
200 | + property alias count: tabs.count |
201 | + |
202 | + // Expose the ListModel through a property instead of inheriting directly |
203 | + // from it due to a bug in QML that does not allow calling base class |
204 | + // methods: https://bugreports.qt.io/browse/QTBUG-25942 |
205 | + // This allows us to create our own methods that implement the required |
206 | + // semantics (such as correctly tracking and updating currentIndex and |
207 | + // currentTab) at the price of the minor inconvenience of having to access |
208 | + // TabsModel.listModel when wanting to use it as model for a view. |
209 | + property ListModel listModel: ListModel { |
210 | + id: tabs |
211 | + |
212 | + function updateCurrentTab() { |
213 | + var newCurrentTab = null |
214 | + if (validIndex(currentIndex)) { |
215 | + if (behaveAsStack) { |
216 | + tabs.move(currentIndex, 0, 1) |
217 | + currentIndex = 0 |
218 | + } |
219 | + newCurrentTab = tabs.get(currentIndex).tab |
220 | + } |
221 | + if (newCurrentTab != currentTab) { |
222 | + currentTab = newCurrentTab |
223 | + } |
224 | + } |
225 | + } |
226 | + |
227 | + onCurrentIndexChanged: { |
228 | + // Clamp the model index to valid values. -1 is a valid value only |
229 | + // if the model is empty. |
230 | + currentIndex = Math.max((tabs.count === 0) ? -1 : 0, |
231 | + Math.min(currentIndex, tabs.count - 1)) |
232 | + tabs.updateCurrentTab() |
233 | + } |
234 | + onBehaveAsStackChanged: if (behaveAsStack) tabs.updateCurrentTab() |
235 | + |
236 | + function validIndex(index) { return index >= 0 && index < tabs.count } |
237 | + |
238 | + function add(tab) { return insert(tab, tabs.count) } |
239 | + |
240 | + function insert(tab, index) { |
241 | + if (tab) { |
242 | + if (index === undefined) index = tabs.count |
243 | + else if (index < 0 || index > tabs.count) { |
244 | + console.warn("Invalid insert index:", index) |
245 | + return |
246 | + } |
247 | + tabs.insert(index, {tab: tab}) |
248 | + if (currentIndex == -1) { |
249 | + // if the current index was -1 then the model must have been |
250 | + // empty, and the first item added to an empty model always |
251 | + // becomes the new current item |
252 | + currentIndex = 0 |
253 | + tabs.updateCurrentTab() |
254 | + } else if (currentIndex == tabs.count - 1) { |
255 | + // index was set out of range, but adding the new item made it |
256 | + // valid, so we need to update the current item |
257 | + tabs.updateCurrentTab() |
258 | + } else if (index <= currentIndex) { |
259 | + if (validIndex(currentIndex)) { |
260 | + // Increment the index if we are inserting items before the |
261 | + // current index, and the current index has not been set to |
262 | + // a position past the end of the list. |
263 | + currentIndex++ |
264 | + } |
265 | + } |
266 | + return index |
267 | + } else { |
268 | + console.warn("Invalid Tab:", tab) |
269 | + return -1 |
270 | + } |
271 | + } |
272 | + |
273 | + function remove(index) { |
274 | + if (!validIndex(index)) return null |
275 | + var tab = tabs.get(index).tab |
276 | + tabs.remove(index) |
277 | + |
278 | + if (index < currentIndex) { |
279 | + // if we removed the current index and it is the last item OR |
280 | + // if we removed any item before the current index, |
281 | + // then decrement the current index |
282 | + --currentIndex |
283 | + } else if (index === currentIndex) { |
284 | + // If the current tab was removed, the following one (if any) is made |
285 | + // current. If it was the last tab in the model, the current index needs |
286 | + // to be decreased. |
287 | + if (index === tabs.count) --currentIndex |
288 | + else tabs.updateCurrentTab() |
289 | + } |
290 | + |
291 | + return tab |
292 | + } |
293 | + |
294 | + function get(index) { |
295 | + return validIndex(index) ? tabs.get(index).tab : null |
296 | + } |
297 | + |
298 | + function move(from, to) |
299 | + { |
300 | + if (from == to || !validIndex(from) || !validIndex(to)) { |
301 | + return |
302 | + } |
303 | + tabs.move(from, to, 1) |
304 | + if (currentIndex == from) { |
305 | + currentIndex = to |
306 | + } else if (currentIndex >= to && currentIndex < from) { |
307 | + currentIndex++ |
308 | + } else if (currentIndex > from && currentIndex <= to) { |
309 | + currentIndex-- |
310 | + } |
311 | + } |
312 | +} |
313 | |
314 | === removed file 'src/app/webbrowser/tabs-model.cpp' |
315 | --- src/app/webbrowser/tabs-model.cpp 2015-09-23 16:50:21 +0000 |
316 | +++ src/app/webbrowser/tabs-model.cpp 1970-01-01 00:00:00 +0000 |
317 | @@ -1,254 +0,0 @@ |
318 | -/* |
319 | - * Copyright 2013-2015 Canonical Ltd. |
320 | - * |
321 | - * This file is part of webbrowser-app. |
322 | - * |
323 | - * webbrowser-app is free software; you can redistribute it and/or modify |
324 | - * it under the terms of the GNU General Public License as published by |
325 | - * the Free Software Foundation; version 3. |
326 | - * |
327 | - * webbrowser-app is distributed in the hope that it will be useful, |
328 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
329 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
330 | - * GNU General Public License for more details. |
331 | - * |
332 | - * You should have received a copy of the GNU General Public License |
333 | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
334 | - */ |
335 | - |
336 | -#include "tabs-model.h" |
337 | - |
338 | -// Qt |
339 | -#include <QtCore/QDebug> |
340 | -#include <QtCore/QObject> |
341 | -#include <QtCore/QtGlobal> |
342 | - |
343 | -/*! |
344 | - \class TabsModel |
345 | - \brief List model that stores the list of currently open tabs. |
346 | - |
347 | - TabsModel is a list model that stores the list of currently open tabs. |
348 | - Each tab holds a pointer to a Tab and associated metadata (URL, title, |
349 | - icon). |
350 | - |
351 | - The model doesn’t own the Tab, so it is the responsibility of whoever |
352 | - adds a tab to instantiate the corresponding Tab, and to destroy it after |
353 | - it’s removed from the model. |
354 | -*/ |
355 | -TabsModel::TabsModel(QObject* parent) |
356 | - : QAbstractListModel(parent) |
357 | - , m_currentIndex(-1) |
358 | -{ |
359 | -} |
360 | - |
361 | -TabsModel::~TabsModel() |
362 | -{ |
363 | -} |
364 | - |
365 | -QHash<int, QByteArray> TabsModel::roleNames() const |
366 | -{ |
367 | - static QHash<int, QByteArray> roles; |
368 | - if (roles.isEmpty()) { |
369 | - roles[Url] = "url"; |
370 | - roles[Title] = "title"; |
371 | - roles[Icon] = "icon"; |
372 | - roles[Tab] = "tab"; |
373 | - } |
374 | - return roles; |
375 | -} |
376 | - |
377 | -int TabsModel::rowCount(const QModelIndex& parent) const |
378 | -{ |
379 | - Q_UNUSED(parent); |
380 | - return m_tabs.count(); |
381 | -} |
382 | - |
383 | -QVariant TabsModel::data(const QModelIndex& index, int role) const |
384 | -{ |
385 | - if (!index.isValid()) { |
386 | - return QVariant(); |
387 | - } |
388 | - QObject* tab = m_tabs.at(index.row()); |
389 | - switch (role) { |
390 | - case Url: |
391 | - return tab->property("url"); |
392 | - case Title: |
393 | - return tab->property("title"); |
394 | - case Icon: |
395 | - return tab->property("icon"); |
396 | - case Tab: |
397 | - return QVariant::fromValue(tab); |
398 | - default: |
399 | - return QVariant(); |
400 | - } |
401 | -} |
402 | - |
403 | -int TabsModel::currentIndex() const |
404 | -{ |
405 | - return m_currentIndex; |
406 | -} |
407 | - |
408 | -void TabsModel::setCurrentIndex(int index) |
409 | -{ |
410 | - if (!checkValidTabIndex(index)) { |
411 | - return; |
412 | - } |
413 | - if (index != m_currentIndex) { |
414 | - m_currentIndex = index; |
415 | - Q_EMIT currentIndexChanged(); |
416 | - Q_EMIT currentTabChanged(); |
417 | - } |
418 | -} |
419 | - |
420 | -QObject* TabsModel::currentTab() const |
421 | -{ |
422 | - if (m_tabs.isEmpty() || !checkValidTabIndex(m_currentIndex)) { |
423 | - return nullptr; |
424 | - } |
425 | - return m_tabs.at(m_currentIndex); |
426 | -} |
427 | - |
428 | -/*! |
429 | - Append a tab to the model and return the corresponding index in the model. |
430 | - |
431 | - It is the responsibility of the caller to instantiate the corresponding |
432 | - Tab beforehand. |
433 | -*/ |
434 | -int TabsModel::add(QObject* tab) |
435 | -{ |
436 | - return insert(tab, m_tabs.count()); |
437 | -} |
438 | - |
439 | -/*! |
440 | - Add a tab to the model at the specified index, and return the index itself, |
441 | - or -1 if the operation failed. |
442 | - |
443 | - It is the responsibility of the caller to instantiate the corresponding |
444 | - Tab beforehand. |
445 | -*/ |
446 | -int TabsModel::insert(QObject* tab, int index) |
447 | -{ |
448 | - if (tab == nullptr) { |
449 | - qWarning() << "Invalid Tab"; |
450 | - return -1; |
451 | - } |
452 | - index = qMax(qMin(index, m_tabs.count()), 0); |
453 | - beginInsertRows(QModelIndex(), index, index); |
454 | - m_tabs.insert(index, tab); |
455 | - connect(tab, SIGNAL(urlChanged()), SLOT(onUrlChanged())); |
456 | - connect(tab, SIGNAL(titleChanged()), SLOT(onTitleChanged())); |
457 | - connect(tab, SIGNAL(iconChanged()), SLOT(onIconChanged())); |
458 | - endInsertRows(); |
459 | - Q_EMIT countChanged(); |
460 | - |
461 | - if (m_currentIndex == -1) { |
462 | - // Set the index to zero if this is the first item that gets added to the |
463 | - // model, as it should not be possible to have items in the model but no |
464 | - // current tab. |
465 | - m_currentIndex = 0; |
466 | - Q_EMIT currentIndexChanged(); |
467 | - Q_EMIT currentTabChanged(); |
468 | - } else if (index <= m_currentIndex) { |
469 | - // Increment the index if we are inserting items before the current index. |
470 | - m_currentIndex++; |
471 | - Q_EMIT currentIndexChanged(); |
472 | - } |
473 | - |
474 | - return index; |
475 | -} |
476 | - |
477 | -/*! |
478 | - Given its index, remove a tab from the model, and return the corresponding |
479 | - Tab. |
480 | - |
481 | - It is the responsibility of the caller to destroy the corresponding |
482 | - Tab afterwards. |
483 | -*/ |
484 | -QObject* TabsModel::remove(int index) |
485 | -{ |
486 | - if (!checkValidTabIndex(index)) { |
487 | - return nullptr; |
488 | - } |
489 | - beginRemoveRows(QModelIndex(), index, index); |
490 | - QObject* tab = m_tabs.takeAt(index); |
491 | - tab->disconnect(this); |
492 | - endRemoveRows(); |
493 | - Q_EMIT countChanged(); |
494 | - |
495 | - if (index < m_currentIndex) { |
496 | - // If we removed any tab before the current one, decrease the |
497 | - // current index to match. |
498 | - m_currentIndex--; |
499 | - Q_EMIT currentIndexChanged(); |
500 | - } else if (index == m_currentIndex) { |
501 | - // If the current tab was removed, the following one (if any) is made |
502 | - // current. If it was the last tab in the model, the current index needs |
503 | - // to be decreased. |
504 | - if (m_currentIndex == m_tabs.count()) { |
505 | - m_currentIndex--; |
506 | - Q_EMIT currentIndexChanged(); |
507 | - } |
508 | - Q_EMIT currentTabChanged(); |
509 | - } |
510 | - return tab; |
511 | -} |
512 | - |
513 | -QObject* TabsModel::get(int index) const |
514 | -{ |
515 | - if (!checkValidTabIndex(index)) { |
516 | - return nullptr; |
517 | - } |
518 | - return m_tabs.at(index); |
519 | -} |
520 | - |
521 | -void TabsModel::move(int from, int to) |
522 | -{ |
523 | - if ((from == to) || !checkValidTabIndex(from) || !checkValidTabIndex(to)) { |
524 | - return; |
525 | - } |
526 | - beginMoveRows(QModelIndex(), from, from, QModelIndex(), to); |
527 | - m_tabs.move(from, to); |
528 | - endMoveRows(); |
529 | - if (m_currentIndex == from) { |
530 | - m_currentIndex = to; |
531 | - Q_EMIT currentIndexChanged(); |
532 | - } else if ((m_currentIndex >= to) && (m_currentIndex < from)) { |
533 | - m_currentIndex++; |
534 | - Q_EMIT currentIndexChanged(); |
535 | - } else if ((m_currentIndex > from) && (m_currentIndex <= to)) { |
536 | - m_currentIndex--; |
537 | - Q_EMIT currentIndexChanged(); |
538 | - } |
539 | -} |
540 | - |
541 | -bool TabsModel::checkValidTabIndex(int index) const |
542 | -{ |
543 | - if ((index < 0) || (index >= m_tabs.count())) { |
544 | - qWarning() << "Invalid tab index:" << index; |
545 | - return false; |
546 | - } |
547 | - return true; |
548 | -} |
549 | - |
550 | -void TabsModel::onDataChanged(QObject* tab, int role) |
551 | -{ |
552 | - int index = m_tabs.indexOf(tab); |
553 | - if (checkValidTabIndex(index)) { |
554 | - Q_EMIT dataChanged(this->index(index, 0), this->index(index, 0), QVector<int>() << role); |
555 | - } |
556 | -} |
557 | - |
558 | -void TabsModel::onUrlChanged() |
559 | -{ |
560 | - onDataChanged(sender(), Url); |
561 | -} |
562 | - |
563 | -void TabsModel::onTitleChanged() |
564 | -{ |
565 | - onDataChanged(sender(), Title); |
566 | -} |
567 | - |
568 | -void TabsModel::onIconChanged() |
569 | -{ |
570 | - onDataChanged(sender(), Icon); |
571 | -} |
572 | |
573 | === removed file 'src/app/webbrowser/tabs-model.h' |
574 | --- src/app/webbrowser/tabs-model.h 2015-09-02 16:37:20 +0000 |
575 | +++ src/app/webbrowser/tabs-model.h 1970-01-01 00:00:00 +0000 |
576 | @@ -1,84 +0,0 @@ |
577 | -/* |
578 | - * Copyright 2013-2015 Canonical Ltd. |
579 | - * |
580 | - * This file is part of webbrowser-app. |
581 | - * |
582 | - * webbrowser-app is free software; you can redistribute it and/or modify |
583 | - * it under the terms of the GNU General Public License as published by |
584 | - * the Free Software Foundation; version 3. |
585 | - * |
586 | - * webbrowser-app is distributed in the hope that it will be useful, |
587 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
588 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
589 | - * GNU General Public License for more details. |
590 | - * |
591 | - * You should have received a copy of the GNU General Public License |
592 | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
593 | - */ |
594 | - |
595 | -#ifndef __TABS_MODEL_H__ |
596 | -#define __TABS_MODEL_H__ |
597 | - |
598 | -// Qt |
599 | -#include <QtCore/QAbstractListModel> |
600 | -#include <QtCore/QList> |
601 | - |
602 | -class QObject; |
603 | - |
604 | -class TabsModel : public QAbstractListModel |
605 | -{ |
606 | - Q_OBJECT |
607 | - |
608 | - Q_ENUMS(Roles) |
609 | - |
610 | - Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) |
611 | - Q_PROPERTY(QObject* currentTab READ currentTab NOTIFY currentTabChanged) |
612 | - Q_PROPERTY(int count READ rowCount NOTIFY countChanged) |
613 | - |
614 | -public: |
615 | - TabsModel(QObject* parent=0); |
616 | - ~TabsModel(); |
617 | - |
618 | - enum Roles { |
619 | - Url = Qt::UserRole + 1, |
620 | - Title, |
621 | - Icon, |
622 | - Tab |
623 | - }; |
624 | - |
625 | - // reimplemented from QAbstractListModel |
626 | - QHash<int, QByteArray> roleNames() const; |
627 | - int rowCount(const QModelIndex& parent=QModelIndex()) const; |
628 | - QVariant data(const QModelIndex& index, int role) const; |
629 | - |
630 | - int currentIndex() const; |
631 | - void setCurrentIndex(int index); |
632 | - |
633 | - QObject* currentTab() const; |
634 | - |
635 | - Q_INVOKABLE int add(QObject* tab); |
636 | - Q_INVOKABLE int insert(QObject* tab, int index); |
637 | - Q_INVOKABLE QObject* remove(int index); |
638 | - Q_INVOKABLE QObject* get(int index) const; |
639 | - Q_INVOKABLE void move(int from, int to); |
640 | - |
641 | -Q_SIGNALS: |
642 | - void currentIndexChanged() const; |
643 | - void currentTabChanged() const; |
644 | - void countChanged() const; |
645 | - |
646 | -private Q_SLOTS: |
647 | - void onUrlChanged(); |
648 | - void onTitleChanged(); |
649 | - void onIconChanged(); |
650 | - |
651 | -private: |
652 | - QList<QObject*> m_tabs; |
653 | - int m_currentIndex; |
654 | - |
655 | - bool checkValidTabIndex(int index) const; |
656 | - void setCurrentIndexNoCheck(int index); |
657 | - void onDataChanged(QObject* tab, int role); |
658 | -}; |
659 | - |
660 | -#endif // __TABS_MODEL_H__ |
661 | |
662 | === modified file 'src/app/webbrowser/webbrowser-app.cpp' |
663 | --- src/app/webbrowser/webbrowser-app.cpp 2015-10-22 15:07:26 +0000 |
664 | +++ src/app/webbrowser/webbrowser-app.cpp 2015-11-08 23:11:15 +0000 |
665 | @@ -30,7 +30,6 @@ |
666 | #include "limit-proxy-model.h" |
667 | #include "searchengine.h" |
668 | #include "text-search-filter-model.h" |
669 | -#include "tabs-model.h" |
670 | #include "top-sites-model.h" |
671 | #include "webbrowser-app.h" |
672 | |
673 | @@ -89,7 +88,6 @@ |
674 | qmlRegisterType<HistoryLastVisitDateListModel>(uri, 0, 1, "HistoryLastVisitDateListModel"); |
675 | qmlRegisterType<HistoryLastVisitDateModel>(uri, 0, 1, "HistoryLastVisitDateModel"); |
676 | qmlRegisterType<LimitProxyModel>(uri, 0 , 1, "LimitProxyModel"); |
677 | - qmlRegisterType<TabsModel>(uri, 0, 1, "TabsModel"); |
678 | qmlRegisterSingletonType<BookmarksModel>(uri, 0, 1, "BookmarksModel", BookmarksModel_singleton_factory); |
679 | qmlRegisterType<BookmarksFolderListModel>(uri, 0, 1, "BookmarksFolderListModel"); |
680 | qmlRegisterSingletonType<FileOperations>(uri, 0, 1, "FileOperations", FileOperations_singleton_factory); |
681 | |
682 | === modified file 'tests/unittests/CMakeLists.txt' |
683 | --- tests/unittests/CMakeLists.txt 2015-10-06 09:48:38 +0000 |
684 | +++ tests/unittests/CMakeLists.txt 2015-11-08 23:11:15 +0000 |
685 | @@ -10,7 +10,6 @@ |
686 | add_subdirectory(history-lastvisitdatelist-model) |
687 | add_subdirectory(top-sites-model) |
688 | add_subdirectory(session-utils) |
689 | -add_subdirectory(tabs-model) |
690 | add_subdirectory(bookmarks-model) |
691 | add_subdirectory(bookmarks-folder-model) |
692 | add_subdirectory(bookmarks-folderlist-model) |
693 | |
694 | === modified file 'tests/unittests/qml/CMakeLists.txt' |
695 | --- tests/unittests/qml/CMakeLists.txt 2015-10-06 09:48:38 +0000 |
696 | +++ tests/unittests/qml/CMakeLists.txt 2015-11-08 23:11:15 +0000 |
697 | @@ -27,7 +27,6 @@ |
698 | ${webbrowser-app_SOURCE_DIR}/history-timeframe-model.cpp |
699 | ${webbrowser-app_SOURCE_DIR}/limit-proxy-model.cpp |
700 | ${webbrowser-app_SOURCE_DIR}/searchengine.cpp |
701 | - ${webbrowser-app_SOURCE_DIR}/tabs-model.cpp |
702 | ${webbrowser-app_SOURCE_DIR}/text-search-filter-model.cpp |
703 | ${webbrowser-app_SOURCE_DIR}/top-sites-model.cpp |
704 | tst_QmlTests.cpp |
705 | |
706 | === modified file 'tests/unittests/qml/tst_QmlTests.cpp' |
707 | --- tests/unittests/qml/tst_QmlTests.cpp 2015-10-22 15:07:26 +0000 |
708 | +++ tests/unittests/qml/tst_QmlTests.cpp 2015-11-08 23:11:15 +0000 |
709 | @@ -35,7 +35,6 @@ |
710 | #include "history-lastvisitdatelist-model.h" |
711 | #include "limit-proxy-model.h" |
712 | #include "searchengine.h" |
713 | -#include "tabs-model.h" |
714 | #include "text-search-filter-model.h" |
715 | #include "top-sites-model.h" |
716 | |
717 | @@ -189,7 +188,6 @@ |
718 | |
719 | const char* browserUri = "webbrowserapp.private"; |
720 | qmlRegisterType<SearchEngine>(browserUri, 0, 1, "SearchEngine"); |
721 | - qmlRegisterType<TabsModel>(browserUri, 0, 1, "TabsModel"); |
722 | qmlRegisterSingletonType<BookmarksModel>(browserUri, 0, 1, "BookmarksModel", BookmarksModel_singleton_factory); |
723 | qmlRegisterType<BookmarksFolderListModel>(browserUri, 0, 1, "BookmarksFolderListModel"); |
724 | qmlRegisterSingletonType<HistoryModel>(browserUri, 0, 1, "HistoryModel", HistoryModel_singleton_factory); |
725 | |
726 | === added file 'tests/unittests/qml/tst_TabsModel.qml' |
727 | --- tests/unittests/qml/tst_TabsModel.qml 1970-01-01 00:00:00 +0000 |
728 | +++ tests/unittests/qml/tst_TabsModel.qml 2015-11-08 23:11:15 +0000 |
729 | @@ -0,0 +1,424 @@ |
730 | +/* |
731 | + * Copyright 2015 Canonical Ltd. |
732 | + * |
733 | + * This file is part of webbrowser-app. |
734 | + * |
735 | + * webbrowser-app is free software; you can redistribute it and/or modify |
736 | + * it under the terms of the GNU General Public License as published by |
737 | + * the Free Software Foundation; version 3. |
738 | + * |
739 | + * webbrowser-app is distributed in the hope that it will be useful, |
740 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
741 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
742 | + * GNU General Public License for more details. |
743 | + * |
744 | + * You should have received a copy of the GNU General Public License |
745 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
746 | + */ |
747 | + |
748 | +import QtQuick 2.4 |
749 | +import QtTest 1.0 |
750 | +import "../../../src/app/webbrowser" |
751 | + |
752 | +Item { |
753 | + width: 1 |
754 | + height: 1 |
755 | + |
756 | + property alias model: modelLoader.item |
757 | + |
758 | + Loader { |
759 | + id: modelLoader |
760 | + active: false |
761 | + asynchronous: false |
762 | + sourceComponent: TabsModel { } |
763 | + } |
764 | + |
765 | + Component { |
766 | + id: tab |
767 | + Item { |
768 | + property url url |
769 | + property string title |
770 | + property url icon |
771 | + } |
772 | + } |
773 | + |
774 | + SignalSpy { |
775 | + id: countSpy |
776 | + target: model |
777 | + signalName: "countChanged" |
778 | + } |
779 | + |
780 | + SignalSpy { |
781 | + id: currentIndexSpy |
782 | + target: model |
783 | + signalName: "currentIndexChanged" |
784 | + } |
785 | + |
786 | + SignalSpy { |
787 | + id: currentTabSpy |
788 | + target: model |
789 | + signalName: "currentTabChanged" |
790 | + } |
791 | + |
792 | + TestCase { |
793 | + name: "TabsModel" |
794 | + when: windowShown |
795 | + |
796 | + function createTab() |
797 | + { |
798 | + return tab.createObject() |
799 | + } |
800 | + |
801 | + function init() |
802 | + { |
803 | + modelLoader.active = true |
804 | + } |
805 | + |
806 | + function cleanup() |
807 | + { |
808 | + modelLoader.active = false |
809 | + countSpy.clear() |
810 | + currentIndexSpy.clear() |
811 | + currentTabSpy.clear() |
812 | + } |
813 | + |
814 | + function createTabWithTitle(title) { |
815 | + var tab = createTab() |
816 | + tab.title = title |
817 | + return tab |
818 | + } |
819 | + |
820 | + function verifyTabsOrder(orderedTitles) { |
821 | + compare(model.count, orderedTitles.length) |
822 | + orderedTitles.forEach(function(title, i) { |
823 | + compare(model.get(i).title, title) |
824 | + }) |
825 | + } |
826 | + |
827 | + function test_shouldBeInitiallyEmpty() |
828 | + { |
829 | + compare(model.count, 0) |
830 | + compare(model.currentIndex, -1) |
831 | + compare(model.currentTab, null) |
832 | + } |
833 | + |
834 | + function test_currentTabShouldBeNullOnInvalidIndex() |
835 | + { |
836 | + model.add(createTab()) |
837 | + model.remove(0) |
838 | + compare(model.currentIndex, -1) |
839 | + compare(model.currentTab, null) |
840 | + } |
841 | + |
842 | + function test_shouldNotAllowOutOfBoundsCurrentIndex() |
843 | + { |
844 | + model.currentIndex = 0; |
845 | + compare(currentIndexSpy.count, 2) |
846 | + compare(model.currentIndex, -1) |
847 | + |
848 | + model.currentIndex = -2; |
849 | + compare(currentIndexSpy.count, 4) |
850 | + compare(model.currentIndex, -1) |
851 | + |
852 | + // verify that when the model is not empty -1 is not a valid |
853 | + // current index |
854 | + model.add(createTab()) |
855 | + compare(model.currentIndex, 0) |
856 | + currentIndexSpy.clear() |
857 | + model.currentIndex = -1; |
858 | + compare(currentIndexSpy.count, 2) |
859 | + compare(model.currentIndex, 0) |
860 | + } |
861 | + |
862 | + function test_shouldNotAddNullTab() |
863 | + { |
864 | + ignoreWarning("Invalid Tab: null") |
865 | + compare(model.add(null), -1) |
866 | + compare(model.count, 0) |
867 | + } |
868 | + |
869 | + function test_shouldNotInsertTabAtInvalidIndex() |
870 | + { |
871 | + ignoreWarning("Invalid insert index: " + -1) |
872 | + model.insert(createTab(), -1) |
873 | + compare(model.count, 0) |
874 | + compare(model.currentIndex, -1) |
875 | + compare(model.currentTab, null) |
876 | + |
877 | + ignoreWarning("Invalid insert index: " + 1) |
878 | + model.insert(createTab(), 1) |
879 | + compare(model.count, 0) |
880 | + compare(model.currentIndex, -1) |
881 | + compare(model.currentTab, null) |
882 | + } |
883 | + |
884 | + function test_shouldAppendWhenInsertingAtUndefinedIndex() |
885 | + { |
886 | + compare(model.insert(createTab()), 0) |
887 | + compare(model.count, 1) |
888 | + |
889 | + compare(model.insert(createTab(), undefined), 1) |
890 | + compare(model.count, 2) |
891 | + } |
892 | + |
893 | + function test_shouldReturnIndexWhenAddingTab() |
894 | + { |
895 | + for (var i = 0; i < 3; ++i) { |
896 | + compare(model.add(createTab()), i) |
897 | + } |
898 | + } |
899 | + |
900 | + function test_shouldUpdateCountWhenAddingTab() |
901 | + { |
902 | + model.add(createTab()) |
903 | + compare(countSpy.count, 1) |
904 | + compare(model.count, 1) |
905 | + } |
906 | + |
907 | + function test_shouldUpdateCountWhenRemovingTab() |
908 | + { |
909 | + model.add(createTab()) |
910 | + countSpy.clear() |
911 | + delete model.remove(0) |
912 | + compare(countSpy.count, 1) |
913 | + compare(model.count, 0) |
914 | + } |
915 | + |
916 | + function test_shouldNotAllowRemovingAtInvalidIndex() |
917 | + { |
918 | + compare(model.remove(0), null) |
919 | + compare(model.remove(2), null) |
920 | + compare(model.remove(-2), null) |
921 | + } |
922 | + |
923 | + function test_shouldReturnTabWhenRemoving() |
924 | + { |
925 | + var tab = createTab() |
926 | + model.add(tab) |
927 | + var removed = model.remove(0) |
928 | + compare(removed, tab) |
929 | + delete removed |
930 | + } |
931 | + |
932 | + function test_shouldUpdateCurrentTabWhenSettingCurrentIndex() |
933 | + { |
934 | + var tab1 = createTab() |
935 | + model.add(tab1) |
936 | + compare(model.currentIndex, 0) |
937 | + compare(currentTabSpy.count, 1) |
938 | + compare(model.currentTab, tab1) |
939 | + |
940 | + var tab2 = createTab() |
941 | + model.add(tab2) |
942 | + model.currentIndex = 1 |
943 | + compare(model.currentIndex, 1) |
944 | + compare(currentTabSpy.count, 2) |
945 | + compare(model.currentTab, tab2) |
946 | + } |
947 | + |
948 | + function test_shouldSetCurrentTabWhenAddingFirstTab() |
949 | + { |
950 | + // Adding a tab to an empty model should update the current tab |
951 | + // to that tab |
952 | + var tab1 = createTab() |
953 | + model.add(tab1) |
954 | + |
955 | + compare(currentTabSpy.count, 1) |
956 | + compare(currentIndexSpy.count, 1) |
957 | + compare(model.currentIndex, 0) |
958 | + compare(model.currentTab, tab1) |
959 | + |
960 | + // But adding further items should keep the index where it was |
961 | + model.add(createTab()) |
962 | + model.add(createTab()) |
963 | + |
964 | + compare(currentTabSpy.count, 1) |
965 | + compare(currentIndexSpy.count, 1) |
966 | + compare(model.currentIndex, 0) |
967 | + compare(model.currentTab, tab1) |
968 | + } |
969 | + |
970 | + function test_shouldSetCurrentTabWhenInsertingFirstTab() |
971 | + { |
972 | + // Inserting a tab to an empty model should update the current tab |
973 | + // to that tab |
974 | + var tab1 = createTab() |
975 | + model.insert(tab1, 0) |
976 | + |
977 | + compare(currentTabSpy.count, 1) |
978 | + compare(currentIndexSpy.count, 1) |
979 | + compare(model.currentIndex, 0) |
980 | + compare(model.currentTab, tab1) |
981 | + } |
982 | + |
983 | + function test_shouldSetInvalidIndexWhenRemovingLastTab() |
984 | + { |
985 | + // Removing the last item should also set the current index to -1 |
986 | + // and the current tab to null |
987 | + model.add(createTab()) |
988 | + |
989 | + currentTabSpy.clear() |
990 | + currentIndexSpy.clear() |
991 | + model.remove(0) |
992 | + compare(currentTabSpy.count, 1) |
993 | + compare(currentIndexSpy.count, 1) |
994 | + compare(model.currentIndex, -1) |
995 | + compare(model.currentTab, null) |
996 | + } |
997 | + |
998 | + function test_shouldNotChangeIndexWhenRemovingAfterCurrent() |
999 | + { |
1000 | + // When removing a tab after the current one, |
1001 | + // the current tab shouldn’t change. |
1002 | + var tab1 = createTab() |
1003 | + model.add(tab1) |
1004 | + model.add(createTab()) |
1005 | + |
1006 | + currentTabSpy.clear() |
1007 | + currentIndexSpy.clear() |
1008 | + model.remove(1) |
1009 | + compare(model.currentTab, tab1) |
1010 | + compare(currentTabSpy.count, 0) |
1011 | + compare(currentIndexSpy.count, 0) |
1012 | + } |
1013 | + |
1014 | + function test_shouldUpdateIndexWhenRemovingCurrent() |
1015 | + { |
1016 | + // When removing the current tab, if there is a tab after it, |
1017 | + // it becomes the current one. |
1018 | + var tab1 = createTab() |
1019 | + var tab2 = createTab() |
1020 | + var tab3 = createTab() |
1021 | + model.add(tab1) |
1022 | + model.add(tab2) |
1023 | + model.add(tab3) |
1024 | + model.currentIndex = 1 |
1025 | + compare(model.currentIndex, 1) |
1026 | + compare(model.currentTab, tab2) |
1027 | + |
1028 | + currentTabSpy.clear() |
1029 | + currentIndexSpy.clear() |
1030 | + model.remove(1) |
1031 | + compare(currentIndexSpy.count, 0) |
1032 | + compare(currentTabSpy.count, 1) |
1033 | + compare(model.currentTab, tab3) |
1034 | + |
1035 | + // If there is no tab after it but one before, that one becomes current |
1036 | + model.remove(1) |
1037 | + compare(currentIndexSpy.count, 1) |
1038 | + compare(currentTabSpy.count, 2) |
1039 | + compare(model.currentIndex, 0) |
1040 | + compare(model.currentTab, tab1) |
1041 | + } |
1042 | + |
1043 | + function test_shouldDecreaseIndexWhenRemovingBeforeCurrent() |
1044 | + { |
1045 | + // When removing a tab before the current tab, the current index |
1046 | + // should decrease to match. |
1047 | + model.add(createTab()) |
1048 | + model.add(createTab()) |
1049 | + var tab = createTab() |
1050 | + model.add(tab) |
1051 | + model.currentIndex = 2 |
1052 | + |
1053 | + currentTabSpy.clear() |
1054 | + currentIndexSpy.clear() |
1055 | + model.remove(1) |
1056 | + compare(currentTabSpy.count, 0) |
1057 | + compare(currentIndexSpy.count, 1) |
1058 | + compare(model.currentIndex, 1) |
1059 | + compare(model.currentTab, tab) |
1060 | + } |
1061 | + |
1062 | + function test_shouldReturnTabAtIndex() |
1063 | + { |
1064 | + // valid indexes |
1065 | + var tab1 = createTab() |
1066 | + model.add(tab1) |
1067 | + var tab2 = createTab() |
1068 | + model.add(tab2) |
1069 | + var tab3 = createTab() |
1070 | + model.add(tab3) |
1071 | + compare(model.get(0), tab1) |
1072 | + compare(model.get(1), tab2) |
1073 | + compare(model.get(2), tab3) |
1074 | + |
1075 | + // invalid indexes |
1076 | + compare(model.get(-1), null) |
1077 | + compare(model.get(3), null) |
1078 | + } |
1079 | + |
1080 | + function test_shouldPreserveCurrentIndexAndTabWhenMoving_data() |
1081 | + { |
1082 | + return [ |
1083 | + {initial: 0, from: 0, to: 2, result: 2}, // moving tab that has the current index |
1084 | + {initial: 1, from: 0, to: 2, result: 0}, // moving from before current index to after current index |
1085 | + {initial: 1, from: 2, to: 0, result: 2} // moving from after current index to before current index |
1086 | + ] |
1087 | + } |
1088 | + function test_shouldPreserveCurrentIndexAndTabWhenMoving(data) |
1089 | + { |
1090 | + for (var i = 0; i < 3; ++i) { |
1091 | + model.add(createTab()) |
1092 | + } |
1093 | + model.currentIndex = data.initial |
1094 | + var currentTabInitial = model.currentTab |
1095 | + model.move(data.from, data.to) |
1096 | + compare(model.currentIndex, data.result) |
1097 | + compare(model.currentTab, currentTabInitial) |
1098 | + } |
1099 | + |
1100 | + function test_shouldMoveToTopWhenSettingCurrentIndexInStackMode() { |
1101 | + var tab1 = createTab() |
1102 | + model.add(tab1) |
1103 | + var tab2 = createTab() |
1104 | + model.add(tab2) |
1105 | + var tab3 = createTab() |
1106 | + model.add(tab3) |
1107 | + model.behaveAsStack = true |
1108 | + compare(model.currentIndex, 0) |
1109 | + |
1110 | + model.currentIndex = 2 |
1111 | + compare(model.currentIndex, 0) |
1112 | + compare(model.currentTab, tab3) |
1113 | + compare(model.get(1), tab1) |
1114 | + compare(model.get(2), tab2) |
1115 | + } |
1116 | + |
1117 | + function test_shouldNotInsertNullTab() |
1118 | + { |
1119 | + ignoreWarning("Invalid Tab: null") |
1120 | + compare(model.insert(null, 0), -1) |
1121 | + compare(model.count, 0) |
1122 | + } |
1123 | + |
1124 | + function test_shouldReturnIndexWhenInsertingTab() |
1125 | + { |
1126 | + for (var i = 0; i < 3; ++i) { |
1127 | + model.add(createTab()) |
1128 | + } |
1129 | + |
1130 | + for (var i = 2; i >= 0; --i) { |
1131 | + compare(model.insert(createTab(), i), i) |
1132 | + } |
1133 | + } |
1134 | + |
1135 | + function test_shouldUpdateCountWhenInsertingTab() |
1136 | + { |
1137 | + model.insert(createTab(), 0) |
1138 | + compare(countSpy.count, 1) |
1139 | + compare(model.count, 1) |
1140 | + } |
1141 | + |
1142 | + function test_shouldInsertAtCorrectIndex() |
1143 | + { |
1144 | + model.insert(createTabWithTitle("B"), 0) |
1145 | + model.insert(createTabWithTitle("A"), 0) |
1146 | + verifyTabsOrder(["A", "B"]) |
1147 | + model.insert(createTabWithTitle("X"), 1) |
1148 | + verifyTabsOrder(["A", "X", "B"]) |
1149 | + model.insert(createTabWithTitle("C"), 3) |
1150 | + verifyTabsOrder(["A", "X", "B", "C"]) |
1151 | + } |
1152 | + } |
1153 | +} |
1154 | |
1155 | === removed directory 'tests/unittests/tabs-model' |
1156 | === removed file 'tests/unittests/tabs-model/CMakeLists.txt' |
1157 | --- tests/unittests/tabs-model/CMakeLists.txt 2015-06-22 10:29:20 +0000 |
1158 | +++ tests/unittests/tabs-model/CMakeLists.txt 1970-01-01 00:00:00 +0000 |
1159 | @@ -1,18 +0,0 @@ |
1160 | -find_package(Qt5Core REQUIRED) |
1161 | -find_package(Qt5Qml REQUIRED) |
1162 | -find_package(Qt5Quick REQUIRED) |
1163 | -find_package(Qt5Sql REQUIRED) |
1164 | -find_package(Qt5Test REQUIRED) |
1165 | -set(TEST tst_TabsModelTests) |
1166 | -add_executable(${TEST} tst_TabsModelTests.cpp) |
1167 | -include_directories(${webbrowser-app_SOURCE_DIR}) |
1168 | -target_link_libraries(${TEST} |
1169 | - Qt5::Core |
1170 | - Qt5::Qml |
1171 | - Qt5::Quick |
1172 | - Qt5::Sql |
1173 | - Qt5::Test |
1174 | - webbrowser-app-models |
1175 | -) |
1176 | -add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml) |
1177 | -set_tests_properties(${TEST} PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal") |
1178 | |
1179 | === removed file 'tests/unittests/tabs-model/tst_TabsModelTests.cpp' |
1180 | --- tests/unittests/tabs-model/tst_TabsModelTests.cpp 2015-09-23 17:14:30 +0000 |
1181 | +++ tests/unittests/tabs-model/tst_TabsModelTests.cpp 1970-01-01 00:00:00 +0000 |
1182 | @@ -1,493 +0,0 @@ |
1183 | -/* |
1184 | - * Copyright 2013-2015 Canonical Ltd. |
1185 | - * |
1186 | - * This file is part of webbrowser-app. |
1187 | - * |
1188 | - * webbrowser-app is free software; you can redistribute it and/or modify |
1189 | - * it under the terms of the GNU General Public License as published by |
1190 | - * the Free Software Foundation; version 3. |
1191 | - * |
1192 | - * webbrowser-app is distributed in the hope that it will be useful, |
1193 | - * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1194 | - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1195 | - * GNU General Public License for more details. |
1196 | - * |
1197 | - * You should have received a copy of the GNU General Public License |
1198 | - * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1199 | - */ |
1200 | - |
1201 | -// Qt |
1202 | -#include <QtCore/QStringList> |
1203 | -#include <QtQml/QQmlComponent> |
1204 | -#include <QtQml/QQmlEngine> |
1205 | -#include <QtQml/QQmlProperty> |
1206 | -#include <QtQuick/QQuickItem> |
1207 | -#include <QtTest/QSignalSpy> |
1208 | -#include <QtTest/QtTest> |
1209 | - |
1210 | -// local |
1211 | -#include "tabs-model.h" |
1212 | - |
1213 | -class TabsModelTests : public QObject |
1214 | -{ |
1215 | - Q_OBJECT |
1216 | - |
1217 | -private: |
1218 | - TabsModel* model; |
1219 | - |
1220 | - QQuickItem* createTab() |
1221 | - { |
1222 | - QQmlEngine engine; |
1223 | - QQmlComponent component(&engine); |
1224 | - QByteArray data("import QtQuick 2.4\nItem {\nproperty url url\n" |
1225 | - "property string title\nproperty url icon\n}"); |
1226 | - component.setData(data, QUrl()); |
1227 | - QObject* object = component.create(); |
1228 | - object->setParent(this); |
1229 | - QQuickItem* item = qobject_cast<QQuickItem*>(object); |
1230 | - return item; |
1231 | - } |
1232 | - |
1233 | - QQuickItem* createTabWithTitle(const QString& title) { |
1234 | - QQuickItem* tab = createTab(); |
1235 | - tab->setProperty("title", title); |
1236 | - return tab; |
1237 | - } |
1238 | - |
1239 | - void verifyTabsOrder(QStringList orderedTitles) { |
1240 | - QCOMPARE(model->rowCount(), orderedTitles.count()); |
1241 | - int i = 0; |
1242 | - Q_FOREACH(QString title, orderedTitles) { |
1243 | - QCOMPARE(model->get(i++)->property("title").toString(), title); |
1244 | - } |
1245 | - } |
1246 | - |
1247 | -private Q_SLOTS: |
1248 | - void init() |
1249 | - { |
1250 | - model = new TabsModel; |
1251 | - } |
1252 | - |
1253 | - void cleanup() |
1254 | - { |
1255 | - while (model->rowCount() > 0) { |
1256 | - delete model->remove(0); |
1257 | - } |
1258 | - delete model; |
1259 | - } |
1260 | - |
1261 | - void shouldBeInitiallyEmpty() |
1262 | - { |
1263 | - QCOMPARE(model->rowCount(), 0); |
1264 | - QCOMPARE(model->currentTab(), (QObject*) nullptr); |
1265 | - } |
1266 | - |
1267 | - void shouldExposeRoleNames() |
1268 | - { |
1269 | - QList<QByteArray> roleNames = model->roleNames().values(); |
1270 | - QVERIFY(roleNames.contains("url")); |
1271 | - QVERIFY(roleNames.contains("title")); |
1272 | - QVERIFY(roleNames.contains("icon")); |
1273 | - QVERIFY(roleNames.contains("tab")); |
1274 | - } |
1275 | - |
1276 | - void shouldNotAllowSettingTheIndexToAnInvalidValue_data() |
1277 | - { |
1278 | - QTest::addColumn<int>("index"); |
1279 | - QTest::newRow("zero") << 0; |
1280 | - QTest::newRow("too high") << 2; |
1281 | - QTest::newRow("too low") << -2; |
1282 | - } |
1283 | - |
1284 | - void shouldNotAllowSettingTheIndexToAnInvalidValue() |
1285 | - { |
1286 | - QFETCH(int, index); |
1287 | - model->setCurrentIndex(index); |
1288 | - QCOMPARE(model->currentIndex(), -1); |
1289 | - QCOMPARE(model->currentTab(), (QObject*) nullptr); |
1290 | - } |
1291 | - |
1292 | - void shouldNotAddNullTab() |
1293 | - { |
1294 | - QCOMPARE(model->add(0), -1); |
1295 | - QCOMPARE(model->rowCount(), 0); |
1296 | - } |
1297 | - |
1298 | - void shouldReturnIndexWhenAddingTab() |
1299 | - { |
1300 | - for(int i = 0; i < 3; ++i) { |
1301 | - QCOMPARE(model->add(createTab()), i); |
1302 | - } |
1303 | - } |
1304 | - |
1305 | - void shouldUpdateCountWhenAddingTab() |
1306 | - { |
1307 | - QSignalSpy spy(model, SIGNAL(countChanged())); |
1308 | - model->add(createTab()); |
1309 | - QCOMPARE(spy.count(), 1); |
1310 | - QCOMPARE(model->rowCount(), 1); |
1311 | - } |
1312 | - |
1313 | - void shouldNotInsertNullTab() |
1314 | - { |
1315 | - QCOMPARE(model->insert(0, 0), -1); |
1316 | - QCOMPARE(model->rowCount(), 0); |
1317 | - } |
1318 | - |
1319 | - void shouldReturnIndexWhenInsertingTab() |
1320 | - { |
1321 | - for(int i = 0; i < 3; ++i) { |
1322 | - model->add(createTab()); |
1323 | - } |
1324 | - for(int i = 2; i >= 0; --i) { |
1325 | - QCOMPARE(model->insert(createTab(), i), i); |
1326 | - } |
1327 | - } |
1328 | - |
1329 | - void shouldUpdateCountWhenInsertingTab() |
1330 | - { |
1331 | - QSignalSpy spy(model, SIGNAL(countChanged())); |
1332 | - model->insert(createTab(), 0); |
1333 | - QCOMPARE(spy.count(), 1); |
1334 | - QCOMPARE(model->rowCount(), 1); |
1335 | - } |
1336 | - |
1337 | - void shouldInsertAtCorrectIndex() |
1338 | - { |
1339 | - model->insert(createTabWithTitle("B"), 0); |
1340 | - model->insert(createTabWithTitle("A"), 0); |
1341 | - verifyTabsOrder(QStringList({"A", "B"})); |
1342 | - model->insert(createTabWithTitle("X"), 1); |
1343 | - verifyTabsOrder(QStringList({"A", "X", "B"})); |
1344 | - model->insert(createTabWithTitle("C"), 3); |
1345 | - verifyTabsOrder(QStringList({"A", "X", "B", "C"})); |
1346 | - } |
1347 | - |
1348 | - void shouldClampIndexWhenInsertingTabOutOfBounds() |
1349 | - { |
1350 | - model->add(createTabWithTitle("A")); |
1351 | - model->add(createTabWithTitle("B")); |
1352 | - model->insert(createTabWithTitle("C"), 3); |
1353 | - verifyTabsOrder(QStringList({"A", "B", "C"})); |
1354 | - model->insert(createTabWithTitle("X"), -1); |
1355 | - verifyTabsOrder(QStringList({"X", "A", "B", "C"})); |
1356 | - } |
1357 | - |
1358 | - void shouldUpdateCountWhenRemovingTab() |
1359 | - { |
1360 | - model->add(createTab()); |
1361 | - QSignalSpy spy(model, SIGNAL(countChanged())); |
1362 | - delete model->remove(0); |
1363 | - QCOMPARE(spy.count(), 1); |
1364 | - QCOMPARE(model->rowCount(), 0); |
1365 | - } |
1366 | - |
1367 | - void shouldNotAllowRemovingAtInvalidIndex() |
1368 | - { |
1369 | - QCOMPARE(model->remove(0), (QObject*) nullptr); |
1370 | - QCOMPARE(model->remove(2), (QObject*) nullptr); |
1371 | - QCOMPARE(model->remove(-2), (QObject*) nullptr); |
1372 | - } |
1373 | - |
1374 | - void shouldReturnTabWhenRemoving() |
1375 | - { |
1376 | - QQuickItem* tab = createTab(); |
1377 | - model->add(tab); |
1378 | - QObject* removed = model->remove(0); |
1379 | - QCOMPARE(removed, tab); |
1380 | - delete removed; |
1381 | - } |
1382 | - |
1383 | - void shouldNotDeleteTabWhenRemoving() |
1384 | - { |
1385 | - QQuickItem* tab = createTab(); |
1386 | - model->add(tab); |
1387 | - model->remove(0); |
1388 | - QCOMPARE(tab->parent(), this); |
1389 | - delete tab; |
1390 | - } |
1391 | - |
1392 | - void shouldNotifyWhenAddingTab() |
1393 | - { |
1394 | - QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int))); |
1395 | - for(int i = 0; i < 3; ++i) { |
1396 | - model->add(createTab()); |
1397 | - QCOMPARE(spy.count(), 1); |
1398 | - QList<QVariant> args = spy.takeFirst(); |
1399 | - QCOMPARE(args.at(1).toInt(), i); |
1400 | - QCOMPARE(args.at(2).toInt(), i); |
1401 | - } |
1402 | - } |
1403 | - |
1404 | - void shouldNotifyWhenRemovingTab() |
1405 | - { |
1406 | - QSignalSpy spy(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int))); |
1407 | - for(int i = 0; i < 5; ++i) { |
1408 | - model->add(createTab()); |
1409 | - } |
1410 | - delete model->remove(3); |
1411 | - QCOMPARE(spy.count(), 1); |
1412 | - QList<QVariant> args = spy.takeFirst(); |
1413 | - QCOMPARE(args.at(1).toInt(), 3); |
1414 | - QCOMPARE(args.at(2).toInt(), 3); |
1415 | - for(int i = 3; i >= 0; --i) { |
1416 | - delete model->remove(i); |
1417 | - QCOMPARE(spy.count(), 1); |
1418 | - args = spy.takeFirst(); |
1419 | - QCOMPARE(args.at(1).toInt(), i); |
1420 | - QCOMPARE(args.at(2).toInt(), i); |
1421 | - } |
1422 | - } |
1423 | - |
1424 | - void shouldNotifyWhenTabPropertiesChange() |
1425 | - { |
1426 | - qRegisterMetaType<QVector<int> >(); |
1427 | - QSignalSpy spy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&))); |
1428 | - QQuickItem* tab = createTab(); |
1429 | - model->add(tab); |
1430 | - |
1431 | - QQmlProperty(tab, "url").write(QUrl("http://ubuntu.com")); |
1432 | - QCOMPARE(spy.count(), 1); |
1433 | - QList<QVariant> args = spy.takeFirst(); |
1434 | - QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1435 | - QCOMPARE(args.at(1).toModelIndex().row(), 0); |
1436 | - QVector<int> roles = args.at(2).value<QVector<int> >(); |
1437 | - QCOMPARE(roles.size(), 1); |
1438 | - QVERIFY(roles.contains(TabsModel::Url)); |
1439 | - |
1440 | - QQmlProperty(tab, "title").write(QString("Lorem Ipsum")); |
1441 | - QCOMPARE(spy.count(), 1); |
1442 | - args = spy.takeFirst(); |
1443 | - QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1444 | - QCOMPARE(args.at(1).toModelIndex().row(), 0); |
1445 | - roles = args.at(2).value<QVector<int> >(); |
1446 | - QCOMPARE(roles.size(), 1); |
1447 | - QVERIFY(roles.contains(TabsModel::Title)); |
1448 | - |
1449 | - QQmlProperty(tab, "icon").write(QUrl("image://webicon/123")); |
1450 | - QCOMPARE(spy.count(), 1); |
1451 | - args = spy.takeFirst(); |
1452 | - QCOMPARE(args.at(0).toModelIndex().row(), 0); |
1453 | - QCOMPARE(args.at(1).toModelIndex().row(), 0); |
1454 | - roles = args.at(2).value<QVector<int> >(); |
1455 | - QCOMPARE(roles.size(), 1); |
1456 | - QVERIFY(roles.contains(TabsModel::Icon)); |
1457 | - } |
1458 | - |
1459 | - void shouldUpdateCurrentTabWhenSettingCurrentIndex() |
1460 | - { |
1461 | - QQuickItem* tab1 = createTab(); |
1462 | - model->add(tab1); |
1463 | - QSignalSpy spy(model, SIGNAL(currentTabChanged())); |
1464 | - model->setCurrentIndex(0); |
1465 | - QCOMPARE(model->currentIndex(), 0); |
1466 | - QVERIFY(spy.isEmpty()); |
1467 | - QCOMPARE(model->currentTab(), tab1); |
1468 | - QQuickItem* tab2 = createTab(); |
1469 | - model->add(tab2); |
1470 | - model->setCurrentIndex(1); |
1471 | - QCOMPARE(model->currentIndex(), 1); |
1472 | - QCOMPARE(spy.count(), 1); |
1473 | - QCOMPARE(model->currentTab(), tab2); |
1474 | - } |
1475 | - |
1476 | - void shouldSetCurrentTabWhenAddingFirstTab() |
1477 | - { |
1478 | - // Adding a tab to an empty model should update the current tab |
1479 | - // to that tab |
1480 | - QSignalSpy spytab(model, SIGNAL(currentTabChanged())); |
1481 | - QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); |
1482 | - QCOMPARE(model->currentIndex(), -1); |
1483 | - QCOMPARE(model->currentTab(), (QObject*) nullptr); |
1484 | - |
1485 | - QQuickItem* tab1 = createTab(); |
1486 | - model->add(tab1); |
1487 | - |
1488 | - QCOMPARE(spytab.count(), 1); |
1489 | - QCOMPARE(spyindex.count(), 1); |
1490 | - QCOMPARE(model->currentIndex(), 0); |
1491 | - QCOMPARE(model->currentTab(), tab1); |
1492 | - |
1493 | - // But adding further items should keep the index where it was |
1494 | - model->add(createTab()); |
1495 | - model->add(createTab()); |
1496 | - |
1497 | - QCOMPARE(spytab.count(), 1); |
1498 | - QCOMPARE(spyindex.count(), 1); |
1499 | - QCOMPARE(model->currentIndex(), 0); |
1500 | - QCOMPARE(model->currentTab(), tab1); |
1501 | - } |
1502 | - |
1503 | - void shouldSetCurrentTabWhenInsertingFirstTab() |
1504 | - { |
1505 | - // Inserting a tab to an empty model should update the current tab |
1506 | - // to that tab |
1507 | - QSignalSpy spytab(model, SIGNAL(currentTabChanged())); |
1508 | - QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); |
1509 | - QCOMPARE(model->currentIndex(), -1); |
1510 | - QCOMPARE(model->currentTab(), (QObject*) nullptr); |
1511 | - |
1512 | - QQuickItem* tab1 = createTab(); |
1513 | - model->insert(tab1, 0); |
1514 | - |
1515 | - QCOMPARE(spytab.count(), 1); |
1516 | - QCOMPARE(spyindex.count(), 1); |
1517 | - QCOMPARE(model->currentIndex(), 0); |
1518 | - QCOMPARE(model->currentTab(), tab1); |
1519 | - } |
1520 | - |
1521 | - void shouldSetInvalidIndexWhenRemovingLastTab() |
1522 | - { |
1523 | - // Removing the last item should also set the current index to -1 |
1524 | - // and the current tab to null |
1525 | - model->add(createTab()); |
1526 | - |
1527 | - QSignalSpy spytab(model, SIGNAL(currentTabChanged())); |
1528 | - QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); |
1529 | - delete model->remove(0); |
1530 | - QCOMPARE(spytab.count(), 1); |
1531 | - QCOMPARE(spyindex.count(), 1); |
1532 | - QCOMPARE(model->currentIndex(), -1); |
1533 | - QCOMPARE(model->currentTab(), (QObject*) nullptr); |
1534 | - } |
1535 | - |
1536 | - void shouldNotChangeIndexWhenRemovingAfterCurrent() |
1537 | - { |
1538 | - // When removing a tab after the current one, |
1539 | - // the current tab shouldn’t change. |
1540 | - QQuickItem* tab1 = createTab(); |
1541 | - model->add(tab1); |
1542 | - model->add(createTab()); |
1543 | - |
1544 | - QSignalSpy spytab(model, SIGNAL(currentTabChanged())); |
1545 | - QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); |
1546 | - delete model->remove(1); |
1547 | - QCOMPARE(model->currentTab(), tab1); |
1548 | - QVERIFY(spytab.isEmpty()); |
1549 | - QVERIFY(spyindex.isEmpty()); |
1550 | - } |
1551 | - |
1552 | - void shouldUpdateIndexWhenRemovingCurrent() |
1553 | - { |
1554 | - // When removing the current tab, if there is a tab after it, |
1555 | - // it becomes the current one. |
1556 | - QQuickItem* tab1 = createTab(); |
1557 | - QQuickItem* tab2 = createTab(); |
1558 | - QQuickItem* tab3 = createTab(); |
1559 | - model->add(tab1); |
1560 | - model->add(tab2); |
1561 | - model->add(tab3); |
1562 | - model->setCurrentIndex(1); |
1563 | - QCOMPARE(model->currentIndex(), 1); |
1564 | - QCOMPARE(model->currentTab(), tab2); |
1565 | - |
1566 | - QSignalSpy spytab(model, SIGNAL(currentTabChanged())); |
1567 | - QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); |
1568 | - delete model->remove(1); |
1569 | - QCOMPARE(spyindex.count(), 0); |
1570 | - QCOMPARE(spytab.count(), 1); |
1571 | - QCOMPARE(model->currentTab(), tab3); |
1572 | - |
1573 | - // If there is no tab after it but one before, that one becomes current |
1574 | - delete model->remove(1); |
1575 | - QCOMPARE(spyindex.count(), 1); |
1576 | - QCOMPARE(spytab.count(), 2); |
1577 | - QCOMPARE(model->currentIndex(), 0); |
1578 | - QCOMPARE(model->currentTab(), tab1); |
1579 | - } |
1580 | - |
1581 | - void shouldDecreaseIndexWhenRemovingBeforeCurrent() |
1582 | - { |
1583 | - // When removing a tab before the current tab, the current index |
1584 | - // should decrease to match. |
1585 | - model->add(createTab()); |
1586 | - model->add(createTab()); |
1587 | - QQuickItem* tab = createTab(); |
1588 | - model->add(tab); |
1589 | - model->setCurrentIndex(2); |
1590 | - |
1591 | - QSignalSpy spytab(model, SIGNAL(currentTabChanged())); |
1592 | - QSignalSpy spyindex(model, SIGNAL(currentIndexChanged())); |
1593 | - delete model->remove(1); |
1594 | - QVERIFY(spytab.isEmpty()); |
1595 | - QCOMPARE(spyindex.count(), 1); |
1596 | - QCOMPARE(model->currentIndex(), 1); |
1597 | - QCOMPARE(model->currentTab(), tab); |
1598 | - } |
1599 | - |
1600 | - void shouldReturnData() |
1601 | - { |
1602 | - QQuickItem* tab = createTab(); |
1603 | - QQmlProperty(tab, "url").write(QUrl("http://ubuntu.com/")); |
1604 | - QQmlProperty(tab, "title").write(QString("Lorem Ipsum")); |
1605 | - QQmlProperty(tab, "icon").write(QUrl("image://webicon/123")); |
1606 | - model->add(tab); |
1607 | - QVERIFY(!model->data(QModelIndex(), TabsModel::Url).isValid()); |
1608 | - QVERIFY(!model->data(model->index(-1, 0), TabsModel::Url).isValid()); |
1609 | - QVERIFY(!model->data(model->index(3, 0), TabsModel::Url).isValid()); |
1610 | - QCOMPARE(model->data(model->index(0, 0), TabsModel::Url).toUrl(), QUrl("http://ubuntu.com/")); |
1611 | - QCOMPARE(model->data(model->index(0, 0), TabsModel::Title).toString(), QString("Lorem Ipsum")); |
1612 | - QCOMPARE(model->data(model->index(0, 0), TabsModel::Icon).toUrl(), QUrl("image://webicon/123")); |
1613 | - QCOMPARE(model->data(model->index(0, 0), TabsModel::Tab).value<QQuickItem*>(), tab); |
1614 | - QVERIFY(!model->data(model->index(0, 0), TabsModel::Tab + 3).isValid()); |
1615 | - } |
1616 | - |
1617 | - void shouldReturnTabAtIndex() |
1618 | - { |
1619 | - // valid indexes |
1620 | - QQuickItem* tab1 = createTab(); |
1621 | - model->add(tab1); |
1622 | - QQuickItem* tab2 = createTab(); |
1623 | - model->add(tab2); |
1624 | - QQuickItem* tab3 = createTab(); |
1625 | - model->add(tab3); |
1626 | - QCOMPARE(model->get(0), tab1); |
1627 | - QCOMPARE(model->get(1), tab2); |
1628 | - QCOMPARE(model->get(2), tab3); |
1629 | - |
1630 | - // invalid indexes |
1631 | - QCOMPARE(model->get(-1), (QObject*) nullptr); |
1632 | - QCOMPARE(model->get(3), (QObject*) nullptr); |
1633 | - } |
1634 | - |
1635 | -private: |
1636 | - void moveTabs(int from, int to, bool moved, bool indexChanged, int newIndex) |
1637 | - { |
1638 | - QSignalSpy spyMoved(model, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int))); |
1639 | - QSignalSpy spyIndex(model, SIGNAL(currentIndexChanged())); |
1640 | - QSignalSpy spyTab(model, SIGNAL(currentTabChanged())); |
1641 | - |
1642 | - model->move(from, to); |
1643 | - QCOMPARE(spyMoved.count(), moved ? 1 : 0); |
1644 | - if (moved) { |
1645 | - QList<QVariant> args = spyMoved.takeFirst(); |
1646 | - QCOMPARE(args.at(1).toInt(), from); |
1647 | - QCOMPARE(args.at(2).toInt(), from); |
1648 | - QCOMPARE(args.at(4).toInt(), to); |
1649 | - } |
1650 | - QCOMPARE(spyIndex.count(), indexChanged ? 1 : 0); |
1651 | - QCOMPARE(model->currentIndex(), newIndex); |
1652 | - QVERIFY(spyTab.isEmpty()); |
1653 | - } |
1654 | - |
1655 | -private Q_SLOTS: |
1656 | - void shouldNotifyWhenMovingTabs() |
1657 | - { |
1658 | - for (int i = 0; i < 3; ++i) { |
1659 | - model->add(createTab()); |
1660 | - } |
1661 | - |
1662 | - moveTabs(1, 1, false, false, 0); |
1663 | - moveTabs(4, 1, false, false, 0); |
1664 | - moveTabs(1, 4, false, false, 0); |
1665 | - moveTabs(0, 2, true, true, 2); |
1666 | - moveTabs(1, 0, true, false, 2); |
1667 | - moveTabs(2, 1, true, true, 1); |
1668 | - moveTabs(2, 0, true, true, 2); |
1669 | - moveTabs(2, 1, true, true, 1); |
1670 | - moveTabs(0, 2, true, true, 0); |
1671 | - } |
1672 | -}; |
1673 | - |
1674 | -QTEST_MAIN(TabsModelTests) |
1675 | -#include "tst_TabsModelTests.moc" |
PASSED: Continuous integration, rev:1175 jenkins. qa.ubuntu. com/job/ webbrowser- app-ci/ 2200/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- vivid-touch/ 4068 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- amd64-ci/ 954 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- armhf-ci/ 954 jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- armhf-ci/ 954/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ webbrowser- app-vivid- i386-ci/ 954 jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- vivid-mako/ 3325 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 4065 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- vivid-armhf/ 4065/artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 23142
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/webbrowser- app-ci/ 2200/rebuild
http://