Merge lp:~osomon/webbrowser-app/bookmarks-proxy-model into lp:webbrowser-app

Proposed by Olivier Tilloy
Status: Needs review
Proposed branch: lp:~osomon/webbrowser-app/bookmarks-proxy-model
Merge into: lp:webbrowser-app
Diff against target: 2398 lines (+1610/-331)
20 files modified
src/app/webbrowser/BookmarksFoldersView.qml (+92/-156)
src/app/webbrowser/BookmarksView.qml (+3/-3)
src/app/webbrowser/CMakeLists.txt (+1/-0)
src/app/webbrowser/NewTabView.qml (+9/-7)
src/app/webbrowser/bookmarks-model.cpp (+1/-1)
src/app/webbrowser/collapsible-bookmarks-model.cpp (+572/-0)
src/app/webbrowser/collapsible-bookmarks-model.h (+98/-0)
src/app/webbrowser/webbrowser-app.cpp (+2/-0)
tests/autopilot/webbrowser_app/emulators/browser.py (+83/-43)
tests/autopilot/webbrowser_app/tests/__init__.py (+1/-6)
tests/autopilot/webbrowser_app/tests/test_bookmark_options.py (+33/-29)
tests/autopilot/webbrowser_app/tests/test_new_tab_view.py (+43/-77)
tests/autopilot/webbrowser_app/tests/test_private.py (+4/-2)
tests/autopilot/webbrowser_app/tests/test_site_previews.py (+1/-1)
tests/unittests/CMakeLists.txt (+1/-0)
tests/unittests/collapsible-bookmarks-model/CMakeLists.txt (+13/-0)
tests/unittests/collapsible-bookmarks-model/tst_CollapsibleBookmarksModelTests.cpp (+645/-0)
tests/unittests/qml/CMakeLists.txt (+1/-0)
tests/unittests/qml/tst_BookmarksView.qml (+5/-6)
tests/unittests/qml/tst_QmlTests.cpp (+2/-0)
To merge this branch: bzr merge lp:~osomon/webbrowser-app/bookmarks-proxy-model
Reviewer Review Type Date Requested Status
system-apps-ci-bot continuous-integration Needs Fixing
PS Jenkins bot continuous-integration Approve
Ubuntu Phablet Team Pending
Review via email: mp+279277@code.launchpad.net

Commit message

Add a C++ model for the narrow bookmarks view: CollapsibleBookmarksModel.

To post a comment you must log in.
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: Approve (continuous-integration)
Revision history for this message
Olivier Tilloy (osomon) wrote :

For reference, I measured the memory overhead induced by duplicating in memory all the entries of the source model, and this is roughly 1MB per 1000 bookmarks, i.e. 1KB per bookmark (tested with 10000 bookmarks, and with 100000).

I’ll see if this can be optimized by keeping in memory only the bookmarks for the expanded folders, and reconstructing the entries for a folder when it is being expanded, and will share my observations here.

Revision history for this message
system-apps-ci-bot (system-apps-ci-bot) wrote :
review: Needs Fixing (continuous-integration)

Unmerged revisions

1308. By Olivier Tilloy

Add documentation to the CollapsibleBookmarksModel class.

1307. By Olivier Tilloy

Use the top-level bookmarks view in test_bookmark_options.py, instead of going through the new tab view.

1306. By Olivier Tilloy

Minor autopilot test improvements.

1305. By Olivier Tilloy

Fix a number of autopilot test failures.

1304. By Olivier Tilloy

More test coverage.

1303. By Olivier Tilloy

More test coverage.

1302. By Olivier Tilloy

More test coverage.

1301. By Olivier Tilloy

More test coverage.

1300. By Olivier Tilloy

More test coverage.

1299. By Olivier Tilloy

More test coverage.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/app/webbrowser/BookmarksFoldersView.qml'
2--- src/app/webbrowser/BookmarksFoldersView.qml 2015-11-12 16:16:15 +0000
3+++ src/app/webbrowser/BookmarksFoldersView.qml 2015-12-02 14:15:47 +0000
4@@ -20,171 +20,107 @@
5 import Ubuntu.Components 1.3
6 import Ubuntu.Components.ListItems 1.3 as ListItem
7 import webbrowserapp.private 0.1
8-import "BookmarksModelUtils.js" as BookmarksModelUtils
9-
10-FocusScope {
11- id: bookmarksFoldersViewItem
12-
13- property alias interactive: bookmarksFolderListView.interactive
14- property url homeBookmarkUrl
15+
16+ListView {
17+ objectName: "bookmarksFolderListView"
18+
19+ property url homepageUrl
20
21 signal bookmarkClicked(url url)
22 signal bookmarkRemoved(url url)
23
24- height: bookmarksFolderListView.contentHeight
25-
26- BookmarksFolderListModel {
27- id: bookmarksFolderListModel
28+ model: CollapsibleBookmarksModel {
29+ id: bookmarksProxyModel
30 sourceModel: BookmarksModel
31 }
32
33- ListView {
34- id: bookmarksFolderListView
35- objectName: "bookmarksFolderListView"
36- anchors.fill: parent
37- interactive: false
38- focus: true
39-
40- model: bookmarksFolderListModel
41- delegate: Loader {
42- objectName: "bookmarkFolderDelegateLoader"
43- anchors {
44- left: parent.left
45- right: parent.right
46+ delegate: Loader {
47+ objectName: "bookmarkViewDelegateLoader"
48+ anchors {
49+ left: parent.left
50+ right: parent.right
51+ }
52+ readonly property string type: model.type
53+ readonly property bool isBookmark: type == "bookmark"
54+ readonly property bool isHomepage: type == "homepage"
55+ readonly property string folder: model.folder
56+ height: (isBookmark || isHomepage) ? units.gu(5) : units.gu(6.5)
57+
58+ sourceComponent: (isBookmark || isHomepage) ? bookmarkComponent : folderComponent
59+
60+ Component {
61+ id: bookmarkComponent
62+ UrlDelegate {
63+ objectName: "bookmarkViewBookmarkDelegate"
64+ readonly property string folderName: folder
65+ anchors.fill: parent
66+ removable: !isHomepage
67+ icon: isHomepage ? "" : model.icon
68+ title: isHomepage ? i18n.tr("Homepage") : model.title
69+ url: isHomepage ? homepageUrl : model.url
70+ onClicked: bookmarkClicked(url)
71+ onRemoved: bookmarkRemoved(url)
72 }
73-
74- height: active ? item.height : 0
75- active: entries.count > 0
76-
77- sourceComponent: Item {
78- objectName: "bookmarkFolderDelegate"
79-
80- property string folderName: folder
81-
82+ }
83+
84+ Component {
85+ id: folderComponent
86+ Item {
87+ objectName: "bookmarkViewFolderDelegate"
88+ readonly property string name: folder
89 anchors {
90- left: parent ? parent.left : undefined
91- right: parent ? parent.right : undefined
92- }
93-
94- height: delegateColumn.height
95-
96- Column {
97- id: delegateColumn
98-
99- property bool expanded: folderName ? false : true
100-
101- anchors {
102- left: parent.left
103- right: parent.right
104- }
105-
106- Item {
107- objectName: "bookmarkFolderHeader"
108-
109- anchors {
110- left: parent.left
111- right: parent.right
112- leftMargin: units.gu(2)
113- rightMargin: units.gu(2)
114- }
115-
116- height: units.gu(6.5)
117-
118- Row {
119- anchors {
120- left: parent.left
121- leftMargin: units.gu(1.5)
122- right: parent.right
123- }
124-
125- height: units.gu(6)
126- spacing: units.gu(1.5)
127-
128- Icon {
129- id: expandedIcon
130- name: delegateColumn.expanded ? "go-down" : "go-next"
131-
132- height: units.gu(2)
133- width: height
134-
135- anchors {
136- leftMargin: units.gu(1)
137- topMargin: units.gu(2)
138- top: parent.top
139- }
140- }
141-
142- Label {
143- width: parent.width - expandedIcon.width - units.gu(3)
144- anchors.verticalCenter: expandedIcon.verticalCenter
145-
146- text: folderName ? folderName : i18n.tr("All Bookmarks")
147- fontSize: "small"
148- }
149- }
150-
151- ListItem.ThinDivider {
152- anchors {
153- left: parent.left
154- right: parent.right
155- bottom: parent.bottom
156- bottomMargin: units.gu(1)
157- }
158- }
159-
160- MouseArea {
161- anchors.fill: parent
162- onClicked: delegateColumn.expanded = !delegateColumn.expanded
163- }
164- }
165-
166- Loader {
167- anchors {
168- left: parent.left
169- right: parent.right
170- }
171-
172- height: item ? item.contentHeight : 0
173-
174- visible: status == Loader.Ready
175-
176- active: delegateColumn.expanded
177- sourceComponent: ListView {
178- readonly property bool isAllBookmarksFolder: folder === ""
179-
180- interactive: false
181-
182- model: {
183- if (isAllBookmarksFolder) {
184- return BookmarksModelUtils.prependHomepageToBookmarks(entries, {
185- title: i18n.tr("Homepage"),
186- url: bookmarksFoldersViewItem.homeBookmarkUrl
187- })
188- }
189-
190- return entries
191- }
192-
193- delegate: UrlDelegate{
194- id: urlDelegate
195- objectName: "urlDelegate_" + index
196-
197- property var entry: isAllBookmarksFolder ? modelData : model
198-
199- width: parent.width
200- height: units.gu(5)
201-
202- removable: !isAllBookmarksFolder || index !== 0
203-
204- icon: entry.icon ? entry.icon : ""
205- title: entry.title ? entry.title : entry.url
206- url: entry.url
207-
208- onClicked: bookmarksFoldersViewItem.bookmarkClicked(url)
209- onRemoved: bookmarksFoldersViewItem.bookmarkRemoved(url)
210- }
211- }
212- }
213+ fill: parent
214+ leftMargin: units.gu(2)
215+ rightMargin: units.gu(2)
216+ }
217+
218+ Row {
219+ anchors {
220+ left: parent.left
221+ leftMargin: units.gu(1.5)
222+ right: parent.right
223+ }
224+
225+ height: units.gu(6)
226+ spacing: units.gu(1.5)
227+
228+ opacity: (type == "emptyFolder") ? 0.3 : 1.0
229+
230+ Icon {
231+ id: expandedIcon
232+ name: (type == "expandedFolder") ? "go-down" : "go-next"
233+
234+ height: units.gu(2)
235+ width: height
236+
237+ anchors {
238+ leftMargin: units.gu(1)
239+ topMargin: units.gu(2)
240+ top: parent.top
241+ }
242+ }
243+
244+ Label {
245+ width: parent.width - expandedIcon.width - units.gu(3)
246+ anchors.verticalCenter: expandedIcon.verticalCenter
247+
248+ text: folder || i18n.tr("All Bookmarks")
249+ fontSize: "small"
250+ }
251+ }
252+
253+ ListItem.ThinDivider {
254+ anchors {
255+ left: parent.left
256+ right: parent.right
257+ bottom: parent.bottom
258+ bottomMargin: units.gu(1)
259+ }
260+ }
261+
262+ MouseArea {
263+ anchors.fill: parent
264+ onClicked: bookmarksProxyModel.toggleFolderState(folder)
265 }
266 }
267 }
268
269=== modified file 'src/app/webbrowser/BookmarksView.qml'
270--- src/app/webbrowser/BookmarksView.qml 2015-11-12 16:16:45 +0000
271+++ src/app/webbrowser/BookmarksView.qml 2015-12-02 14:15:47 +0000
272@@ -24,7 +24,7 @@
273 FocusScope {
274 id: bookmarksView
275
276- property alias homepageUrl: bookmarksFoldersView.homeBookmarkUrl
277+ property alias homepageUrl: bookmarksFoldersView.homepageUrl
278
279 signal bookmarkEntryClicked(url url)
280 signal done()
281@@ -51,10 +51,10 @@
282
283 onBookmarkClicked: bookmarksView.bookmarkEntryClicked(url)
284 onBookmarkRemoved: {
285- if (BookmarksModel.count == 1) {
286+ BookmarksModel.remove(url)
287+ if (count == 0) {
288 done()
289 }
290- BookmarksModel.remove(url)
291 }
292 }
293
294
295=== modified file 'src/app/webbrowser/CMakeLists.txt'
296--- src/app/webbrowser/CMakeLists.txt 2015-10-18 19:21:48 +0000
297+++ src/app/webbrowser/CMakeLists.txt 2015-12-02 14:15:47 +0000
298@@ -13,6 +13,7 @@
299 bookmarks-model.cpp
300 bookmarks-folder-model.cpp
301 bookmarks-folderlist-model.cpp
302+ collapsible-bookmarks-model.cpp
303 history-domain-model.cpp
304 history-domainlist-chronological-model.cpp
305 history-domainlist-model.cpp
306
307=== modified file 'src/app/webbrowser/NewTabView.qml'
308--- src/app/webbrowser/NewTabView.qml 2015-10-22 08:12:32 +0000
309+++ src/app/webbrowser/NewTabView.qml 2015-12-02 14:15:47 +0000
310@@ -63,10 +63,11 @@
311 }
312
313 Flickable {
314+ id: flickable
315 anchors.fill: parent
316 contentHeight: internal.seeMoreBookmarksView ?
317- bookmarksFolderListViewLoader.height + units.gu(6) :
318- contentColumn.height
319+ title.height + separator.height + bookmarksViewLoader.height :
320+ contentColumn.height
321
322 Column {
323 id: contentColumn
324@@ -78,6 +79,7 @@
325 height: childrenRect.height
326
327 Row {
328+ id: title
329 height: units.gu(6)
330 anchors {
331 left: parent.left
332@@ -127,6 +129,7 @@
333 }
334
335 Rectangle {
336+ id: separator
337 height: units.gu(0.1)
338 anchors {
339 left: parent.left
340@@ -137,20 +140,19 @@
341 }
342
343 Loader {
344- id: bookmarksFolderListViewLoader
345+ id: bookmarksViewLoader
346
347 anchors {
348 left: parent.left
349 right: parent.right
350 }
351-
352- height: status == Loader.Ready ? item.height : 0
353+ height: active ? (flickable.height - title.height - separator.height) : 0
354
355 active: internal.seeMoreBookmarksView
356+ clip: active
357
358 sourceComponent: BookmarksFoldersView {
359- homeBookmarkUrl: newTabView.settingsObject.homepage
360-
361+ homepageUrl: newTabView.settingsObject.homepage
362 onBookmarkClicked: newTabView.bookmarkClicked(url)
363 onBookmarkRemoved: newTabView.bookmarkRemoved(url)
364 }
365
366=== modified file 'src/app/webbrowser/bookmarks-model.cpp'
367--- src/app/webbrowser/bookmarks-model.cpp 2015-07-27 15:36:59 +0000
368+++ src/app/webbrowser/bookmarks-model.cpp 2015-12-02 14:15:47 +0000
369@@ -31,7 +31,7 @@
370 BookmarksModel is a list model that stores bookmark entries for quick access
371 to favourite websites. For a given URL, the following information is stored:
372 page title and URL to the favorite icon if any.
373- The model is sorted alphabetically at all times (by URL).
374+ The model is sorted by recency at all times (most recently created bookmark first).
375
376 The information is persistently stored on disk in a SQLite database.
377 The database is read at startup to populate the model, and whenever a new
378
379=== added file 'src/app/webbrowser/collapsible-bookmarks-model.cpp'
380--- src/app/webbrowser/collapsible-bookmarks-model.cpp 1970-01-01 00:00:00 +0000
381+++ src/app/webbrowser/collapsible-bookmarks-model.cpp 2015-12-02 14:15:47 +0000
382@@ -0,0 +1,572 @@
383+/*
384+ * Copyright 2015 Canonical Ltd.
385+ *
386+ * This file is part of webbrowser-app.
387+ *
388+ * webbrowser-app is free software; you can redistribute it and/or modify
389+ * it under the terms of the GNU General Public License as published by
390+ * the Free Software Foundation; version 3.
391+ *
392+ * webbrowser-app is distributed in the hope that it will be useful,
393+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
394+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
395+ * GNU General Public License for more details.
396+ *
397+ * You should have received a copy of the GNU General Public License
398+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
399+ */
400+
401+#include "collapsible-bookmarks-model.h"
402+
403+// Qt
404+#include <QtCore/QUrl>
405+
406+/*!
407+ \class CollapsibleBookmarksModel
408+ \brief List model that exposes bookmarked websites in collapsible folders.
409+
410+ CollapsibleBookmarksModel is a proxy model that takes a BookmarksModel as
411+ source and exposes its folders and bookmarks. Folders are collapsed by
412+ default (meaning that no bookmarks under them are advertised by the model)
413+ and can be expanded or collapsed again using the toggleFolderState()
414+ method.
415+
416+ The first folder in the model has no name, it contains all bookmarks that
417+ are not in any other folder. It may also contain a special placeholder
418+ entry (always first) that can be used to display a special homepage
419+ bookmark. This is controlled by setting the 'prependHomepage' property
420+ (true by default).
421+
422+ The folders are sorted by alphabetical order in the model, and inside each
423+ folder bookmarks are sorted by recency (most recently created first).
424+
425+ By default, empty folders are hidden, but they can be shown by setting the
426+ 'showEmptyFolders' property to true.
427+
428+ The roles exposed by the model are as follows:
429+ - "url" (url): for a bookmark, the associated URL
430+ - "title" (string): for a bookmark, the associated title
431+ - "icon" (url): for a bookmark, the associated favicon
432+ - "created" (datetime): for a bookmark, the creation timestamp
433+ - "folder" (string): the folder name
434+ - "type" (string): one of "homepage", "bookmark", "emptyFolder",
435+ "collapsedFolder", "expandedFolder"
436+*/
437+CollapsibleBookmarksModel::CollapsibleBookmarksModel(QObject* parent)
438+ : QAbstractListModel(parent)
439+ , m_sourceModel(nullptr)
440+ , m_prependHomepage(true)
441+ , m_showEmptyFolders(false)
442+ , m_count(0)
443+{}
444+
445+CollapsibleBookmarksModel::~CollapsibleBookmarksModel()
446+{}
447+
448+BookmarksModel* CollapsibleBookmarksModel::sourceModel() const
449+{
450+ return m_sourceModel;
451+}
452+
453+void CollapsibleBookmarksModel::setSourceModel(BookmarksModel* sourceModel)
454+{
455+ if (sourceModel != m_sourceModel) {
456+ beginResetModel();
457+ if (m_sourceModel) {
458+ m_sourceModel->disconnect(this);
459+ }
460+ m_folders.clear();
461+ m_sourceModel = sourceModel;
462+ populateModel();
463+ if (m_sourceModel) {
464+ connect(m_sourceModel, SIGNAL(folderAdded(const QString&)),
465+ SLOT(onFolderAdded(const QString&)));
466+ connect(m_sourceModel, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
467+ SLOT(onRowsInserted(const QModelIndex&, int, int)));
468+ connect(m_sourceModel, SIGNAL(removed(const QUrl&)), SLOT(onBookmarkRemoved(const QUrl&)));
469+ connect(m_sourceModel, SIGNAL(modelReset()), SLOT(onModelReset()));
470+ connect(m_sourceModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, QVector<int>)),
471+ SLOT(onDataChanged(const QModelIndex&, const QModelIndex&, QVector<int>)));
472+ }
473+ endResetModel();
474+ Q_EMIT sourceModelChanged();
475+ }
476+}
477+
478+bool CollapsibleBookmarksModel::prependHomepage() const
479+{
480+ return m_prependHomepage;
481+}
482+
483+void CollapsibleBookmarksModel::setPrependHomepage(bool value)
484+{
485+ if (value != m_prependHomepage) {
486+ m_prependHomepage = value;
487+ if (m_sourceModel) {
488+ QPair<bool, QList<QVariantMap>>& allFolder = m_folders[QStringLiteral("")];
489+ int first = ((allFolder.second.isEmpty() && !m_showEmptyFolders) ? 0 : 1);
490+ int last = allFolder.first ? 1 : 0;
491+ if (last >= first) {
492+ if (m_prependHomepage) {
493+ beginInsertRows(QModelIndex(), first, last);
494+ m_count += (last - first + 1);
495+ endInsertRows();
496+ } else {
497+ beginRemoveRows(QModelIndex(), first, last);
498+ m_count -= (last - first + 1);
499+ endRemoveRows();
500+ }
501+ }
502+ }
503+ Q_EMIT prependHomepageChanged();
504+ }
505+}
506+
507+bool CollapsibleBookmarksModel::showEmptyFolders() const
508+{
509+ return m_showEmptyFolders;
510+}
511+
512+void CollapsibleBookmarksModel::setShowEmptyFolders(bool value)
513+{
514+ if (value != m_showEmptyFolders) {
515+ m_showEmptyFolders = value;
516+ FoldersMap::const_iterator i;
517+ int index = -1;
518+ for (i = m_folders.constBegin(); i != m_folders.constEnd(); ++i) {
519+ ++index;
520+ if (i.value().second.isEmpty() && !(i.key().isEmpty() && m_prependHomepage)) {
521+ if (m_showEmptyFolders) {
522+ beginInsertRows(QModelIndex(), index, index);
523+ ++m_count;
524+ endInsertRows();
525+ } else {
526+ beginRemoveRows(QModelIndex(), index, index);
527+ --m_count;
528+ endRemoveRows();
529+ --index;
530+ }
531+ } else {
532+ index += (i.value().first ? i.value().second.count() : 0);
533+ index += ((i.key().isEmpty() && i.value().first && m_prependHomepage) ? 1 : 0);
534+ }
535+ }
536+ Q_EMIT showEmptyFoldersChanged();
537+ }
538+}
539+
540+QHash<int, QByteArray> CollapsibleBookmarksModel::roleNames() const
541+{
542+ static QHash<int, QByteArray> roles;
543+ if (roles.isEmpty()) {
544+ roles[Url] = "url";
545+ roles[Title] = "title";
546+ roles[Icon] = "icon";
547+ roles[Created] = "created";
548+ roles[Folder] = "folder";
549+ roles[Type] = "type";
550+ }
551+ return roles;
552+}
553+
554+int CollapsibleBookmarksModel::rowCount(const QModelIndex& parent) const
555+{
556+ Q_UNUSED(parent);
557+ return m_count;
558+}
559+
560+QVariant CollapsibleBookmarksModel::data(const QModelIndex& index, int role) const
561+{
562+ if (!index.isValid()) {
563+ return QVariant();
564+ }
565+ QString folder;
566+ QVariantMap bookmark;
567+ bool homepage = false;
568+ int row = index.row();
569+ FoldersMap::const_iterator i;
570+ for (i = m_folders.constBegin(); i != m_folders.constEnd(); ++i) {
571+ if (!m_showEmptyFolders && i.value().second.isEmpty() &&
572+ !(i.key().isEmpty() && m_prependHomepage)) {
573+ continue;
574+ }
575+ if (row == 0) {
576+ folder = i.key();
577+ break;
578+ } else if (i.value().first) {
579+ bool allBookmarksWithHomepage = (i.key().isEmpty() && m_prependHomepage);
580+ if (allBookmarksWithHomepage) {
581+ if (row == 1) {
582+ homepage = true;
583+ break;
584+ } else {
585+ --row;
586+ }
587+ }
588+ int count = i.value().second.count();
589+ if (row <= count) {
590+ bookmark = i.value().second.at(row - 1);
591+ break;
592+ } else {
593+ row -= (count + 1);
594+ }
595+ } else {
596+ --row;
597+ }
598+ }
599+ switch(role) {
600+ case Url:
601+ return bookmark[QStringLiteral("url")];
602+ case Title:
603+ return bookmark[QStringLiteral("title")];
604+ case Icon:
605+ return bookmark[QStringLiteral("icon")];
606+ case Created:
607+ return bookmark[QStringLiteral("created")];
608+ case Folder:
609+ return (homepage ? QStringLiteral("") : (folder.isNull() ? bookmark[QStringLiteral("folder")] : folder));
610+ case Type:
611+ if (homepage) {
612+ return QStringLiteral("homepage");
613+ } else if (folder.isNull()) {
614+ return QStringLiteral("bookmark");
615+ } else if (i.value().second.isEmpty() && !(i.key().isEmpty() && m_prependHomepage)) {
616+ return QStringLiteral("emptyFolder");
617+ } else if (i.value().first) {
618+ return QStringLiteral("expandedFolder");
619+ } else {
620+ return QStringLiteral("collapsedFolder");
621+ }
622+ default:
623+ return QVariant();
624+ }
625+}
626+
627+void CollapsibleBookmarksModel::toggleFolderState(const QString& folder) {
628+ int index = folderIndex(folder);
629+ if (index == -1) {
630+ return;
631+ }
632+ QPair<bool, QList<QVariantMap>>& folderInfo = m_folders[folder];
633+ if (folderInfo.second.isEmpty() && !(folder.isEmpty() && m_prependHomepage)) {
634+ return;
635+ }
636+ bool expanded = folderInfo.first;
637+ int first = index + 1;
638+ int last = index + folderInfo.second.count();
639+ if (folder.isEmpty() && m_prependHomepage) {
640+ ++last;
641+ }
642+ if (expanded) {
643+ beginRemoveRows(QModelIndex(), first, last);
644+ m_count -= (last - first + 1);
645+ } else {
646+ beginInsertRows(QModelIndex(), first, last);
647+ m_count += (last - first + 1);
648+ }
649+ folderInfo.first = !expanded;
650+ if (expanded) {
651+ endRemoveRows();
652+ } else {
653+ endInsertRows();
654+ }
655+ QModelIndex modelIndex = this->index(index, 0);
656+ Q_EMIT dataChanged(modelIndex, modelIndex, QVector<int>() << Type);
657+}
658+
659+void CollapsibleBookmarksModel::populateModel()
660+{
661+ if (!m_sourceModel) {
662+ return;
663+ }
664+ Q_FOREACH(const QString& folder, m_sourceModel->folders()) {
665+ m_folders[folder].first = false;
666+ }
667+ m_folders[QStringLiteral("")].first = true;
668+ int count = m_sourceModel->rowCount();
669+ for (int i = 0; i < count; ++i) {
670+ QModelIndex index = m_sourceModel->index(i);
671+ QVariantMap variant;
672+ variant[QStringLiteral("url")] = m_sourceModel->data(index, BookmarksModel::Url);
673+ variant[QStringLiteral("title")] = m_sourceModel->data(index, BookmarksModel::Title);
674+ variant[QStringLiteral("icon")] = m_sourceModel->data(index, BookmarksModel::Icon);
675+ variant[QStringLiteral("created")] = m_sourceModel->data(index, BookmarksModel::Created);
676+ QString folder = m_sourceModel->data(index, BookmarksModel::Folder).toString();
677+ variant[QStringLiteral("folder")] = folder;
678+ m_folders[folder].second.append(variant);
679+ }
680+ m_count = 0;
681+ FoldersMap::const_iterator i;
682+ for (i = m_folders.constBegin(); i != m_folders.constEnd(); ++i) {
683+ bool empty = i.value().second.isEmpty();
684+ if (!empty || m_showEmptyFolders || (i.key().isEmpty() && m_prependHomepage)) {
685+ ++m_count;
686+ }
687+ m_count += (i.value().first ? i.value().second.count() : 0);
688+ m_count += ((i.key().isEmpty() && i.value().first && m_prependHomepage) ? 1 : 0);
689+ }
690+}
691+
692+int CollapsibleBookmarksModel::folderIndex(const QString& folder) const
693+{
694+ if (!m_showEmptyFolders && m_folders[folder].second.isEmpty() &&
695+ !(folder.isEmpty() && m_prependHomepage)) {
696+ return -1;
697+ }
698+ int index = 0;
699+ FoldersMap::const_iterator i;
700+ for (i = m_folders.constBegin(); i != m_folders.constEnd(); ++i) {
701+ if (i.key() == folder) {
702+ return index;
703+ }
704+ if (m_showEmptyFolders || !i.value().second.isEmpty() ||
705+ (i.key().isEmpty() && m_prependHomepage)) {
706+ ++index;
707+ }
708+ if (i.value().first) {
709+ index += i.value().second.count();
710+ index += ((i.key().isEmpty() && m_prependHomepage) ? 1 : 0);
711+ }
712+ }
713+ return -1;
714+}
715+
716+QPair<QString, int> CollapsibleBookmarksModel::bookmarkPosition(const QUrl& url) const
717+{
718+ // This assumes url is not equal to the homepage, which should be safe as
719+ // the homepage is not supposed to be stored in the bookmarks model anyway.
720+ int index = 0;
721+ int homepageOffset = 0;
722+ FoldersMap::const_iterator i;
723+ for (i = m_folders.constBegin(); i != m_folders.constEnd(); ++i) {
724+ if (m_showEmptyFolders || !i.value().second.isEmpty() ||
725+ (i.key().isEmpty() && m_prependHomepage)) {
726+ ++index;
727+ }
728+ if (i.key().isEmpty() && m_prependHomepage && i.value().first) {
729+ homepageOffset = 1;
730+ }
731+ const QList<QVariantMap>& bookmarks = i.value().second;
732+ for (int j = 0; j < bookmarks.count(); ++j) {
733+ if (bookmarks.at(j)["url"].toUrl() == url) {
734+ if (i.value().first) {
735+ return QPair<QString, int>(i.key(), index + j + homepageOffset);
736+ } else {
737+ return QPair<QString, int>(i.key(), -1);
738+ }
739+ }
740+ }
741+ index += (i.value().first ? bookmarks.count() : 0);
742+ }
743+ return QPair<QString, int>(QString(), -1);
744+}
745+
746+void CollapsibleBookmarksModel::onFolderAdded(const QString& folder)
747+{
748+ if (m_folders.contains(folder)) {
749+ return;
750+ }
751+ m_folders[folder].first = false;
752+ int index = folderIndex(folder);
753+ if (index != -1) {
754+ beginInsertRows(QModelIndex(), index, index);
755+ ++m_count;
756+ endInsertRows();
757+ }
758+}
759+
760+void CollapsibleBookmarksModel::onRowsInserted(const QModelIndex& parent, int start, int end)
761+{
762+ for (int i = start; i <= end; ++i) {
763+ QModelIndex index = m_sourceModel->index(i);
764+ QString folder = m_sourceModel->data(index, BookmarksModel::Folder).toString();
765+ int folderIndex = this->folderIndex(folder);
766+ QDateTime created = m_sourceModel->data(index, BookmarksModel::Created).toDateTime();
767+ if (folderIndex == -1) {
768+ // new folder, insert it
769+ m_folders[folder].first = false;
770+ }
771+ bool expanded = m_folders[folder].first;
772+ QList<QVariantMap>& bookmarks = m_folders[folder].second;
773+ int newIndexInFolder = bookmarks.count();
774+ for (int j = 0; j < newIndexInFolder; ++j) {
775+ if (created >= bookmarks.at(j)[QStringLiteral("created")].toDateTime()) {
776+ newIndexInFolder = j;
777+ break;
778+ }
779+ }
780+ if (expanded) {
781+ int newGlobalIndex = folderIndex + newIndexInFolder + 1;
782+ if (folder.isEmpty() && m_prependHomepage) {
783+ ++newGlobalIndex;
784+ }
785+ beginInsertRows(QModelIndex(), newGlobalIndex, newGlobalIndex);
786+ }
787+ QVariantMap variant;
788+ variant[QStringLiteral("url")] = m_sourceModel->data(index, BookmarksModel::Url);
789+ variant[QStringLiteral("title")] = m_sourceModel->data(index, BookmarksModel::Title);
790+ variant[QStringLiteral("icon")] = m_sourceModel->data(index, BookmarksModel::Icon);
791+ variant[QStringLiteral("created")] = created;
792+ variant[QStringLiteral("folder")] = folder;
793+ m_folders[folder].second.insert(newIndexInFolder, variant);
794+ if (expanded) {
795+ ++m_count;
796+ endInsertRows();
797+ } else if (folderIndex == -1) {
798+ // this is a new folder, doing that now (after inserting the bookmark)
799+ // otherwise folderIndex() would return -1
800+ folderIndex = this->folderIndex(folder);
801+ beginInsertRows(QModelIndex(), folderIndex, folderIndex);
802+ ++m_count;
803+ endInsertRows();
804+ }
805+ }
806+}
807+
808+void CollapsibleBookmarksModel::onBookmarkRemoved(const QUrl& url)
809+{
810+ QPair<QString, int> position = bookmarkPosition(url);
811+ if (position.second != -1) {
812+ int first = position.second;
813+ if (!m_showEmptyFolders && (m_folders[position.first].second.count() == 1) &&
814+ !(position.first.isEmpty() && m_prependHomepage)) {
815+ // removed the last bookmark from the folder, which becomes empty: hide it
816+ --first;
817+ }
818+ beginRemoveRows(QModelIndex(), first, position.second);
819+ m_count -= (position.second - first + 1);
820+ }
821+ QList<QVariantMap>& bookmarks = m_folders[position.first].second;
822+ for (int i = 0; i < bookmarks.count(); ++i) {
823+ if (bookmarks.at(i)[QStringLiteral("url")].toUrl() == url) {
824+ bookmarks.removeAt(i);
825+ break;
826+ }
827+ }
828+ if (bookmarks.isEmpty() && !(position.first.isEmpty() && m_prependHomepage)) {
829+ // the folder has become empty, collapse it
830+ m_folders[position.first].first = false;
831+ if (m_showEmptyFolders) {
832+ QModelIndex folderModelIndex = this->index(folderIndex(position.first));
833+ Q_EMIT dataChanged(folderModelIndex, folderModelIndex, QVector<int>() << Type);
834+ }
835+ }
836+ if (position.second != -1) {
837+ endRemoveRows();
838+ }
839+}
840+
841+void CollapsibleBookmarksModel::onModelReset()
842+{
843+ beginResetModel();
844+ populateModel();
845+ endResetModel();
846+}
847+
848+void CollapsibleBookmarksModel::onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, QVector<int> roles)
849+{
850+ Q_UNUSED(roles);
851+ int first = topLeft.row();
852+ int last = bottomRight.row();
853+ for (int i = first; i <= last; ++i) {
854+ QModelIndex index = m_sourceModel->index(i);
855+ QUrl url = m_sourceModel->data(index, BookmarksModel::Url).toUrl();
856+ QString title = m_sourceModel->data(index, BookmarksModel::Title).toString();
857+ QUrl icon = m_sourceModel->data(index, BookmarksModel::Icon).toUrl();
858+ QString folder = m_sourceModel->data(index, BookmarksModel::Folder).toString();
859+ int folderIndex = this->folderIndex(folder);
860+ QDateTime created = m_sourceModel->data(index, BookmarksModel::Created).toDateTime();
861+ bool expanded = m_folders[folder].first;
862+ QPair<QString, int> position = bookmarkPosition(url);
863+ if (position.first != folder) {
864+ // the bookmark was moved to a different folder
865+ QList<QVariantMap>& targetFolder = m_folders[folder].second;
866+ int newIndexInFolder = targetFolder.count();
867+ int newGlobalIndex = 0;
868+ for (int j = 0; j < newIndexInFolder; ++j) {
869+ if (created >= targetFolder.at(j)[QStringLiteral("created")].toDateTime()) {
870+ newIndexInFolder = j;
871+ break;
872+ }
873+ }
874+ if (expanded) {
875+ newGlobalIndex = folderIndex + newIndexInFolder + 1;
876+ if (folder.isEmpty() && m_prependHomepage) {
877+ ++newGlobalIndex;
878+ }
879+ }
880+ int sourceFolderIndex = this->folderIndex(position.first);
881+ if (expanded && (position.second != -1)) {
882+ // both source folder and target folder are expanded
883+ beginMoveRows(QModelIndex(), position.second, position.second,
884+ QModelIndex(), newGlobalIndex);
885+ if (position.second > newGlobalIndex) {
886+ ++sourceFolderIndex;
887+ }
888+ } else if (expanded) {
889+ // source folder is collapsed, target folder is expanded
890+ beginInsertRows(QModelIndex(), newGlobalIndex, newGlobalIndex);
891+ } else if (position.second != -1) {
892+ // source folder is expanded, target folder is collapsed
893+ beginRemoveRows(QModelIndex(), position.second, position.second);
894+ }
895+ // actually move the bookmark from one folder to the other
896+ QList<QVariantMap>& sourceFolder = m_folders[position.first].second;
897+ QList<QVariantMap>::iterator j;
898+ for (j = sourceFolder.begin(); j != sourceFolder.end(); ++j) {
899+ if ((*j)["url"].toUrl() == url) {
900+ sourceFolder.erase(j);
901+ break;
902+ }
903+ }
904+ QVariantMap bookmark;
905+ bookmark["url"] = url;
906+ bookmark["title"] = title;
907+ bookmark["icon"] = icon;
908+ bookmark["folder"] = folder;
909+ bookmark["created"] = created;
910+ targetFolder.insert(newIndexInFolder, bookmark);
911+ if (expanded && (position.second != -1)) {
912+ endMoveRows();
913+ if (newGlobalIndex > position.second) {
914+ --newGlobalIndex;
915+ }
916+ QModelIndex changedIndex = this->index(newGlobalIndex);
917+ Q_EMIT dataChanged(changedIndex, changedIndex);
918+ } else if (expanded) {
919+ ++m_count;
920+ endInsertRows();
921+ } else if (position.second != -1) {
922+ --m_count;
923+ endRemoveRows();
924+ }
925+ if (sourceFolder.isEmpty() && !(position.first.isEmpty() && m_prependHomepage)) {
926+ // removed the last bookmark from the folder, which becomes empty, collapse it
927+ m_folders[position.first].first = false;
928+ if (m_showEmptyFolders) {
929+ QModelIndex folderModelIndex = this->index(sourceFolderIndex);
930+ Q_EMIT dataChanged(folderModelIndex, folderModelIndex, QVector<int>() << Type);
931+ } else {
932+ // hide it
933+ beginRemoveRows(QModelIndex(), sourceFolderIndex, sourceFolderIndex);
934+ --m_count;
935+ endRemoveRows();
936+ }
937+ }
938+ } else if (position.second != -1) {
939+ // the bookmark has changed and its folder is currently expanded
940+ QList<QVariantMap>& bookmarks = m_folders[position.first].second;
941+ QList<QVariantMap>::iterator j = bookmarks.begin();
942+ for (j = bookmarks.begin(); j != bookmarks.end(); ++j) {
943+ if ((*j)["url"].toUrl() == url) {
944+ (*j)["title"] = title;
945+ (*j)["icon"] = icon;
946+ (*j)["created"] = created;
947+ break;
948+ }
949+ }
950+ QModelIndex changedIndex = this->index(position.second);
951+ Q_EMIT dataChanged(changedIndex, changedIndex);
952+ }
953+ }
954+}
955
956=== added file 'src/app/webbrowser/collapsible-bookmarks-model.h'
957--- src/app/webbrowser/collapsible-bookmarks-model.h 1970-01-01 00:00:00 +0000
958+++ src/app/webbrowser/collapsible-bookmarks-model.h 2015-12-02 14:15:47 +0000
959@@ -0,0 +1,98 @@
960+/*
961+ * Copyright 2015 Canonical Ltd.
962+ *
963+ * This file is part of webbrowser-app.
964+ *
965+ * webbrowser-app is free software; you can redistribute it and/or modify
966+ * it under the terms of the GNU General Public License as published by
967+ * the Free Software Foundation; version 3.
968+ *
969+ * webbrowser-app is distributed in the hope that it will be useful,
970+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
971+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
972+ * GNU General Public License for more details.
973+ *
974+ * You should have received a copy of the GNU General Public License
975+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
976+ */
977+
978+#ifndef __BOOKMARKS_PROXY_MODEL_H__
979+#define __BOOKMARKS_PROXY_MODEL_H__
980+
981+// Qt
982+#include <QtCore/QAbstractListModel>
983+#include <QtCore/QMap>
984+#include <QtCore/QPair>
985+#include <QtCore/QString>
986+#include <QtCore/QVariant>
987+
988+#include "bookmarks-model.h"
989+
990+class QUrl;
991+
992+class CollapsibleBookmarksModel : public QAbstractListModel
993+{
994+ Q_OBJECT
995+
996+ Q_PROPERTY(BookmarksModel* sourceModel READ sourceModel WRITE setSourceModel NOTIFY sourceModelChanged)
997+ Q_PROPERTY(bool prependHomepage READ prependHomepage WRITE setPrependHomepage NOTIFY prependHomepageChanged)
998+ Q_PROPERTY(bool showEmptyFolders READ showEmptyFolders WRITE setShowEmptyFolders NOTIFY showEmptyFoldersChanged)
999+
1000+ Q_ENUMS(Roles)
1001+
1002+public:
1003+ CollapsibleBookmarksModel(QObject* parent=0);
1004+ ~CollapsibleBookmarksModel();
1005+
1006+ enum Roles {
1007+ Url = BookmarksModel::Url,
1008+ Title = BookmarksModel::Title,
1009+ Icon = BookmarksModel::Icon,
1010+ Created = BookmarksModel::Created,
1011+ Folder = BookmarksModel::Folder,
1012+ Type
1013+ };
1014+
1015+ // reimplemented from QAbstractListModel
1016+ QHash<int, QByteArray> roleNames() const;
1017+ int rowCount(const QModelIndex& parent=QModelIndex()) const;
1018+ QVariant data(const QModelIndex& index, int role) const;
1019+
1020+ BookmarksModel* sourceModel() const;
1021+ void setSourceModel(BookmarksModel* sourceModel);
1022+
1023+ bool prependHomepage() const;
1024+ void setPrependHomepage(bool value);
1025+
1026+ bool showEmptyFolders() const;
1027+ void setShowEmptyFolders(bool value);
1028+
1029+ Q_INVOKABLE void toggleFolderState(const QString& folder);
1030+
1031+Q_SIGNALS:
1032+ void sourceModelChanged();
1033+ void prependHomepageChanged();
1034+ void showEmptyFoldersChanged();
1035+
1036+private:
1037+ BookmarksModel* m_sourceModel;
1038+ bool m_prependHomepage;
1039+ bool m_showEmptyFolders;
1040+ // the first folder in the map has an empty (non null) name, it contains all bookmarks that are not in a specific folder
1041+ typedef QMap<QString, QPair<bool, QList<QVariantMap>>> FoldersMap;
1042+ FoldersMap m_folders;
1043+ int m_count;
1044+
1045+ void populateModel();
1046+ int folderIndex(const QString& folder) const;
1047+ QPair<QString, int> bookmarkPosition(const QUrl& url) const;
1048+
1049+private Q_SLOTS:
1050+ void onFolderAdded(const QString& folder);
1051+ void onRowsInserted(const QModelIndex& parent, int start, int end);
1052+ void onBookmarkRemoved(const QUrl& url);
1053+ void onModelReset();
1054+ void onDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, QVector<int> roles);
1055+};
1056+
1057+#endif // __BOOKMARKS_PROXY_MODEL_H__
1058
1059=== modified file 'src/app/webbrowser/webbrowser-app.cpp'
1060--- src/app/webbrowser/webbrowser-app.cpp 2015-10-22 15:07:26 +0000
1061+++ src/app/webbrowser/webbrowser-app.cpp 2015-12-02 14:15:47 +0000
1062@@ -19,6 +19,7 @@
1063 #include "bookmarks-model.h"
1064 #include "bookmarks-folderlist-model.h"
1065 #include "cache-deleter.h"
1066+#include "collapsible-bookmarks-model.h"
1067 #include "config.h"
1068 #include "file-operations.h"
1069 #include "history-domainlist-chronological-model.h"
1070@@ -92,6 +93,7 @@
1071 qmlRegisterType<TabsModel>(uri, 0, 1, "TabsModel");
1072 qmlRegisterSingletonType<BookmarksModel>(uri, 0, 1, "BookmarksModel", BookmarksModel_singleton_factory);
1073 qmlRegisterType<BookmarksFolderListModel>(uri, 0, 1, "BookmarksFolderListModel");
1074+ qmlRegisterType<CollapsibleBookmarksModel>(uri, 0, 1, "CollapsibleBookmarksModel");
1075 qmlRegisterSingletonType<FileOperations>(uri, 0, 1, "FileOperations", FileOperations_singleton_factory);
1076 qmlRegisterType<SearchEngine>(uri, 0, 1, "SearchEngine");
1077 qmlRegisterSingletonType<CacheDeleter>(uri, 0, 1, "CacheDeleter", CacheDeleter_singleton_factory);
1078
1079=== modified file 'tests/autopilot/webbrowser_app/emulators/browser.py'
1080--- tests/autopilot/webbrowser_app/emulators/browser.py 2015-11-26 13:42:12 +0000
1081+++ tests/autopilot/webbrowser_app/emulators/browser.py 2015-12-02 14:15:47 +0000
1082@@ -134,9 +134,9 @@
1083
1084 def get_new_tab_view(self):
1085 if self.wide:
1086- return self.wait_select_single("NewTabViewWide", visible=True)
1087+ return self.wait_select_single(NewTabViewWide, visible=True)
1088 else:
1089- return self.wait_select_single("NewTabView", visible=True)
1090+ return self.wait_select_single(NewTabView, visible=True)
1091
1092 # Since the NewPrivateTabView does not define any new QML property in its
1093 # extended file, it does not report itself to autopilot with the same name
1094@@ -169,9 +169,9 @@
1095 def get_bookmarks_view(self):
1096 try:
1097 if self.wide:
1098- return self.select_single("BookmarksViewWide")
1099+ return self.select_single(BookmarksViewWide)
1100 else:
1101- return self.select_single("BookmarksView")
1102+ return self.select_single(BookmarksView)
1103 except exceptions.StateNotFoundError:
1104 return None
1105
1106@@ -286,8 +286,7 @@
1107 return self.select_single("ChromeButton", objectName="drawerButton")
1108
1109 def get_drawer(self):
1110- return self.wait_select_single("QQuickItem", objectName="drawer",
1111- clip=False)
1112+ return self.wait_select_single(objectName="drawer", clip=False)
1113
1114 def get_drawer_action(self, actionName):
1115 drawer = self.get_drawer()
1116@@ -567,21 +566,19 @@
1117 def get_notopsites_label(self):
1118 return self.select_single(objectName="notopsites")
1119
1120- def get_top_site_items(self):
1121- return self.get_top_sites_list().get_delegates()
1122-
1123- def get_bookmarks_folder_list_view(self):
1124+ def _get_bookmarks_folder_list_view(self):
1125 return self.wait_select_single(BookmarksFoldersView)
1126
1127+ def get_bookmark_folders(self):
1128+ return self._get_bookmarks_folder_list_view().get_folder_delegates()
1129+
1130+ def get_bookmark_folder(self, folder):
1131+ view = self._get_bookmarks_folder_list_view()
1132+ return view.get_folder_delegate(folder)
1133+
1134 def get_bookmarks(self, folder_name):
1135- # assumes that the "more" button has been clicked
1136- folders = self.get_bookmarks_folder_list_view()
1137- folder_delegate = folders.get_folder_delegate(folder_name)
1138- return folders.get_urls_from_folder(folder_delegate)
1139-
1140- def get_folder_names(self):
1141- folders = self.get_bookmarks_folder_list_view().get_delegates()
1142- return [folder.folderName for folder in folders]
1143+ view = self._get_bookmarks_folder_list_view()
1144+ return view.get_ordered_bookmark_delegates(folder_name)
1145
1146
1147 class NewTabViewWide(uitk.UbuntuUIToolkitCustomProxyObjectBase):
1148@@ -596,7 +593,7 @@
1149 list = self.select_single(uitk.QQuickListView,
1150 objectName="bookmarksList")
1151 return sorted(list.select_many("DraggableUrlDelegateWide",
1152- objectName="bookmarkItem"),
1153+ objectName="bookmarkItem"),
1154 key=lambda delegate: delegate.globalRect.y)
1155
1156 def get_top_sites_list(self):
1157@@ -610,19 +607,63 @@
1158 return sorted(list.select_many(objectName="folderItem"),
1159 key=lambda delegate: delegate.globalRect.y)
1160
1161- def get_top_site_items(self):
1162- return self.get_top_sites_list().get_delegates()
1163-
1164 def get_bookmarks(self, folder_name):
1165- folders = self.get_folders_list()
1166- matches = [folder for folder in folders if folder.name == folder_name]
1167- if not len(matches) == 1:
1168- return []
1169- self.pointing_device.click_object(matches[0])
1170+ folder = self.get_bookmark_folder(folder_name)
1171+ self.pointing_device.click_object(folder)
1172 return self.get_bookmarks_list()
1173
1174- def get_folder_names(self):
1175- return [folder.name for folder in self.get_folders_list()]
1176+ def get_bookmark_folders(self):
1177+ return self.get_folders_list()
1178+
1179+ def get_bookmark_folder(self, folder):
1180+ self.go_to_section(1)
1181+ list = self.select_single(uitk.QQuickListView,
1182+ objectName="foldersList")
1183+ return list.select_single(objectName="folderItem", name=folder)
1184+
1185+
1186+class BookmarksViewBase(uitk.UbuntuUIToolkitCustomProxyObjectBase):
1187+
1188+ def close(self):
1189+ button = self.select_single(objectName="doneButton")
1190+ self.pointing_device.click_object(button)
1191+
1192+
1193+class BookmarksView(BookmarksViewBase):
1194+
1195+ def get_bookmark_folders(self):
1196+ return self.select_many(objectName="bookmarkViewFolderDelegate")
1197+
1198+ def get_bookmark_folder(self, folder):
1199+ return self.select_single(objectName="bookmarkViewFolderDelegate",
1200+ name=folder)
1201+
1202+ def get_bookmarks(self, folder_name):
1203+ bookmarks = self.select_many(objectName="bookmarkViewBookmarkDelegate",
1204+ folderName=folder_name)
1205+ return sorted(bookmarks, key=lambda delegate: delegate.globalRect.y)
1206+
1207+
1208+class BookmarksViewWide(BookmarksViewBase):
1209+
1210+ def get_bookmark_folders(self):
1211+ list = self.select_single(uitk.QQuickListView,
1212+ objectName="foldersList")
1213+ return list.select_many(objectName="folderItem")
1214+
1215+ def get_bookmark_folder(self, folder):
1216+ list = self.select_single(uitk.QQuickListView,
1217+ objectName="foldersList")
1218+ return list.select_single(objectName="folderItem", name=folder)
1219+
1220+ def get_bookmarks(self, folder_name):
1221+ folder = self.get_bookmark_folder(folder_name)
1222+ self.pointing_device.click_object(folder)
1223+ bookmarks = self.select_single(uitk.QQuickListView,
1224+ objectName="bookmarksList")
1225+ return sorted(bookmarks.select_many("DraggableUrlDelegateWide",
1226+ objectName="bookmarkItem"),
1227+ key=lambda delegate: delegate.globalRect.y)
1228
1229
1230 class UrlsList(uitk.UbuntuUIToolkitCustomProxyObjectBase):
1231@@ -700,24 +741,23 @@
1232
1233 class BookmarksFoldersView(uitk.UbuntuUIToolkitCustomProxyObjectBase):
1234
1235- def get_delegates(self):
1236- return sorted(self.select_many("QQuickItem",
1237- objectName="bookmarkFolderDelegate"),
1238- key=lambda delegate: delegate.globalRect.y)
1239+ def get_folder_delegates(self):
1240+ return sorted(
1241+ self.select_many(objectName="bookmarkViewFolderDelegate"),
1242+ key=lambda delegate: delegate.globalRect.y)
1243
1244 def get_folder_delegate(self, folder):
1245- return self.select_single("QQuickItem",
1246- objectName="bookmarkFolderDelegate",
1247- folderName=folder)
1248-
1249- def get_urls_from_folder(self, folder):
1250- return sorted(folder.select_many(UrlDelegate),
1251+ return self.select_single(objectName="bookmarkViewFolderDelegate",
1252+ name=folder)
1253+
1254+ def get_bookmark_delegates(self, folder):
1255+ return self.select_many(objectName="bookmarkViewBookmarkDelegate",
1256+ folderName=folder)
1257+
1258+ def get_ordered_bookmark_delegates(self, folder):
1259+ return sorted(self.get_bookmark_delegates(folder),
1260 key=lambda delegate: delegate.globalRect.y)
1261
1262- def get_header_from_folder(self, folder):
1263- return folder.wait_select_single("QQuickItem",
1264- objectName="bookmarkFolderHeader")
1265-
1266
1267 class ContextMenuBase(uitk.UbuntuUIToolkitCustomProxyObjectBase):
1268
1269
1270=== modified file 'tests/autopilot/webbrowser_app/tests/__init__.py'
1271--- tests/autopilot/webbrowser_app/tests/__init__.py 2015-10-15 19:09:59 +0000
1272+++ tests/autopilot/webbrowser_app/tests/__init__.py 2015-12-02 14:15:47 +0000
1273@@ -146,7 +146,7 @@
1274 time.sleep(1)
1275 return tabs_view
1276
1277- def open_new_tab(self, open_tabs_view=False, expand_view=False):
1278+ def open_new_tab(self, open_tabs_view=False):
1279 if (self.main_window.incognito):
1280 count = len(self.main_window.get_incognito_webviews())
1281 else:
1282@@ -179,11 +179,6 @@
1283 self.main_window.address_bar.activeFocus,
1284 Eventually(Equals(True)))
1285
1286- if not self.main_window.wide and expand_view:
1287- more_button = new_tab_view.get_bookmarks_more_button()
1288- self.assertThat(more_button.visible, Equals(True))
1289- self.pointing_device.click_object(more_button)
1290-
1291 return new_tab_view
1292
1293 def open_settings(self):
1294
1295=== modified file 'tests/autopilot/webbrowser_app/tests/test_bookmark_options.py'
1296--- tests/autopilot/webbrowser_app/tests/test_bookmark_options.py 2015-10-13 13:40:29 +0000
1297+++ tests/autopilot/webbrowser_app/tests/test_bookmark_options.py 2015-12-02 14:15:47 +0000
1298@@ -91,19 +91,19 @@
1299 self.pointing_device.click_object(bookmark_toggle)
1300 return self.main_window.get_bookmark_options()
1301
1302- def _assert_bookmark_count_in_folder(self, tab, folder_name, count):
1303- urls = tab.get_bookmarks(folder_name)
1304- self.assertThat(lambda: len(urls), Eventually(Equals(count)))
1305+ def _assert_bookmark_count_in_folder(self, view, folder_name, count):
1306+ self.assertThat(lambda: len(view.get_bookmarks(folder_name)),
1307+ Eventually(Equals(count)))
1308
1309- def _toggle_bookmark_folder(self, tab, folder_name):
1310- folders = tab.get_bookmarks_folder_list_view()
1311- folder_delegate = folders.get_folder_delegate(folder_name)
1312- self.pointing_device.click_object(
1313- folders.get_header_from_folder(folder_delegate))
1314+ def _toggle_bookmark_folder(self, view, folder_name):
1315+ delegate = view.get_bookmark_folder(folder_name)
1316+ self.pointing_device.click_object(delegate)
1317
1318 def test_save_bookmarked_url_in_default_folder(self):
1319- new_tab = self.open_new_tab(open_tabs_view=True, expand_view=True)
1320- self._assert_bookmark_count_in_folder(new_tab, "", 5)
1321+ view = self.open_bookmarks()
1322+ self._assert_bookmark_count_in_folder(view, "", 5)
1323+ view.close()
1324+ view.wait_until_destroyed()
1325
1326 url = self.base_url + "/test2"
1327 self.main_window.go_to_url(url)
1328@@ -118,16 +118,18 @@
1329
1330 self.assertThat(chrome.bookmarked, Eventually(Equals(True)))
1331
1332- new_tab = self.open_new_tab(open_tabs_view=True, expand_view=True)
1333- self._assert_bookmark_count_in_folder(new_tab, "", 6)
1334+ view = self.open_bookmarks()
1335+ self._assert_bookmark_count_in_folder(view, "", 6)
1336
1337 def test_save_bookmarked_url_in_existing_folder(self):
1338- new_tab = self.open_new_tab(open_tabs_view=True, expand_view=True)
1339- self.assertThat(lambda: len(new_tab.get_folder_names()),
1340+ view = self.open_bookmarks()
1341+ self.assertThat(lambda: len(view.get_bookmark_folders()),
1342 Eventually(Equals(3)))
1343 if not self.main_window.wide:
1344- self._toggle_bookmark_folder(new_tab, "Actinide")
1345- self._assert_bookmark_count_in_folder(new_tab, "Actinide", 1)
1346+ self._toggle_bookmark_folder(view, "Actinide")
1347+ self._assert_bookmark_count_in_folder(view, "Actinide", 1)
1348+ view.close()
1349+ view.wait_until_destroyed()
1350
1351 url = self.base_url + "/test2"
1352 self.main_window.go_to_url(url)
1353@@ -154,17 +156,19 @@
1354
1355 self.assertThat(chrome.bookmarked, Eventually(Equals(True)))
1356
1357- new_tab = self.open_new_tab(open_tabs_view=True, expand_view=True)
1358- self.assertThat(lambda: len(new_tab.get_folder_names()),
1359+ view = self.open_bookmarks()
1360+ self.assertThat(lambda: len(view.get_bookmark_folders()),
1361 Eventually(Equals(3)))
1362 if not self.main_window.wide:
1363- self._toggle_bookmark_folder(new_tab, "Actinide")
1364- self._assert_bookmark_count_in_folder(new_tab, "Actinide", 2)
1365+ self._toggle_bookmark_folder(view, "Actinide")
1366+ self._assert_bookmark_count_in_folder(view, "Actinide", 2)
1367
1368 def test_save_bookmarked_url_in_new_folder(self):
1369- new_tab = self.open_new_tab(open_tabs_view=True, expand_view=True)
1370- self.assertThat(lambda: len(new_tab.get_folder_names()),
1371+ view = self.open_bookmarks()
1372+ self.assertThat(lambda: len(view.get_bookmark_folders()),
1373 Eventually(Equals(3)))
1374+ view.close()
1375+ view.wait_until_destroyed()
1376
1377 url = self.base_url + "/test2"
1378 self.main_window.go_to_url(url)
1379@@ -200,12 +204,12 @@
1380
1381 self.assertThat(chrome.bookmarked, Eventually(Equals(True)))
1382
1383- new_tab = self.open_new_tab(open_tabs_view=True, expand_view=True)
1384- self.assertThat(lambda: len(new_tab.get_folder_names()),
1385+ view = self.open_bookmarks()
1386+ self.assertThat(lambda: len(view.get_bookmark_folders()),
1387 Eventually(Equals(4)))
1388 if not self.main_window.wide:
1389- self._toggle_bookmark_folder(new_tab, "NewFolder")
1390- self._assert_bookmark_count_in_folder(new_tab, "NewFolder", 1)
1391+ self._toggle_bookmark_folder(view, "NewFolder")
1392+ self._assert_bookmark_count_in_folder(view, "NewFolder", 1)
1393
1394 def test_set_bookmark_title(self):
1395 url = self.base_url + "/blanktargetlink"
1396@@ -229,10 +233,10 @@
1397
1398 self.assertThat(chrome.bookmarked, Eventually(Equals(True)))
1399
1400- new_tab = self.open_new_tab(open_tabs_view=True, expand_view=True)
1401- self._assert_bookmark_count_in_folder(new_tab, "", 6)
1402+ view = self.open_bookmarks()
1403+ self._assert_bookmark_count_in_folder(view, "", 6)
1404
1405- bookmark = new_tab.get_bookmarks("")[1]
1406+ bookmark = view.get_bookmarks("")[1]
1407 self.assertThat(bookmark.title, Equals("NewTitle"))
1408
1409 def test_bookmark_options_from_contextual_menu(self):
1410
1411=== modified file 'tests/autopilot/webbrowser_app/tests/test_new_tab_view.py'
1412--- tests/autopilot/webbrowser_app/tests/test_new_tab_view.py 2015-10-15 19:09:59 +0000
1413+++ tests/autopilot/webbrowser_app/tests/test_new_tab_view.py 2015-12-02 14:15:47 +0000
1414@@ -251,12 +251,9 @@
1415 more_button = self.new_tab_view.get_bookmarks_more_button()
1416 self.assertThat(more_button.visible, Equals(True))
1417 self.pointing_device.click_object(more_button)
1418- folders = self.new_tab_view.get_bookmarks_folder_list_view()
1419- folder_delegate = folders.get_folder_delegate("")
1420- self.assertThat(lambda: len(folders.get_urls_from_folder(
1421- folder_delegate)),
1422+ self.assertThat(lambda: len(self.new_tab_view.get_bookmarks("")),
1423 Eventually(Equals(5)))
1424- bookmark = folders.get_urls_from_folder(folder_delegate)[0]
1425+ bookmark = self.new_tab_view.get_bookmarks("")[0]
1426 url = bookmark.url
1427 self.pointing_device.click_object(bookmark)
1428 self.new_tab_view.wait_until_destroyed()
1429@@ -273,10 +270,7 @@
1430 more_button = self.new_tab_view.get_bookmarks_more_button()
1431 self.assertThat(more_button.visible, Equals(True))
1432 self.pointing_device.click_object(more_button)
1433- folders = self.new_tab_view.get_bookmarks_folder_list_view()
1434- folder_delegate = folders.get_folder_delegate("")
1435- self.assertThat(lambda: len(folders.get_urls_from_folder(
1436- folder_delegate)),
1437+ self.assertThat(lambda: len(self.new_tab_view.get_bookmarks("")),
1438 Eventually(Equals(5)))
1439 self.assertThat(top_sites.visible, Eventually(Equals(False)))
1440 # Collapse again
1441@@ -296,23 +290,20 @@
1442 Eventually(NotEquals(url)))
1443
1444 def _remove_first_bookmark_from_folder(self, folder):
1445- folders = self.new_tab_view.get_bookmarks_folder_list_view()
1446- folder_delegate = folders.get_folder_delegate(folder)
1447- delegate = folders.get_urls_from_folder(folder_delegate)[0]
1448+ delegates = self.new_tab_view.get_bookmarks(folder)
1449+ delegate = delegates[0]
1450 url = delegate.url
1451- count = len(folders.get_urls_from_folder(folder_delegate))
1452+ count = len(delegates)
1453 delegate.trigger_leading_action("leadingAction.delete",
1454 delegate.wait_until_destroyed)
1455 if ((count - 1) > 4):
1456 self.assertThat(
1457- lambda: folders.get_urls_from_folder(folder_delegate)[0],
1458+ lambda: self.new_tab_view.get_bookmarks(folder)[0].url,
1459 Eventually(NotEquals(url)))
1460
1461 def _toggle_bookmark_folder(self, folder):
1462- folders = self.new_tab_view.get_bookmarks_folder_list_view()
1463- folder_delegate = folders.get_folder_delegate(folder)
1464- self.pointing_device.click_object(
1465- folders.get_header_from_folder(folder_delegate))
1466+ delegate = self.new_tab_view.get_bookmark_folder(folder)
1467+ self.pointing_device.click_object(delegate)
1468
1469 def test_remove_bookmarks_when_collapsed(self):
1470 bookmarks = self.new_tab_view.get_bookmarks_list()
1471@@ -329,10 +320,7 @@
1472 more_button = self.new_tab_view.get_bookmarks_more_button()
1473 self.assertThat(more_button.visible, Equals(True))
1474 self.pointing_device.click_object(more_button)
1475- folders = self.new_tab_view.get_bookmarks_folder_list_view()
1476- folder_delegate = folders.get_folder_delegate("")
1477- self.assertThat(lambda: len(folders.get_urls_from_folder(
1478- folder_delegate)),
1479+ self.assertThat(lambda: len(self.new_tab_view.get_bookmarks("")),
1480 Eventually(Equals(5)))
1481 more_button = self.new_tab_view.get_bookmarks_more_button()
1482 top_sites = self.new_tab_view.get_top_sites_list()
1483@@ -347,89 +335,67 @@
1484 more_button = self.new_tab_view.get_bookmarks_more_button()
1485 self.assertThat(more_button.visible, Equals(True))
1486 self.pointing_device.click_object(more_button)
1487- folders = self.new_tab_view.get_bookmarks_folder_list_view()
1488- self.assertThat(lambda: len(folders.get_delegates()),
1489+ self.assertThat(lambda: len(self.new_tab_view.get_bookmark_folders()),
1490 Eventually(Equals(3)))
1491- folder_delegate = folders.get_folder_delegate("")
1492- self.assertThat(lambda: len(folders.get_urls_from_folder(
1493- folder_delegate)),
1494+ self.assertThat(lambda: len(self.new_tab_view.get_bookmarks("")),
1495 Eventually(Equals(5)))
1496 self._toggle_bookmark_folder("Actinide")
1497- folder_delegate = folders.get_folder_delegate("Actinide")
1498- self.assertThat(lambda: len(folders.get_urls_from_folder(
1499- folder_delegate)),
1500- Eventually(Equals(1)))
1501+ self.assertThat(
1502+ lambda: len(self.new_tab_view.get_bookmarks("Actinide")),
1503+ Eventually(Equals(1)))
1504 self._toggle_bookmark_folder("NobleGas")
1505- folder_delegate = folders.get_folder_delegate("NobleGas")
1506- self.assertThat(lambda: len(folders.get_urls_from_folder(
1507- folder_delegate)),
1508- Eventually(Equals(1)))
1509+ self.assertThat(
1510+ lambda: len(self.new_tab_view.get_bookmarks("NobleGas")),
1511+ Eventually(Equals(1)))
1512
1513 def test_collapsed_bookmarks_folders_when_expanded(self):
1514 more_button = self.new_tab_view.get_bookmarks_more_button()
1515 self.assertThat(more_button.visible, Equals(True))
1516 self.pointing_device.click_object(more_button)
1517- folders = self.new_tab_view.get_bookmarks_folder_list_view()
1518- self.assertThat(lambda: len(folders.get_delegates()),
1519+ self.assertThat(lambda: len(self.new_tab_view.get_bookmark_folders()),
1520 Eventually(Equals(3)))
1521- folder_delegate = folders.get_folder_delegate("")
1522- self.assertThat(lambda: len(folders.get_urls_from_folder(
1523- folder_delegate)),
1524+ self.assertThat(lambda: len(self.new_tab_view.get_bookmarks("")),
1525 Eventually(Equals(5)))
1526- folder_delegate = folders.get_folder_delegate("Actinide")
1527- self.assertThat(lambda: len(folders.get_urls_from_folder(
1528- folder_delegate)),
1529- Eventually(Equals(0)))
1530- folder_delegate = folders.get_folder_delegate("NobleGas")
1531- self.assertThat(lambda: len(folders.get_urls_from_folder(
1532- folder_delegate)),
1533- Eventually(Equals(0)))
1534+ self.assertThat(
1535+ lambda: len(self.new_tab_view.get_bookmarks("Actinide")),
1536+ Eventually(Equals(0)))
1537+ self.assertThat(
1538+ lambda: len(self.new_tab_view.get_bookmarks("NobleGas")),
1539+ Eventually(Equals(0)))
1540
1541 def test_hide_empty_bookmarks_folders_when_expanded(self):
1542 more_button = self.new_tab_view.get_bookmarks_more_button()
1543 self.assertThat(more_button.visible, Equals(True))
1544 self.pointing_device.click_object(more_button)
1545- folders = self.new_tab_view.get_bookmarks_folder_list_view()
1546- self.assertThat(lambda: len(folders.get_delegates()),
1547+ self.assertThat(lambda: len(self.new_tab_view.get_bookmark_folders()),
1548 Eventually(Equals(3)))
1549 self._toggle_bookmark_folder("Actinide")
1550- folder_delegate = folders.get_folder_delegate("Actinide")
1551- self.assertThat(lambda: len(folders.get_urls_from_folder(
1552- folder_delegate)),
1553- Eventually(Equals(1)))
1554+ self.assertThat(
1555+ lambda: len(self.new_tab_view.get_bookmarks("Actinide")),
1556+ Eventually(Equals(1)))
1557 self._remove_first_bookmark_from_folder("Actinide")
1558- self.assertThat(lambda: len(folders.get_delegates()),
1559+ self.assertThat(lambda: len(self.new_tab_view.get_bookmark_folders()),
1560 Eventually(Equals(2)))
1561- folder_delegate = folders.get_folder_delegate("")
1562- self.assertThat(lambda: len(folders.get_urls_from_folder(
1563- folder_delegate)),
1564+ self.assertThat(lambda: len(self.new_tab_view.get_bookmarks("")),
1565 Eventually(Equals(5)))
1566 self._toggle_bookmark_folder("NobleGas")
1567- folder_delegate = folders.get_folder_delegate("NobleGas")
1568- self.assertThat(lambda: len(folders.get_urls_from_folder(
1569- folder_delegate)),
1570- Eventually(Equals(1)))
1571+ self.assertThat(
1572+ lambda: len(self.new_tab_view.get_bookmarks("NobleGas")),
1573+ Eventually(Equals(1)))
1574
1575 def test_bookmarks_folder_expands_and_collapses(self):
1576 more_button = self.new_tab_view.get_bookmarks_more_button()
1577 self.assertThat(more_button.visible, Equals(True))
1578 self.pointing_device.click_object(more_button)
1579- folders = self.new_tab_view.get_bookmarks_folder_list_view()
1580- self.assertThat(lambda: len(folders.get_delegates()),
1581+ self.assertThat(lambda: len(self.new_tab_view.get_bookmark_folders()),
1582 Eventually(Equals(3)))
1583- folder_delegate = folders.get_folder_delegate("")
1584- self.assertThat(lambda: len(folders.get_urls_from_folder(
1585- folder_delegate)),
1586+ self.assertThat(lambda: len(self.new_tab_view.get_bookmarks("")),
1587 Eventually(Equals(5)))
1588- self.pointing_device.click_object(
1589- folders.get_header_from_folder(folder_delegate))
1590- self.assertThat(lambda: len(folders.get_urls_from_folder(
1591- folder_delegate)),
1592+ self._toggle_bookmark_folder("")
1593+ self.assertThat(lambda: len(self.new_tab_view.get_bookmarks("")),
1594 Eventually(Equals(0)))
1595- self.pointing_device.click_object(
1596- folders.get_header_from_folder(folder_delegate))
1597- self.assertThat(lambda: len(folders.get_urls_from_folder(
1598- folder_delegate)),
1599+ self._toggle_bookmark_folder("")
1600+ self.assertThat(lambda: len(self.new_tab_view.get_bookmarks("")),
1601 Eventually(Equals(5)))
1602
1603 def test_remove_top_sites(self):
1604@@ -475,10 +441,10 @@
1605
1606 def test_remove_top_sites(self):
1607 view = self.new_tab_view
1608- topsites = view.get_top_site_items()
1609+ topsites = view.get_top_sites_list().get_delegates()
1610 previous_count = len(topsites)
1611 topsites[0].hide_from_history(self.main_window)
1612- self.assertThat(len(view.get_top_site_items()),
1613+ self.assertThat(len(view.get_top_sites_list().get_delegates()),
1614 Equals(previous_count - 1))
1615
1616 def test_drag_bookmarks(self):
1617
1618=== modified file 'tests/autopilot/webbrowser_app/tests/test_private.py'
1619--- tests/autopilot/webbrowser_app/tests/test_private.py 2015-11-23 17:12:15 +0000
1620+++ tests/autopilot/webbrowser_app/tests/test_private.py 2015-12-02 14:15:47 +0000
1621@@ -68,7 +68,8 @@
1622
1623 def test_url_showing_in_top_sites_in_and_out_private_mode(self):
1624 new_tab = self.open_new_tab(open_tabs_view=True)
1625- urls = [site.url for site in new_tab.get_top_site_items()]
1626+ top_sites = new_tab.get_top_sites_list().get_delegates()
1627+ urls = [site.url for site in top_sites]
1628 self.assertIn(self.url, urls)
1629
1630 self.main_window.enter_private_mode()
1631@@ -82,7 +83,8 @@
1632 Eventually(Equals(False)))
1633
1634 new_tab = self.open_new_tab(open_tabs_view=True)
1635- urls = [site.url for site in new_tab.get_top_site_items()]
1636+ top_sites = new_tab.get_top_sites_list().get_delegates()
1637+ urls = [site.url for site in top_sites]
1638 self.assertNotIn(url, urls)
1639
1640 def test_public_tabs_should_not_be_visible_in_private_mode(self):
1641
1642=== modified file 'tests/autopilot/webbrowser_app/tests/test_site_previews.py'
1643--- tests/autopilot/webbrowser_app/tests/test_site_previews.py 2015-11-19 11:48:10 +0000
1644+++ tests/autopilot/webbrowser_app/tests/test_site_previews.py 2015-12-02 14:15:47 +0000
1645@@ -123,7 +123,7 @@
1646 return cap
1647
1648 def remove_top_site(self, new_tab_view, url):
1649- top_sites = new_tab_view.get_top_site_items()
1650+ top_sites = new_tab_view.get_top_sites_list().get_delegates()
1651 top_sites = [d for d in top_sites if d.url == url]
1652 self.assertThat(len(top_sites), Equals(1))
1653 delegate = top_sites[0]
1654
1655=== modified file 'tests/unittests/CMakeLists.txt'
1656--- tests/unittests/CMakeLists.txt 2015-10-06 09:48:38 +0000
1657+++ tests/unittests/CMakeLists.txt 2015-12-02 14:15:47 +0000
1658@@ -14,6 +14,7 @@
1659 add_subdirectory(bookmarks-model)
1660 add_subdirectory(bookmarks-folder-model)
1661 add_subdirectory(bookmarks-folderlist-model)
1662+add_subdirectory(collapsible-bookmarks-model)
1663 add_subdirectory(limit-proxy-model)
1664 add_subdirectory(container-url-patterns)
1665 add_subdirectory(cookie-store)
1666
1667=== added directory 'tests/unittests/collapsible-bookmarks-model'
1668=== added file 'tests/unittests/collapsible-bookmarks-model/CMakeLists.txt'
1669--- tests/unittests/collapsible-bookmarks-model/CMakeLists.txt 1970-01-01 00:00:00 +0000
1670+++ tests/unittests/collapsible-bookmarks-model/CMakeLists.txt 2015-12-02 14:15:47 +0000
1671@@ -0,0 +1,13 @@
1672+find_package(Qt5Core REQUIRED)
1673+find_package(Qt5Sql REQUIRED)
1674+find_package(Qt5Test REQUIRED)
1675+set(TEST tst_CollapsibleBookmarksModelTests)
1676+add_executable(${TEST} tst_CollapsibleBookmarksModelTests.cpp)
1677+include_directories(${webbrowser-app_SOURCE_DIR})
1678+target_link_libraries(${TEST}
1679+ Qt5::Core
1680+ Qt5::Sql
1681+ Qt5::Test
1682+ webbrowser-app-models
1683+)
1684+add_test(${TEST} ${CMAKE_CURRENT_BINARY_DIR}/${TEST} -xunitxml -o ${TEST}.xml)
1685
1686=== added file 'tests/unittests/collapsible-bookmarks-model/tst_CollapsibleBookmarksModelTests.cpp'
1687--- tests/unittests/collapsible-bookmarks-model/tst_CollapsibleBookmarksModelTests.cpp 1970-01-01 00:00:00 +0000
1688+++ tests/unittests/collapsible-bookmarks-model/tst_CollapsibleBookmarksModelTests.cpp 2015-12-02 14:15:47 +0000
1689@@ -0,0 +1,645 @@
1690+/*
1691+ * Copyright 2015 Canonical Ltd.
1692+ *
1693+ * This file is part of webbrowser-app.
1694+ *
1695+ * webbrowser-app is free software; you can redistribute it and/or modify
1696+ * it under the terms of the GNU General Public License as published by
1697+ * the Free Software Foundation; version 3.
1698+ *
1699+ * webbrowser-app is distributed in the hope that it will be useful,
1700+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
1701+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1702+ * GNU General Public License for more details.
1703+ *
1704+ * You should have received a copy of the GNU General Public License
1705+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
1706+ */
1707+
1708+// Qt
1709+#include <QtCore/QMetaType>
1710+#include <QtCore/QVariant>
1711+#include <QtTest/QSignalSpy>
1712+#include <QtTest/QtTest>
1713+
1714+// local
1715+#include "collapsible-bookmarks-model.h"
1716+
1717+class CollapsibleBookmarksModelTests : public QObject
1718+{
1719+ Q_OBJECT
1720+
1721+private:
1722+ BookmarksModel* bookmarks;
1723+ CollapsibleBookmarksModel* model;
1724+
1725+private Q_SLOTS:
1726+ void init()
1727+ {
1728+ bookmarks = new BookmarksModel;
1729+ bookmarks->setDatabasePath(":memory:");
1730+ model = new CollapsibleBookmarksModel;
1731+ model->setSourceModel(bookmarks);
1732+ }
1733+
1734+ void populateBookmarksModel()
1735+ {
1736+ bookmarks->add(QUrl(QStringLiteral("http://test/url1")), QStringLiteral("url1"),
1737+ QUrl(QStringLiteral("http://test/icon1")), QStringLiteral(""));
1738+ QTest::qWait(1);
1739+ bookmarks->add(QUrl(QStringLiteral("http://test/url2")), QStringLiteral("url2"),
1740+ QUrl(QStringLiteral("http://test/icon2")), QStringLiteral("folder1"));
1741+ QTest::qWait(1);
1742+ bookmarks->add(QUrl(QStringLiteral("http://test/url3")), QStringLiteral("url3"),
1743+ QUrl(QStringLiteral("http://test/icon3")), QStringLiteral("folder2"));
1744+ QTest::qWait(1);
1745+ bookmarks->addFolder(QStringLiteral("folder3"));
1746+ bookmarks->add(QUrl(QStringLiteral("http://test/url4")), QStringLiteral("url4"),
1747+ QUrl(QStringLiteral("http://test/icon4")), QStringLiteral("folder4"));
1748+ }
1749+
1750+ void cleanup()
1751+ {
1752+ delete model;
1753+ delete bookmarks;
1754+ }
1755+
1756+ void test_default_values()
1757+ {
1758+ QCOMPARE(model->prependHomepage(), true);
1759+ QCOMPARE(model->showEmptyFolders(), false);
1760+ }
1761+
1762+ void test_initial_contents_data()
1763+ {
1764+ QTest::addColumn<bool>("prependHomepage");
1765+ QTest::addColumn<bool>("showEmptyFolders");
1766+ QTest::addColumn<int>("count");
1767+ QTest::newRow("no/no") << false << false << 0;
1768+ QTest::newRow("no/yes") << false << true << 1;
1769+ QTest::newRow("yes/no") << true << false << 2;
1770+ QTest::newRow("yes/yes") << true << true << 2;
1771+ }
1772+
1773+ void test_initial_contents()
1774+ {
1775+ QFETCH(bool, prependHomepage);
1776+ QFETCH(bool, showEmptyFolders);
1777+ QFETCH(int, count);
1778+ model->setPrependHomepage(prependHomepage);
1779+ model->setShowEmptyFolders(showEmptyFolders);
1780+ QCOMPARE(model->rowCount(), count);
1781+ }
1782+
1783+ void test_roleNames()
1784+ {
1785+ QList<QByteArray> roleNames = model->roleNames().values();
1786+ QCOMPARE(roleNames.size(), 6);
1787+ QVERIFY(roleNames.contains("url"));
1788+ QVERIFY(roleNames.contains("title"));
1789+ QVERIFY(roleNames.contains("icon"));
1790+ QVERIFY(roleNames.contains("created"));
1791+ QVERIFY(roleNames.contains("folder"));
1792+ QVERIFY(roleNames.contains("type"));
1793+ }
1794+
1795+ void test_setSourceModel()
1796+ {
1797+ QSignalSpy spy(model, SIGNAL(sourceModelChanged()));
1798+ QCOMPARE(model->sourceModel(), bookmarks);
1799+
1800+ model->setSourceModel(bookmarks);
1801+ QVERIFY(spy.isEmpty());
1802+ QCOMPARE(model->sourceModel(), bookmarks);
1803+
1804+ model->setSourceModel(nullptr);
1805+ QCOMPARE(spy.count(), 1);
1806+ QCOMPARE(model->sourceModel(), (BookmarksModel*) nullptr);
1807+
1808+ model->setSourceModel(bookmarks);
1809+ QCOMPARE(spy.count(), 2);
1810+ QCOMPARE(model->sourceModel(), bookmarks);
1811+ }
1812+
1813+ void test_allBookmarks_initially_expanded()
1814+ {
1815+ populateBookmarksModel();
1816+ model->setPrependHomepage(true);
1817+ model->setShowEmptyFolders(true);
1818+ QStringList expectedTypes;
1819+ expectedTypes << QStringLiteral("expandedFolder") << QStringLiteral("homepage")
1820+ << QStringLiteral("bookmark") << QStringLiteral("collapsedFolder")
1821+ << QStringLiteral("collapsedFolder") << QStringLiteral("emptyFolder")
1822+ << QStringLiteral("collapsedFolder");
1823+ int count = expectedTypes.count();
1824+ QCOMPARE(model->rowCount(), count);
1825+ for (int i = 0; i < count; ++i) {
1826+ QCOMPARE(model->data(model->index(i), CollapsibleBookmarksModel::Type).toString(), expectedTypes.at(i));
1827+ }
1828+ }
1829+
1830+ void test_modelData_data() {
1831+ QTest::addColumn<bool>("showEmptyFolders");
1832+ QTest::addColumn<int>("index");
1833+ QTest::addColumn<int>("role");
1834+ QTest::addColumn<QVariant>("value");
1835+
1836+ int url = CollapsibleBookmarksModel::Url;
1837+ int title = CollapsibleBookmarksModel::Title;
1838+ int icon = CollapsibleBookmarksModel::Icon;
1839+ int created = CollapsibleBookmarksModel::Created;
1840+ int folder = CollapsibleBookmarksModel::Folder;
1841+ int type = CollapsibleBookmarksModel::Type;
1842+
1843+ QTest::newRow("invalid index") << true << -1 << type << QVariant();
1844+ QTest::newRow("invalid index 2") << true << 10 << type << QVariant();
1845+ QTest::newRow("invalid role") << true << 0 << 25 << QVariant();
1846+
1847+ QTest::newRow("expanded folder url") << true << 0 << url << QVariant();
1848+ QTest::newRow("expanded folder title") << true << 0 << title << QVariant();
1849+ QTest::newRow("expanded folder icon") << true << 0 << icon << QVariant();
1850+ QTest::newRow("expanded folder created") << true << 0 << created << QVariant();
1851+ QTest::newRow("expanded folder name") << true << 0 << folder << QVariant(QStringLiteral(""));
1852+ QTest::newRow("expanded folder type") << true << 0 << type << QVariant(QStringLiteral("expandedFolder"));
1853+
1854+ QTest::newRow("homepage url") << true << 1 << url << QVariant();
1855+ QTest::newRow("homepage title") << true << 1 << title << QVariant();
1856+ QTest::newRow("homepage icon") << true << 1 << icon << QVariant();
1857+ QTest::newRow("homepage created") << true << 1 << created << QVariant();
1858+ QTest::newRow("homepage folder") << true << 1 << folder << QVariant(QStringLiteral(""));
1859+ QTest::newRow("homepage type") << true << 1 << type << QVariant(QStringLiteral("homepage"));
1860+
1861+ QTest::newRow("bookmark url") << true << 2 << url << QVariant(QUrl("http://test/url1"));
1862+ QTest::newRow("bookmark title") << true << 2 << title << QVariant(QStringLiteral("url1"));
1863+ QTest::newRow("bookmark icon") << true << 2 << icon << QVariant(QUrl("http://test/icon1"));
1864+ QTest::newRow("bookmark folder") << true << 2 << folder << QVariant(QStringLiteral(""));
1865+ QTest::newRow("bookmark type") << true << 2 << type << QVariant(QStringLiteral("bookmark"));
1866+
1867+ QTest::newRow("collapsed folder url") << true << 3 << url << QVariant();
1868+ QTest::newRow("collapsed folder title") << true << 3 << title << QVariant();
1869+ QTest::newRow("collapsed folder icon") << true << 3 << icon << QVariant();
1870+ QTest::newRow("collapsed folder created") << true << 3 << created << QVariant();
1871+ QTest::newRow("collapsed folder name") << true << 3 << folder << QVariant(QStringLiteral("folder1"));
1872+ QTest::newRow("collapsed folder type") << true << 3 << type << QVariant(QStringLiteral("collapsedFolder"));
1873+
1874+ QTest::newRow("empty folder url") << true << 5 << url << QVariant();
1875+ QTest::newRow("empty folder title") << true << 5 << title << QVariant();
1876+ QTest::newRow("empty folder icon") << true << 5 << icon << QVariant();
1877+ QTest::newRow("empty folder created") << true << 5 << created << QVariant();
1878+ QTest::newRow("empty folder name") << true << 5 << folder << QVariant(QStringLiteral("folder3"));
1879+ QTest::newRow("empty folder type") << true << 5 << type << QVariant(QStringLiteral("emptyFolder"));
1880+
1881+ QTest::newRow("another collapsed folder name") << false << 5 << folder << QVariant(QStringLiteral("folder4"));
1882+ QTest::newRow("another collapsed folder type") << false << 5 << type << QVariant(QStringLiteral("collapsedFolder"));
1883+ }
1884+
1885+ void test_modelData() {
1886+ QFETCH(bool, showEmptyFolders);
1887+ QFETCH(int, index);
1888+ QFETCH(int, role);
1889+ QFETCH(QVariant, value);
1890+
1891+ populateBookmarksModel();
1892+ model->setPrependHomepage(true);
1893+ model->setShowEmptyFolders(showEmptyFolders);
1894+
1895+ QVariant actualValue = model->data(model->index(index), role);
1896+ if (!value.isValid()) {
1897+ QVERIFY(!actualValue.isValid());
1898+ } else {
1899+ QCOMPARE(actualValue, value);
1900+ }
1901+ }
1902+
1903+ void test_initialModelPopulation() {
1904+ populateBookmarksModel();
1905+ model->setPrependHomepage(true);
1906+ model->setShowEmptyFolders(false);
1907+ model->setSourceModel(nullptr);
1908+
1909+ QSignalSpy spy(model, SIGNAL(modelReset()));
1910+ model->setSourceModel(bookmarks);
1911+ QCOMPARE(spy.count(), 1);
1912+ QCOMPARE(model->rowCount(), 6);
1913+ }
1914+
1915+ void test_expandAndCollapseFolders() {
1916+ populateBookmarksModel();
1917+ model->setPrependHomepage(true);
1918+ model->setShowEmptyFolders(true);
1919+
1920+ qRegisterMetaType<QVector<int>>();
1921+ QSignalSpy removalSpy(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
1922+ QSignalSpy insertionSpy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
1923+ QSignalSpy dataSpy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
1924+ QCOMPARE(model->rowCount(), 7);
1925+
1926+ // collapse the "All Bookmarks" folder
1927+ QCOMPARE(model->data(model->index(0), CollapsibleBookmarksModel::Type).toString(), QStringLiteral("expandedFolder"));
1928+ model->toggleFolderState(QStringLiteral(""));
1929+ QCOMPARE(model->data(model->index(0), CollapsibleBookmarksModel::Type).toString(), QStringLiteral("collapsedFolder"));
1930+ QCOMPARE(model->rowCount(), 5);
1931+ QVERIFY(insertionSpy.isEmpty());
1932+ QCOMPARE(removalSpy.count(), 1);
1933+ QCOMPARE(dataSpy.count(), 1);
1934+ QVariantList args = removalSpy.takeFirst();
1935+ QCOMPARE(args.at(1).toInt(), 1);
1936+ QCOMPARE(args.at(2).toInt(), 2);
1937+ args = dataSpy.takeFirst();
1938+ QCOMPARE(args.at(0).toModelIndex().row(), 0);
1939+ QCOMPARE(args.at(1).toModelIndex().row(), 0);
1940+ QVector<int> dataChanged = args.at(2).value<QVector<int>>();
1941+ QCOMPARE(dataChanged.size(), 1);
1942+ QVERIFY(dataChanged.contains((int) CollapsibleBookmarksModel::Type));
1943+
1944+ // expand the "All Bookmarks" folder
1945+ model->toggleFolderState(QStringLiteral(""));
1946+ QCOMPARE(model->data(model->index(0), CollapsibleBookmarksModel::Type).toString(), QStringLiteral("expandedFolder"));
1947+ QCOMPARE(model->rowCount(), 7);
1948+ QVERIFY(removalSpy.isEmpty());
1949+ QCOMPARE(insertionSpy.count(), 1);
1950+ QCOMPARE(dataSpy.count(), 1);
1951+ args = insertionSpy.takeFirst();
1952+ QCOMPARE(args.at(1).toInt(), 1);
1953+ QCOMPARE(args.at(2).toInt(), 2);
1954+ args = dataSpy.takeFirst();
1955+ QCOMPARE(args.at(0).toModelIndex().row(), 0);
1956+ QCOMPARE(args.at(1).toModelIndex().row(), 0);
1957+ dataChanged = args.at(2).value<QVector<int>>();
1958+ QCOMPARE(dataChanged.size(), 1);
1959+ QVERIFY(dataChanged.contains((int) CollapsibleBookmarksModel::Type));
1960+
1961+ // expand another folder
1962+ QCOMPARE(model->data(model->index(3), CollapsibleBookmarksModel::Type).toString(), QStringLiteral("collapsedFolder"));
1963+ model->toggleFolderState(QStringLiteral("folder1"));
1964+ QCOMPARE(model->data(model->index(3), CollapsibleBookmarksModel::Type).toString(), QStringLiteral("expandedFolder"));
1965+ QCOMPARE(model->rowCount(), 8);
1966+ QVERIFY(removalSpy.isEmpty());
1967+ QCOMPARE(insertionSpy.count(), 1);
1968+ QCOMPARE(dataSpy.count(), 1);
1969+ args = insertionSpy.takeFirst();
1970+ QCOMPARE(args.at(1).toInt(), 4);
1971+ QCOMPARE(args.at(2).toInt(), 4);
1972+ args = dataSpy.takeFirst();
1973+ QCOMPARE(args.at(0).toModelIndex().row(), 3);
1974+ QCOMPARE(args.at(1).toModelIndex().row(), 3);
1975+ dataChanged = args.at(2).value<QVector<int>>();
1976+ QCOMPARE(dataChanged.size(), 1);
1977+ QVERIFY(dataChanged.contains((int) CollapsibleBookmarksModel::Type));
1978+
1979+ // toggle the state of an empty folder: nothing should happen
1980+ QCOMPARE(model->data(model->index(6), CollapsibleBookmarksModel::Type).toString(), QStringLiteral("emptyFolder"));
1981+ model->toggleFolderState(QStringLiteral("folder3"));
1982+ QCOMPARE(model->data(model->index(6), CollapsibleBookmarksModel::Type).toString(), QStringLiteral("emptyFolder"));
1983+ QCOMPARE(model->rowCount(), 8);
1984+ QVERIFY(removalSpy.isEmpty());
1985+ QVERIFY(insertionSpy.isEmpty());
1986+ QVERIFY(dataSpy.isEmpty());
1987+ }
1988+
1989+ void test_setShowEmptyFolders()
1990+ {
1991+ populateBookmarksModel();
1992+ model->setPrependHomepage(true);
1993+ model->setShowEmptyFolders(true);
1994+
1995+ QSignalSpy removalSpy(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
1996+ QSignalSpy insertionSpy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
1997+ QCOMPARE(model->rowCount(), 7);
1998+ QVERIFY(model->showEmptyFolders());
1999+
2000+ // hide empty folders, "folder3" gets hidden
2001+ model->setShowEmptyFolders(false);
2002+ QVERIFY(!model->showEmptyFolders());
2003+ QCOMPARE(model->rowCount(), 6);
2004+ QVERIFY(insertionSpy.isEmpty());
2005+ QCOMPARE(removalSpy.count(), 1);
2006+ QVariantList args = removalSpy.takeFirst();
2007+ QCOMPARE(args.at(1).toInt(), 5);
2008+ QCOMPARE(args.at(2).toInt(), 5);
2009+
2010+ // add a new empty folder, it should remain hidden
2011+ bookmarks->addFolder(QStringLiteral("folder5"));
2012+ QCOMPARE(model->rowCount(), 6);
2013+ QVERIFY(insertionSpy.isEmpty());
2014+ QVERIFY(removalSpy.isEmpty());
2015+
2016+ // show empty folders, "folder3" and "folder5" should appear
2017+ model->setShowEmptyFolders(true);
2018+ QVERIFY(model->showEmptyFolders());
2019+ QCOMPARE(model->rowCount(), 8);
2020+ QVERIFY(removalSpy.isEmpty());
2021+ QCOMPARE(insertionSpy.count(), 2);
2022+ args = insertionSpy.takeFirst();
2023+ QCOMPARE(args.at(1).toInt(), 5);
2024+ QCOMPARE(args.at(2).toInt(), 5);
2025+ args = insertionSpy.takeFirst();
2026+ QCOMPARE(args.at(1).toInt(), 7);
2027+ QCOMPARE(args.at(2).toInt(), 7);
2028+
2029+ // add a new empty folder, it should appear
2030+ bookmarks->addFolder(QStringLiteral("a new folder"));
2031+ QCOMPARE(model->rowCount(), 9);
2032+ QVERIFY(removalSpy.isEmpty());
2033+ QCOMPARE(insertionSpy.count(), 1);
2034+ args = insertionSpy.takeFirst();
2035+ QCOMPARE(args.at(1).toInt(), 3);
2036+ QCOMPARE(args.at(2).toInt(), 3);
2037+ }
2038+
2039+ void test_setPrependHomepage()
2040+ {
2041+ model->setPrependHomepage(true);
2042+ model->setShowEmptyFolders(true);
2043+
2044+ QSignalSpy removalSpy(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
2045+ QSignalSpy insertionSpy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
2046+ QCOMPARE(model->rowCount(), 2);
2047+ QVERIFY(model->prependHomepage());
2048+
2049+ // do not prepend the homepage bookmark, the "All Bookmarks" folder
2050+ // should remain visible though (because showEmptyFolders is true)
2051+ model->setPrependHomepage(false);
2052+ QVERIFY(!model->prependHomepage());
2053+ QCOMPARE(model->rowCount(), 1);
2054+ QVERIFY(insertionSpy.isEmpty());
2055+ QCOMPARE(removalSpy.count(), 1);
2056+ QVariantList args = removalSpy.takeFirst();
2057+ QCOMPARE(args.at(1).toInt(), 1);
2058+ QCOMPARE(args.at(2).toInt(), 1);
2059+
2060+ // hide empty folders, the model should become empty
2061+ model->setShowEmptyFolders(false);
2062+ QVERIFY(!model->showEmptyFolders());
2063+ QCOMPARE(model->rowCount(), 0);
2064+ QVERIFY(insertionSpy.isEmpty());
2065+ QCOMPARE(removalSpy.count(), 1);
2066+ args = removalSpy.takeFirst();
2067+ QCOMPARE(args.at(1).toInt(), 0);
2068+ QCOMPARE(args.at(2).toInt(), 0);
2069+
2070+ // add bookmark to "All Bookmarks" folder, which should become
2071+ // visible again (but collapsed)
2072+ bookmarks->add(QUrl(QStringLiteral("http://test/url1")), QStringLiteral("url1"),
2073+ QUrl(QStringLiteral("http://test/icon1")), QStringLiteral(""));
2074+ QCOMPARE(model->rowCount(), 1);
2075+ QVERIFY(removalSpy.isEmpty());
2076+ QCOMPARE(insertionSpy.count(), 1);
2077+ args = insertionSpy.takeFirst();
2078+ QCOMPARE(args.at(1).toInt(), 0);
2079+ QCOMPARE(args.at(2).toInt(), 0);
2080+
2081+ // expand the "All Bookmarks" folder
2082+ model->toggleFolderState(QStringLiteral(""));
2083+ QCOMPARE(model->rowCount(), 2);
2084+ QVERIFY(removalSpy.isEmpty());
2085+ QCOMPARE(insertionSpy.count(), 1);
2086+ args = insertionSpy.takeFirst();
2087+ QCOMPARE(args.at(1).toInt(), 1);
2088+ QCOMPARE(args.at(2).toInt(), 1);
2089+
2090+ // prepend the homepage bookmark, it should be inserted again
2091+ model->setPrependHomepage(true);
2092+ QVERIFY(model->prependHomepage());
2093+ QCOMPARE(model->rowCount(), 3);
2094+ QVERIFY(removalSpy.isEmpty());
2095+ QCOMPARE(insertionSpy.count(), 1);
2096+ args = insertionSpy.takeFirst();
2097+ QCOMPARE(args.at(1).toInt(), 1);
2098+ QCOMPARE(args.at(2).toInt(), 1);
2099+ }
2100+
2101+ void test_removingBookmarks()
2102+ {
2103+ populateBookmarksModel();
2104+ model->setPrependHomepage(true);
2105+ model->setShowEmptyFolders(true);
2106+
2107+ qRegisterMetaType<QVector<int>>();
2108+ QSignalSpy removalSpy(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
2109+ QSignalSpy insertionSpy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
2110+ QSignalSpy dataSpy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
2111+ QCOMPARE(model->rowCount(), 7);
2112+
2113+ // remove last bookmark from "folder2", which becomes empty
2114+ bookmarks->remove(QUrl(QStringLiteral("http://test/url3")));
2115+ QCOMPARE(model->rowCount(), 7);
2116+ QVERIFY(removalSpy.isEmpty());
2117+ QVERIFY(insertionSpy.isEmpty());
2118+ QCOMPARE(dataSpy.count(), 1);
2119+ QVariantList args = dataSpy.takeFirst();
2120+ QCOMPARE(args.at(0).toModelIndex().row(), 4);
2121+ QCOMPARE(args.at(1).toModelIndex().row(), 4);
2122+ QVector<int> dataChanged = args.at(2).value<QVector<int>>();
2123+ QCOMPARE(dataChanged.size(), 1);
2124+ QVERIFY(dataChanged.contains((int) CollapsibleBookmarksModel::Type));
2125+
2126+ // expand "folder1"
2127+ model->toggleFolderState(QStringLiteral("folder1"));
2128+ QCOMPARE(model->rowCount(), 8);
2129+ QVERIFY(removalSpy.isEmpty());
2130+ QCOMPARE(insertionSpy.count(), 1);
2131+ QCOMPARE(dataSpy.count(), 1);
2132+ args = insertionSpy.takeFirst();
2133+ QCOMPARE(args.at(1).toInt(), 4);
2134+ QCOMPARE(args.at(2).toInt(), 4);
2135+ args = dataSpy.takeFirst();
2136+ QCOMPARE(args.at(0).toModelIndex().row(), 3);
2137+ QCOMPARE(args.at(1).toModelIndex().row(), 3);
2138+ dataChanged = args.at(2).value<QVector<int>>();
2139+ QCOMPARE(dataChanged.size(), 1);
2140+ QVERIFY(dataChanged.contains((int) CollapsibleBookmarksModel::Type));
2141+
2142+ // remove last bookmark from "folder1"
2143+ bookmarks->remove(QUrl(QStringLiteral("http://test/url2")));
2144+ QCOMPARE(model->rowCount(), 7);
2145+ QVERIFY(insertionSpy.isEmpty());
2146+ QCOMPARE(removalSpy.count(), 1);
2147+ QCOMPARE(dataSpy.count(), 1);
2148+ args = removalSpy.takeFirst();
2149+ QCOMPARE(args.at(1).toInt(), 4);
2150+ QCOMPARE(args.at(2).toInt(), 4);
2151+ args = dataSpy.takeFirst();
2152+ QCOMPARE(args.at(0).toModelIndex().row(), 3);
2153+ QCOMPARE(args.at(1).toModelIndex().row(), 3);
2154+ dataChanged = args.at(2).value<QVector<int>>();
2155+ QCOMPARE(dataChanged.size(), 1);
2156+ QVERIFY(dataChanged.contains((int) CollapsibleBookmarksModel::Type));
2157+
2158+ // add a bookmark to "folder1"
2159+ bookmarks->add(QUrl(QStringLiteral("http://test/url5")), QStringLiteral("url5"),
2160+ QUrl(QStringLiteral("http://test/icon5")), QStringLiteral("folder1"));
2161+ QCOMPARE(model->rowCount(), 7);
2162+
2163+ // and expand it
2164+ model->toggleFolderState(QStringLiteral("folder1"));
2165+ QCOMPARE(model->rowCount(), 8);
2166+
2167+ // hide empty folders
2168+ model->setShowEmptyFolders(false);
2169+ QCOMPARE(model->rowCount(), 6);
2170+
2171+ // remove last bookmark from "folder1", which should be hidden
2172+ insertionSpy.clear();
2173+ removalSpy.clear();
2174+ dataSpy.clear();
2175+ bookmarks->remove(QUrl(QStringLiteral("http://test/url5")));
2176+ QCOMPARE(model->rowCount(), 4);
2177+ QVERIFY(insertionSpy.isEmpty());
2178+ QCOMPARE(removalSpy.count(), 1);
2179+ QVERIFY(dataSpy.isEmpty());
2180+ args = removalSpy.takeFirst();
2181+ QCOMPARE(args.at(1).toInt(), 3);
2182+ QCOMPARE(args.at(2).toInt(), 4);
2183+ }
2184+
2185+ void test_updatingBookmarks()
2186+ {
2187+ populateBookmarksModel();
2188+ model->setPrependHomepage(true);
2189+ model->setShowEmptyFolders(true);
2190+
2191+ qRegisterMetaType<QVector<int>>();
2192+ QSignalSpy removalSpy(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)));
2193+ QSignalSpy insertionSpy(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)));
2194+ QSignalSpy movedSpy(model, SIGNAL(rowsMoved(const QModelIndex&, int, int, const QModelIndex&, int)));
2195+ QSignalSpy dataSpy(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)));
2196+ QCOMPARE(model->rowCount(), 7);
2197+
2198+ // update a bookmark in a collapsed folder, nothing should happen
2199+ bookmarks->update(QUrl(QStringLiteral("http://test/url2")), QStringLiteral("new title 2"), QStringLiteral("folder1"));
2200+ QVERIFY(removalSpy.isEmpty());
2201+ QVERIFY(insertionSpy.isEmpty());
2202+ QVERIFY(movedSpy.isEmpty());
2203+ QVERIFY(dataSpy.isEmpty());
2204+
2205+ // update a bookmark in an expanded folder
2206+ bookmarks->update(QUrl(QStringLiteral("http://test/url1")), QStringLiteral("new title 1"), QStringLiteral(""));
2207+ QVERIFY(removalSpy.isEmpty());
2208+ QVERIFY(insertionSpy.isEmpty());
2209+ QVERIFY(movedSpy.isEmpty());
2210+ QCOMPARE(dataSpy.count(), 1);
2211+ QVariantList args = dataSpy.takeFirst();
2212+ QCOMPARE(args.at(0).toModelIndex().row(), 2);
2213+ QCOMPARE(args.at(1).toModelIndex().row(), 2);
2214+ QVERIFY(args.at(2).value<QVector<int>>().isEmpty());
2215+ QCOMPARE(model->data(model->index(2), CollapsibleBookmarksModel::Title).toString(), QStringLiteral("new title 1"));
2216+
2217+ // move a bookmark from an expanded folder to a collapsed folder
2218+ bookmarks->update(QUrl(QStringLiteral("http://test/url1")), QStringLiteral("new title 1"), QStringLiteral("folder1"));
2219+ QCOMPARE(removalSpy.count(), 1);
2220+ QVERIFY(insertionSpy.isEmpty());
2221+ QVERIFY(movedSpy.isEmpty());
2222+ QVERIFY(dataSpy.isEmpty());
2223+ args = removalSpy.takeFirst();
2224+ QCOMPARE(args.at(1).toInt(), 2);
2225+ QCOMPARE(args.at(2).toInt(), 2);
2226+
2227+ // move a bookmark from a collapsed folder to an expanded folder
2228+ bookmarks->update(QUrl(QStringLiteral("http://test/url1")), QStringLiteral("new title 1"), QStringLiteral(""));
2229+ QVERIFY(removalSpy.isEmpty());
2230+ QCOMPARE(insertionSpy.count(), 1);
2231+ QVERIFY(movedSpy.isEmpty());
2232+ QVERIFY(dataSpy.isEmpty());
2233+ args = insertionSpy.takeFirst();
2234+ QCOMPARE(args.at(1).toInt(), 2);
2235+ QCOMPARE(args.at(2).toInt(), 2);
2236+
2237+ // move a bookmark from a collapsed folder to another collapsed folder,
2238+ // "folder1" becomes empty
2239+ bookmarks->update(QUrl(QStringLiteral("http://test/url2")), QStringLiteral("url2"), QStringLiteral("folder2"));
2240+ QVERIFY(removalSpy.isEmpty());
2241+ QVERIFY(insertionSpy.isEmpty());
2242+ QVERIFY(movedSpy.isEmpty());
2243+ QCOMPARE(dataSpy.count(), 1);
2244+ args = dataSpy.takeFirst();
2245+ QCOMPARE(args.at(0).toModelIndex().row(), 3);
2246+ QCOMPARE(args.at(1).toModelIndex().row(), 3);
2247+ QVector<int> roles = args.at(2).value<QVector<int>>();
2248+ QCOMPARE(roles.count(), 1);
2249+ QVERIFY(roles.contains(CollapsibleBookmarksModel::Type));
2250+ QCOMPARE(model->data(model->index(3), CollapsibleBookmarksModel::Type).toString(), QStringLiteral("emptyFolder"));
2251+
2252+ // expand "folder2"
2253+ model->toggleFolderState(QStringLiteral("folder2"));
2254+ insertionSpy.clear();
2255+ dataSpy.clear();
2256+
2257+ // move a bookmark from an expanded folder to another expanded folder
2258+ bookmarks->update(QUrl(QStringLiteral("http://test/url1")), QStringLiteral("new title 1"), QStringLiteral("folder2"));
2259+ QVERIFY(removalSpy.isEmpty());
2260+ QVERIFY(insertionSpy.isEmpty());
2261+ QCOMPARE(movedSpy.count(), 1);
2262+ QCOMPARE(dataSpy.count(), 1);
2263+ args = movedSpy.takeFirst();
2264+ QCOMPARE(args.at(1).toInt(), 2);
2265+ QCOMPARE(args.at(2).toInt(), 2);
2266+ QCOMPARE(args.at(4).toInt(), 7);
2267+ args = dataSpy.takeFirst();
2268+ QCOMPARE(args.at(0).toModelIndex().row(), 6);
2269+ QCOMPARE(args.at(1).toModelIndex().row(), 6);
2270+ QVERIFY(args.at(2).value<QVector<int>>().isEmpty());
2271+
2272+ // move a bookmark from an expanded folder to a new folder
2273+ bookmarks->update(QUrl(QStringLiteral("http://test/url2")), QStringLiteral("url2"), QStringLiteral("a new folder"));
2274+ QCOMPARE(insertionSpy.count(), 1);
2275+ QCOMPARE(removalSpy.count(), 1);
2276+ QVERIFY(movedSpy.isEmpty());
2277+ QVERIFY(dataSpy.isEmpty());
2278+ args = insertionSpy.takeFirst();
2279+ QCOMPARE(args.at(1).toInt(), 2);
2280+ QCOMPARE(args.at(2).toInt(), 2);
2281+ QCOMPARE(model->data(model->index(2), CollapsibleBookmarksModel::Type).toString(), QStringLiteral("collapsedFolder"));
2282+ QCOMPARE(model->data(model->index(2), CollapsibleBookmarksModel::Folder).toString(), QStringLiteral("a new folder"));
2283+ args = removalSpy.takeFirst();
2284+ QCOMPARE(args.at(1).toInt(), 6);
2285+ QCOMPARE(args.at(2).toInt(), 6);
2286+
2287+ // expand "a new folder"
2288+ model->toggleFolderState(QStringLiteral("a new folder"));
2289+ insertionSpy.clear();
2290+ dataSpy.clear();
2291+
2292+ // do not show empty folders
2293+ model->setShowEmptyFolders(false);
2294+ removalSpy.clear();
2295+
2296+ // move the last bookmark from a folder to another folder,
2297+ // the source folder becomes empty and so should be hidden
2298+ bookmarks->update(QUrl(QStringLiteral("http://test/url2")), QStringLiteral("url2"), QStringLiteral("folder2"));
2299+ QVERIFY(insertionSpy.isEmpty());
2300+ QCOMPARE(removalSpy.count(), 1);
2301+ QCOMPARE(movedSpy.count(), 1);
2302+ QCOMPARE(dataSpy.count(), 1);
2303+ args = movedSpy.takeFirst();
2304+ QCOMPARE(args.at(1).toInt(), 3);
2305+ QCOMPARE(args.at(2).toInt(), 3);
2306+ QCOMPARE(args.at(4).toInt(), 6);
2307+ args = dataSpy.takeFirst();
2308+ QCOMPARE(args.at(0).toModelIndex().row(), 5);
2309+ QCOMPARE(args.at(1).toModelIndex().row(), 5);
2310+ QVERIFY(args.at(2).value<QVector<int>>().isEmpty());
2311+ args = removalSpy.takeFirst();
2312+ QCOMPARE(args.at(1).toInt(), 2);
2313+ QCOMPARE(args.at(2).toInt(), 2);
2314+
2315+ // move a bookmark from an expanded folder to another expanded folder
2316+ // that is positioned above in the model
2317+ bookmarks->update(QUrl(QStringLiteral("http://test/url2")), QStringLiteral("url2"), QStringLiteral(""));
2318+ QVERIFY(insertionSpy.isEmpty());
2319+ QVERIFY(removalSpy.isEmpty());
2320+ QCOMPARE(movedSpy.count(), 1);
2321+ QCOMPARE(dataSpy.count(), 1);
2322+ args = movedSpy.takeFirst();
2323+ QCOMPARE(args.at(1).toInt(), 4);
2324+ QCOMPARE(args.at(2).toInt(), 4);
2325+ QCOMPARE(args.at(4).toInt(), 2);
2326+ args = dataSpy.takeFirst();
2327+ QCOMPARE(args.at(0).toModelIndex().row(), 2);
2328+ QCOMPARE(args.at(1).toModelIndex().row(), 2);
2329+ QVERIFY(args.at(2).value<QVector<int>>().isEmpty());
2330+ }
2331+};
2332+
2333+QTEST_MAIN(CollapsibleBookmarksModelTests)
2334+#include "tst_CollapsibleBookmarksModelTests.moc"
2335
2336=== modified file 'tests/unittests/qml/CMakeLists.txt'
2337--- tests/unittests/qml/CMakeLists.txt 2015-10-06 09:48:38 +0000
2338+++ tests/unittests/qml/CMakeLists.txt 2015-12-02 14:15:47 +0000
2339@@ -20,6 +20,7 @@
2340 ${webbrowser-app_SOURCE_DIR}/bookmarks-model.cpp
2341 ${webbrowser-app_SOURCE_DIR}/bookmarks-folder-model.cpp
2342 ${webbrowser-app_SOURCE_DIR}/bookmarks-folderlist-model.cpp
2343+ ${webbrowser-app_SOURCE_DIR}/collapsible-bookmarks-model.cpp
2344 ${webbrowser-app_SOURCE_DIR}/file-operations.cpp
2345 ${webbrowser-app_SOURCE_DIR}/history-model.cpp
2346 ${webbrowser-app_SOURCE_DIR}/history-lastvisitdate-model.cpp
2347
2348=== modified file 'tests/unittests/qml/tst_BookmarksView.qml'
2349--- tests/unittests/qml/tst_BookmarksView.qml 2015-11-17 17:19:13 +0000
2350+++ tests/unittests/qml/tst_BookmarksView.qml 2015-12-02 14:15:47 +0000
2351@@ -92,22 +92,21 @@
2352
2353 function test_click_bookmark() {
2354 var items = getListItems(findChild(view, "bookmarksFolderListView"),
2355- "bookmarkFolderDelegateLoader")
2356+ "bookmarkViewDelegateLoader")
2357
2358- clickItem(findChild(items[0], "urlDelegate_0"))
2359+ clickItem(items[1])
2360 compare(bookmarkEntryClickedSpy.count, 1)
2361 compare(bookmarkEntryClickedSpy.signalArguments[0][0], view.homepageUrl)
2362
2363- clickItem(findChild(items[0], "urlDelegate_1"))
2364+ clickItem(items[2])
2365 compare(bookmarkEntryClickedSpy.count, 2)
2366 compare(bookmarkEntryClickedSpy.signalArguments[1][0], "http://example.com")
2367 }
2368
2369 function test_delete_bookmark() {
2370 var items = getListItems(findChild(view, "bookmarksFolderListView"),
2371- "bookmarkFolderDelegateLoader")
2372- var bookmark = findChild(items[0], "urlDelegate_1")
2373- swipeToDeleteAndConfirm(bookmark)
2374+ "bookmarkViewDelegateLoader")
2375+ swipeToDeleteAndConfirm(items[2])
2376 tryCompare(BookmarksModel, "count", 2)
2377 }
2378 }
2379
2380=== modified file 'tests/unittests/qml/tst_QmlTests.cpp'
2381--- tests/unittests/qml/tst_QmlTests.cpp 2015-10-22 15:07:26 +0000
2382+++ tests/unittests/qml/tst_QmlTests.cpp 2015-12-02 14:15:47 +0000
2383@@ -27,6 +27,7 @@
2384 // local
2385 #include "bookmarks-model.h"
2386 #include "bookmarks-folderlist-model.h"
2387+#include "collapsible-bookmarks-model.h"
2388 #include "favicon-fetcher.h"
2389 #include "file-operations.h"
2390 #include "history-model.h"
2391@@ -192,6 +193,7 @@
2392 qmlRegisterType<TabsModel>(browserUri, 0, 1, "TabsModel");
2393 qmlRegisterSingletonType<BookmarksModel>(browserUri, 0, 1, "BookmarksModel", BookmarksModel_singleton_factory);
2394 qmlRegisterType<BookmarksFolderListModel>(browserUri, 0, 1, "BookmarksFolderListModel");
2395+ qmlRegisterType<CollapsibleBookmarksModel>(browserUri, 0, 1, "CollapsibleBookmarksModel");
2396 qmlRegisterSingletonType<HistoryModel>(browserUri, 0, 1, "HistoryModel", HistoryModel_singleton_factory);
2397 qmlRegisterType<HistoryTimeframeModel>(browserUri, 0, 1, "HistoryTimeframeModel");
2398 qmlRegisterType<HistoryLastVisitDateListModel>(browserUri, 0, 1, "HistoryLastVisitDateListModel");

Subscribers

People subscribed via source and target branches

to status/vote changes: