on xenial help sign in
Dash: Fix issue when expanding a category and collapsing another one at the same time.
acb6653Michał Sawicz
plugins/Dash/listviewwithpageheader.cpp+11−1
@@ -409,6 +409,16 @@ void ListViewWithPageHeader::showHeader()
409 }
409 }
410}
410}
411
411
412+int ListViewWithPageHeader::firstCreatedIndex() const
413+{
414+ return m_firstVisibleIndex;
415+}
416+
417+int ListViewWithPageHeader::createdItemCount() const
418+{
419+ return m_visibleItems.count();
420+}
421+
412QQuickItem *ListViewWithPageHeader::item(int modelIndex) const
422QQuickItem *ListViewWithPageHeader::item(int modelIndex) const
413{
423{
414 ListItem *item = itemAtIndex(modelIndex);
424 ListItem *item = itemAtIndex(modelIndex);
@@ -520,7 +530,7 @@ void ListViewWithPageHeader::adjustHeader(qreal diff)
520 // (but the header was not shown by it's own position)
530 // (but the header was not shown by it's own position)
521 // or the header is partially shown and we are not doing a maximizeVisibleArea either
531 // or the header is partially shown and we are not doing a maximizeVisibleArea either
522 const bool scrolledUp = m_previousContentY > contentY();
532 const bool scrolledUp = m_previousContentY > contentY();
523− const bool notRebounding = contentY() + height() < contentHeight();
533+ const bool notRebounding = qRound(contentY() + height()) < qRound(contentHeight());
524 const bool notShownByItsOwn = contentY() + diff >= m_headerItem->y() + m_headerItem->height();
534 const bool notShownByItsOwn = contentY() + diff >= m_headerItem->y() + m_headerItem->height();
525 const bool maximizeVisibleAreaRunning = m_contentYAnimation->isRunning() && contentYAnimationType == ContentYAnimationMaximizeVisibleArea;
535 const bool maximizeVisibleAreaRunning = m_contentYAnimation->isRunning() && contentYAnimationType == ContentYAnimationMaximizeVisibleArea;
526
536
plugins/Dash/listviewwithpageheader.h+2
@@ -86,6 +86,8 @@ public:
86
86
87 Q_INVOKABLE void positionAtBeginning();
87 Q_INVOKABLE void positionAtBeginning();
88 Q_INVOKABLE void showHeader();
88 Q_INVOKABLE void showHeader();
89+ Q_INVOKABLE int firstCreatedIndex() const;
90+ Q_INVOKABLE int createdItemCount() const;
89 Q_INVOKABLE QQuickItem *item(int modelIndex) const;
91 Q_INVOKABLE QQuickItem *item(int modelIndex) const;
90
92
91 // The index has to be created for this to try to do something
93 // The index has to be created for this to try to do something
qml/Dash/GenericScopeView.qml+74−29
@@ -47,6 +47,10 @@ FocusScope {
47
47
48 signal backClicked()
48 signal backClicked()
49
49
50+ onScopeChanged: {
51+ floatingSeeLess.companionBase = null;
52+ }
53+
50 function positionAtBeginning() {
54 function positionAtBeginning() {
51 categoryView.positionAtBeginning()
55 categoryView.positionAtBeginning()
52 }
56 }
@@ -152,11 +156,54 @@ FocusScope {
152 forceNoClip: subPageLoader.open
156 forceNoClip: subPageLoader.open
153 pixelAligned: true
157 pixelAligned: true
154
158
155− property Item expandedCategoryItem: null
159+ property string expandedCategoryId: ""
160+ property int runMaximizeAfterSizeChanges: 0
156
161
157 readonly property bool pageHeaderTotallyVisible: scopeView.showPageHeader &&
162 readonly property bool pageHeaderTotallyVisible: scopeView.showPageHeader &&
158 ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == pageHeaderLoader.item.height))
163 ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == pageHeaderLoader.item.height))
159
164
165+ onExpandedCategoryIdChanged: {
166+ var firstCreated = firstCreatedIndex();
167+ var shrinkingAny = false;
168+ var shrinkHeightDifference = 0;
169+ for (var i = 0; i < createdItemCount(); ++i) {
170+ var baseItem = item(firstCreated + i);
171+ if (baseItem.expandable) {
172+ var shouldExpand = baseItem.category === expandedCategoryId;
173+ if (shouldExpand != baseItem.expanded) {
174+ var animate = false;
175+ if (!subPageLoader.open) {
176+ var animateShrinking = !shouldExpand && baseItem.y + baseItem.item.collapsedHeight + baseItem.seeAllButton.height < categoryView.height;
177+ var animateGrowing = shouldExpand && baseItem.y + baseItem.height < categoryView.height;
178+ animate = shrinkingAny || animateShrinking || animateGrowing;
179+ }
180+
181+ if (!shouldExpand) {
182+ shrinkingAny = true;
183+ shrinkHeightDifference = baseItem.item.expandedHeight - baseItem.item.collapsedHeight;
184+ }
185+
186+ if (shouldExpand && !subPageLoader.open) {
187+ if (!shrinkingAny) {
188+ categoryView.maximizeVisibleArea(firstCreated + i, baseItem.item.expandedHeight + baseItem.seeAllButton.height);
189+ } else {
190+ // If the space that shrinking is smaller than the one we need to grow we'll call maximizeVisibleArea
191+ // after the shrink/grow animation ends
192+ var growHeightDifference = baseItem.item.expandedHeight - baseItem.item.collapsedHeight;
193+ if (growHeightDifference > shrinkHeightDifference) {
194+ runMaximizeAfterSizeChanges = 2;
195+ } else {
196+ runMaximizeAfterSizeChanges = 0;
197+ }
198+ }
199+ }
200+
201+ baseItem.expand(shouldExpand, animate);
202+ }
203+ }
204+ }
205+ }
206+
160 delegate: ListItems.Base {
207 delegate: ListItems.Base {
161 id: baseItem
208 id: baseItem
162 objectName: "dashCategory" + category
209 objectName: "dashCategory" + category
@@ -194,7 +241,7 @@ FocusScope {
194 // This can happen with the VJ that doesn't know how height it will be on creation
241 // This can happen with the VJ that doesn't know how height it will be on creation
195 // so doesn't set expandable until a bit too late for onLoaded
242 // so doesn't set expandable until a bit too late for onLoaded
196 if (expandable) {
243 if (expandable) {
197− var shouldExpand = baseItem === categoryView.expandedCategoryItem;
244+ var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
198 baseItem.expand(shouldExpand, false /*animate*/);
245 baseItem.expand(shouldExpand, false /*animate*/);
199 }
246 }
200 }
247 }
@@ -215,9 +262,23 @@ FocusScope {
215 id: heightBehaviour
262 id: heightBehaviour
216 enabled: false
263 enabled: false
217 animation: UbuntuNumberAnimation {
264 animation: UbuntuNumberAnimation {
265+ duration: UbuntuAnimation.FastDuration
218 onRunningChanged: {
266 onRunningChanged: {
219 if (!running) {
267 if (!running) {
220 heightBehaviour.enabled = false
268 heightBehaviour.enabled = false
269+ if (categoryView.runMaximizeAfterSizeChanges > 0) {
270+ categoryView.runMaximizeAfterSizeChanges--;
271+ if (categoryView.runMaximizeAfterSizeChanges == 0) {
272+ var firstCreated = categoryView.firstCreatedIndex();
273+ for (var i = 0; i < categoryView.createdItemCount(); ++i) {
274+ var baseItem = categoryView.item(firstCreated + i);
275+ if (baseItem.category === categoryView.expandedCategoryId) {
276+ categoryView.maximizeVisibleArea(firstCreated + i, baseItem.item.expandedHeight + baseItem.seeAllButton.height);
277+ break;
278+ }
279+ }
280+ }
281+ }
221 }
282 }
222 }
283 }
223 }
284 }
@@ -245,7 +306,7 @@ FocusScope {
245 item.objectName = Qt.binding(function() { return categoryId })
306 item.objectName = Qt.binding(function() { return categoryId })
246 item.scopeStyle = scopeView.scopeStyle;
307 item.scopeStyle = scopeView.scopeStyle;
247 if (baseItem.expandable) {
308 if (baseItem.expandable) {
248− var shouldExpand = baseItem === categoryView.expandedCategoryItem;
309+ var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
249 baseItem.expand(shouldExpand, false /*animate*/);
310 baseItem.expand(shouldExpand, false /*animate*/);
250 }
311 }
251 updateDelegateCreationRange();
312 updateDelegateCreationRange();
@@ -282,27 +343,6 @@ FocusScope {
282 }
343 }
283 Connections {
344 Connections {
284 target: categoryView
345 target: categoryView
285− onExpandedCategoryItemChanged: {
286− collapseAllButExpandedCategory();
287− }
288− function collapseAllButExpandedCategory() {
289− var item = rendererLoader.item;
290− if (baseItem.expandable) {
291− var shouldExpand = baseItem === categoryView.expandedCategoryItem;
292− if (shouldExpand != baseItem.expanded) {
293− // If the filter animation will be seen start it, otherwise, just flip the switch
294− var shrinkingVisible = !shouldExpand && y + item.collapsedHeight + seeAll.height < categoryView.height;
295− var growingVisible = shouldExpand && y + height < categoryView.height;
296− if (!subPageLoader.open || shouldExpand) {
297− var animate = shrinkingVisible || growingVisible;
298− baseItem.expand(shouldExpand, animate)
299− if (shouldExpand && !subPageLoader.open) {
300− categoryView.maximizeVisibleArea(index, item.expandedHeight + seeAll.height);
301− }
302− }
303− }
304− }
305− }
306 onOriginYChanged: rendererLoader.updateDelegateCreationRange();
346 onOriginYChanged: rendererLoader.updateDelegateCreationRange();
307 onContentYChanged: rendererLoader.updateDelegateCreationRange();
347 onContentYChanged: rendererLoader.updateDelegateCreationRange();
308 onHeightChanged: rendererLoader.updateDelegateCreationRange();
348 onHeightChanged: rendererLoader.updateDelegateCreationRange();
@@ -352,10 +392,11 @@ FocusScope {
352 height: seeAllLabel.visible ? seeAllLabel.font.pixelSize + units.gu(4) : 0
392 height: seeAllLabel.visible ? seeAllLabel.font.pixelSize + units.gu(4) : 0
353
393
354 onClicked: {
394 onClicked: {
355− if (categoryView.expandedCategoryItem !== baseItem) {
395+ if (categoryView.expandedCategoryId !== baseItem.category) {
356− categoryView.expandedCategoryItem = baseItem;
396+ categoryView.expandedCategoryId = baseItem.category;
397+ floatingSeeLess.companionBase = baseItem;
357 } else {
398 } else {
358− categoryView.expandedCategoryItem = null;
399+ categoryView.expandedCategoryId = "";
359 }
400 }
360 }
401 }
361
402
@@ -455,7 +496,7 @@ FocusScope {
455 objectName: "floatingSeeLess"
496 objectName: "floatingSeeLess"
456
497
457 property Item companionTo: companionBase ? companionBase.seeAllButton : null
498 property Item companionTo: companionBase ? companionBase.seeAllButton : null
458− property Item companionBase: categoryView.expandedCategoryItem
499+ property Item companionBase: null
459 property bool showBecausePosition: false
500 property bool showBecausePosition: false
460 property real yOffset: 0
501 property real yOffset: 0
461
502
@@ -467,7 +508,7 @@ FocusScope {
467 height: seeLessLabel.font.pixelSize + units.gu(4)
508 height: seeLessLabel.font.pixelSize + units.gu(4)
468 visible: companionTo && showBecausePosition
509 visible: companionTo && showBecausePosition
469
510
470− onClicked: categoryView.expandedCategoryItem = null;
511+ onClicked: categoryView.expandedCategoryId = "";
471
512
472 function updateVisibility() {
513 function updateVisibility() {
473 var companionPos = companionTo.mapToItem(floatingSeeLess, 0, 0);
514 var companionPos = companionTo.mapToItem(floatingSeeLess, 0, 0);
@@ -476,6 +517,10 @@ FocusScope {
476 var posToBase = floatingSeeLess.mapToItem(companionBase, 0, -yOffset).y;
517 var posToBase = floatingSeeLess.mapToItem(companionBase, 0, -yOffset).y;
477 yOffset = Math.max(0, companionBase.item.collapsedHeight - posToBase);
518 yOffset = Math.max(0, companionBase.item.collapsedHeight - posToBase);
478 yOffset = Math.min(yOffset, height);
519 yOffset = Math.min(yOffset, height);
520+
521+ if (!showBecausePosition && categoryView.expandedCategoryId === "") {
522+ companionBase = null;
523+ }
479 }
524 }
480
525
481 Label {
526 Label {
tests/mocks/Unity/fake_categories.cpp+95−136
@@ -29,25 +29,12 @@ Categories::Categories(int category_count, QObject* parent)
29
29
30int Categories::rowCount(const QModelIndex& /*parent*/) const
30int Categories::rowCount(const QModelIndex& /*parent*/) const
31{
31{
32− return m_category_count + m_specialCategories.count();
32+ return m_category_count;
33}
33}
34
34
35−void Categories::addSpecialCategory(QString const& categoryId, QString const& name, QString const& icon, QString const& rawTemplate, QObject* countObject)
35+void Categories::addSpecialCategory(QString const& /*categoryId*/, QString const& /*name*/, QString const& /*icon*/, QString const& /*rawTemplate*/, QObject* /*countObject*/)
36{
36{
37− CategoryData catData;
37+ qFatal("Using un-implemented Categories::addSpecialCategory");
38− catData.categoryId = categoryId;
39− catData.name = name;
40− catData.icon = icon;
41− catData.rawTemplate = rawTemplate;
42− catData.countObject = countObject;
43−
44− beginInsertRows(QModelIndex(), 0, 0);
45− m_specialCategories.prepend(catData);
46− endInsertRows();
47−
48− if (countObject) {
49− connect(countObject, SIGNAL(countChanged()), this, SLOT(countChanged()));
50− }
51}
38}
52
39
53bool Categories::overrideCategoryJson(QString const& /* categoryId */, QString const& /* json */)
40bool Categories::overrideCategoryJson(QString const& /* categoryId */, QString const& /* json */)
@@ -55,23 +42,6 @@ bool Categories::overrideCategoryJson(QString const& /* categoryId */, QString c
55 qFatal("Using un-implemented Categories::overrideCategoryJson");
42 qFatal("Using un-implemented Categories::overrideCategoryJson");
56}
43}
57
44
58−void Categories::countChanged()
59−{
60− QObject* countObject = sender();
61−
62− for (int i = 0; i < m_specialCategories.count(); ++i) {
63− const CategoryData &catData = m_specialCategories.at(i);
64− if (catData.countObject == countObject) {
65− QVector<int> roles;
66− roles.append(RoleCount);
67−
68− QModelIndex changedIndex(index(i));
69− dataChanged(changedIndex, changedIndex, roles);
70− break;
71− }
72− }
73−}
74−
75QVariant
45QVariant
76Categories::data(const QModelIndex& index, int role) const
46Categories::data(const QModelIndex& index, int role) const
77{
47{
@@ -79,115 +49,74 @@ Categories::data(const QModelIndex& index, int role) const
79 return QVariant();
49 return QVariant();
80 }
50 }
81
51
82− ResultsModel *resultsModel = m_resultsModels[index.row()];
52+ ResultsModel *resultsModel = resultModel(index.row());
83− if (!resultsModel) {
84− resultsModel = new ResultsModel(15, index.row());
85− m_resultsModels[index.row()] = resultsModel;
86− }
87− if (index.row() < m_specialCategories.count()) {
88− const CategoryData &catData = m_specialCategories.at(index.row());
89− switch (role) {
53+ switch (role) {
90− case RoleCategoryId:
54+ case RoleCategoryId:
91− return catData.categoryId;
55+ return QString("%1").arg(index.row());
92− case RoleName:
56+ case RoleName:
93− return catData.name;
57+ return QString("Category %1").arg(index.row());
94− case RoleIcon:
58+ case RoleIcon:
95− return catData.icon;
59+ return "gtk-apply";
96− case RoleRawRendererTemplate:
60+ case RoleRawRendererTemplate:
97− return catData.rawTemplate;
61+ qFatal("Using un-implemented RoleRawRendererTemplate Categories role");
62+ return QVariant();
98− case RoleRenderer:
63+ case RoleRenderer:
99− {
64+ {
100− QVariantMap map;
65+ QVariantMap map;
101− if (index.row() % 2 == 0) {
66+ if (index.row() % 2 == 0) {
102− map["category-layout"] = "grid";
67+ map["category-layout"] = "grid";
103− } else {
68+ } else {
104− map["category-layout"] = "carousel";
69+ map["category-layout"] = "carousel";
105− map["overlay"] = true;
106− }
107− map["card-size"] = "small";
108− return map;
109 }
70 }
110− case RoleComponents:
71+ if (index.row() == 18) {
111− {
72+ map["category-layout"] = "horizontal-list";
112− QVariantMap map, artMap, attributeMap;
73+ }
113− artMap["aspect-ratio"] = "1.0";
74+ if (index.row() == 19) {
114− artMap["field"] = "art";
75+ map["category-layout"] = "grid";
115− map["art"] = artMap;
76+ map["collapsed-rows"] = 0;
116− map["title"] = "HOLA";
77+ }
117− attributeMap["field"] = "attribute";
78+ map["card-size"] = "small";
118− map["attributes"] = attributeMap;
79+
119− return map;
80+ map["category-layout"] = m_layouts.value(index.row(), map["category-layout"].toString());
81+
82+ if (map["category-layout"] == "carousel") {
83+ map["overlay"] = true;
120 }
84 }
121− case RoleHeaderLink:
85+
122− return QString();
123− case RoleResults:
124− return QVariant();
86+ return map;
125− case RoleCount:
126− return catData.countObject->property("count");
127− default:
128− qFatal("Using un-implemented Categories role");
129− return QVariant();
130 }
87 }
131− } else {
88+ case RoleComponents:
132− switch (role) {
133− case RoleCategoryId:
134− return QString("%1").arg(index.row());
135− case RoleName:
136− return QString("Category %1").arg(index.row());
137− case RoleIcon:
138− return "gtk-apply";
139− case RoleRawRendererTemplate:
140− qFatal("Using un-implemented RoleRawRendererTemplate Categories role");
141− return QVariant();
142− case RoleRenderer:
143− {
89+ {
144− QVariantMap map;
90+ QVariantMap map, artMap, attributeMap;
145− if (index.row() % 2 == 0) {
91+ if (index.row() % 2 != 0) {
146− map["category-layout"] = "grid";
92+ artMap["aspect-ratio"] = QString("1.%1").arg(index.row());
147− } else {
93+ } else {
148− map["category-layout"] = "carousel";
94+ artMap["aspect-ratio"] = "1.0";
149− map["card-size"] = "medium";
150− map["overlay"] = true;
151− }
152− if (index.row() == 18) {
153− map["category-layout"] = "horizontal-list";
154− }
155− if (index.row() == 19) {
156− map["category-layout"] = "grid";
157− map["collapsed-rows"] = 0;
158− }
159− map["card-size"] = "small";
160− return map;
161 }
95 }
162− case RoleComponents:
163− {
164− QVariantMap map, artMap, attributeMap;
165− if (index.row() % 2 != 0) {
166− artMap["aspect-ratio"] = QString("1.%1").arg(index.row());
167− } else {
168− artMap["aspect-ratio"] = "1.0";
169− }
170− artMap["field"] = "art";
96+ artMap["field"] = "art";
171− map["art"] = artMap;
97+ map["art"] = artMap;
172− map["title"] = "HOLA";
98+ map["title"] = "HOLA";
173− map["subtitle"] = "HOLA";
99+ map["subtitle"] = "HOLA";
174− attributeMap["field"] = "attribute";
100+ attributeMap["field"] = "attribute";
175− map["attributes"] = attributeMap;
101+ map["attributes"] = attributeMap;
176− return map;
102+ return map;
103+ }
104+ case RoleHeaderLink:
105+ {
106+ QString res;
107+ if (index.row() == 1 || index.row() == 4) {
108+ res = QString("scope://query/1");
177 }
109 }
178− case RoleHeaderLink:
110+ res = m_headerLinks.value(index.row(), res);
179− if (index.row() == 1 || index.row() == 4) {
180− return QString("scope://query/1");
181− }
182− return QString();
183− case RoleResults:
184− return QVariant::fromValue(resultsModel);
185− case RoleCount:
186− return resultsModel->rowCount();
187− default:
188− qFatal("Using un-implemented Categories role");
189− return QVariant();
111+ return res;
190 }
112 }
113+ case RoleResults:
114+ return QVariant::fromValue(resultsModel);
115+ case RoleCount:
116+ return resultsModel->rowCount();
117+ default:
118+ qFatal("Using un-implemented Categories role");
119+ return QVariant();
191 }
120 }
192}
121}
193
122
@@ -196,3 +125,33 @@ Categories::data(int row, int role) const
196{
125{
197 return data(index(row, 0), role);
126 return data(index(row, 0), role);
198}
127}
128+
129+void Categories::setCount(int count)
130+{
131+ if (m_category_count != count) {
132+ beginResetModel(); // This is just for test setup so we can be lazy and reset
133+ m_category_count = count;
134+ endResetModel();
135+ }
136+}
137+
138+ResultsModel* Categories::resultModel(int row) const
139+{
140+ ResultsModel *result = m_resultsModels[row];
141+ if (!result) {
142+ Categories *that = const_cast<Categories*>(this);
143+ result = new ResultsModel(15, row, that);
144+ m_resultsModels[row] = result;
145+ }
146+ return result;
147+}
148+
149+void Categories::setLayout(int row, const QString &layout)
150+{
151+ m_layouts[row] = layout;
152+}
153+
154+void Categories::setHeaderLink(int row, const QString &headerLink)
155+{
156+ m_headerLinks[row] = headerLink;
157+}
tests/mocks/Unity/fake_categories.h+7−12
@@ -39,22 +39,17 @@ public:
39
39
40 Q_INVOKABLE QVariant data(int row, int role) const;
40 Q_INVOKABLE QVariant data(int row, int role) const;
41
41
42−private Q_SLOTS:
42+ // For testing purposes
43− void countChanged();
43+ Q_INVOKABLE void setCount(int count);
44+ Q_INVOKABLE ResultsModel* resultModel(int row) const;
45+ Q_INVOKABLE void setLayout(int row, const QString &layout);
46+ Q_INVOKABLE void setHeaderLink(int row, const QString &headerLink);
44
47
45private:
48private:
46 mutable QHash<int, ResultsModel*> m_resultsModels;
49 mutable QHash<int, ResultsModel*> m_resultsModels;
50+ QHash<int, QString> m_layouts;
51+ QHash<int, QString> m_headerLinks;
47 int m_category_count;
52 int m_category_count;
48−
49− struct CategoryData {
50− QString categoryId;
51− QString name;
52− QString icon;
53− QString rawTemplate;
54− QObject* countObject;
55− };
56−
57− QList<CategoryData> m_specialCategories;
58};
53};
59
54
60#endif // FAKE_CATEGORIES_H
55#endif // FAKE_CATEGORIES_H
tests/mocks/Unity/fake_resultsmodel.cpp+9
@@ -44,6 +44,15 @@ void ResultsModel::setCategoryId(QString const& /*id*/)
44 qFatal("Calling un-implemented ResultsModel::setCategoryId");
44 qFatal("Calling un-implemented ResultsModel::setCategoryId");
45}
45}
46
46
47+void ResultsModel::setResultCount(int result_count)
48+{
49+ if (m_result_count != result_count) {
50+ beginResetModel(); // This is just for test setup so we can be lazy and reset
51+ m_result_count = result_count;
52+ endResetModel();
53+ }
54+}
55+
47int ResultsModel::rowCount(const QModelIndex& parent) const
56int ResultsModel::rowCount(const QModelIndex& parent) const
48{
57{
49 Q_UNUSED(parent);
58 Q_UNUSED(parent);
tests/mocks/Unity/fake_resultsmodel.h+3
@@ -41,6 +41,9 @@ public:
41 /* setters */
41 /* setters */
42 void setCategoryId(QString const& id) override;
42 void setCategoryId(QString const& id) override;
43
43
44+ // For testing purposes
45+ Q_INVOKABLE void setResultCount(int result_count);
46+
44private:
47private:
45 int m_result_count;
48 int m_result_count;
46 int m_categoryId;
49 int m_categoryId;
tests/mocks/Unity/fake_scope.cpp+17
@@ -145,6 +145,23 @@ void Scope::setFavorite(const bool favorite)
145 Q_EMIT favoriteChanged();
145 Q_EMIT favoriteChanged();
146 }
146 }
147}
147}
148+
149+void Scope::setId(const QString &id)
150+{
151+ if (id != m_id) {
152+ m_id = id;
153+ Q_EMIT idChanged();
154+ }
155+}
156+
157+void Scope::setName(const QString &name)
158+{
159+ if (name != m_name) {
160+ m_name = name;
161+ Q_EMIT nameChanged();
162+ }
163+}
164+
148void Scope::setSearchInProgress(const bool inProg)
165void Scope::setSearchInProgress(const bool inProg)
149{
166{
150 if (inProg != m_searching) {
167 if (inProg != m_searching) {
tests/mocks/Unity/fake_scope.h+2
@@ -56,6 +56,8 @@ public:
56 void setFormFactor(const QString& form_factor) override;
56 void setFormFactor(const QString& form_factor) override;
57 void setActive(const bool) override;
57 void setActive(const bool) override;
58 void setFavorite(const bool) override;
58 void setFavorite(const bool) override;
59+ Q_INVOKABLE void setId(const QString &id); // This is not invokable in the Interface, here for testing benefits
60+ Q_INVOKABLE void setName(const QString &name); // This is not invokable in the Interface, here for testing benefits
59 Q_INVOKABLE void setSearchInProgress(const bool inProg); // This is not invokable in the Interface, here for testing benefits
61 Q_INVOKABLE void setSearchInProgress(const bool inProg); // This is not invokable in the Interface, here for testing benefits
60
62
61 Q_INVOKABLE void activate(QVariant const& result) override;
63 Q_INVOKABLE void activate(QVariant const& result) override;
tests/qmltests/Dash/tst_GenericScopeView.qml+97−1
@@ -1,5 +1,5 @@
1/*
1/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2014 Canonical Ltd.
3 *
3 *
4 * This program is free software; you can redistribute it and/or modify
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
5 * it under the terms of the GNU General Public License as published by
@@ -43,6 +43,10 @@ Item {
43 onLoadedChanged: if (loaded) genericScopeView.scope = scopes.getScope(2);
43 onLoadedChanged: if (loaded) genericScopeView.scope = scopes.getScope(2);
44 }
44 }
45
45
46+ MockScope {
47+ id: mockScope
48+ }
49+
46 SignalSpy {
50 SignalSpy {
47 id: spy
51 id: spy
48 }
52 }
@@ -92,6 +96,17 @@ Item {
92 return findChild(genericScopeView, category);
96 return findChild(genericScopeView, category);
93 }
97 }
94
98
99+ function scrollToEnd()
100+ {
101+ var categoryListView = findChild(genericScopeView, "categoryListView");
102+ waitForRendering(categoryListView);
103+ while (!categoryListView.atYEnd) {
104+ mouseFlick(genericScopeView, genericScopeView.width/2, genericScopeView.height - units.gu(8),
105+ genericScopeView.width/2, genericScopeView.y)
106+ tryCompare(categoryListView, "moving", false);
107+ }
108+ }
109+
95 function test_isActive() {
110 function test_isActive() {
96 tryCompare(genericScopeView.scope, "isActive", false)
111 tryCompare(genericScopeView.scope, "isActive", false)
97 genericScopeView.isCurrent = true
112 genericScopeView.isCurrent = true
@@ -426,6 +441,87 @@ Item {
426 compare(image.source, data.logo, "Title image has the wrong source");
441 compare(image.source, data.logo, "Title image has the wrong source");
427 }
442 }
428
443
444+ function test_seeAllTwoCategoriesScenario1() {
445+ mockScope.setId("mockScope");
446+ mockScope.setName("Mock Scope");
447+ mockScope.categories.setCount(2);
448+ mockScope.categories.resultModel(0).setResultCount(50);
449+ mockScope.categories.resultModel(1).setResultCount(15);
450+ mockScope.categories.setLayout(0, "grid");
451+ mockScope.categories.setLayout(1, "grid");
452+ mockScope.categories.setHeaderLink(0, "");
453+ mockScope.categories.setHeaderLink(1, "");
454+ genericScopeView.scope = mockScope;
455+ waitForRendering(genericScopeView.categoryView);
456+
457+ var category0 = findChild(genericScopeView, "dashCategory0")
458+ var seeAll0 = findChild(category0, "seeAll")
459+
460+ waitForRendering(seeAll0);
461+ verify(category0.expandable);
462+ verify(!category0.expanded);
463+
464+ mouseClick(seeAll0, seeAll0.width / 2, seeAll0.height / 2);
465+ verify(category0.expanded);
466+ tryCompare(category0, "height", category0.item.expandedHeight + seeAll0.height);
467+ tryCompare(genericScopeView.categoryView, "contentY", units.gu(13.5));
468+
469+ scrollToEnd();
470+
471+ tryCompareFunction(function() { return findChild(genericScopeView, "dashCategory1") !== null; }, true);
472+ var category1 = findChild(genericScopeView, "dashCategory1")
473+ var seeAll1 = findChild(category1, "seeAll")
474+ verify(category1.expandable);
475+ verify(!category1.expanded);
476+
477+ mouseClick(seeAll1, seeAll1.width / 2, seeAll1.height / 2);
478+ verify(!category0.expanded);
479+ verify(category1.expanded);
480+ tryCompare(category1, "height", category1.item.expandedHeight + seeAll1.height);
481+ tryCompareFunction(function() {
482+ return genericScopeView.categoryView.contentY + category1.y + category1.height
483+ == genericScopeView.categoryView.contentHeight;}
484+ , true);
485+ }
486+
487+ function test_seeAllTwoCategoriesScenario2() {
488+ mockScope.setId("mockScope");
489+ mockScope.setName("Mock Scope");
490+ mockScope.categories.setCount(2);
491+ mockScope.categories.resultModel(0).setResultCount(15);
492+ mockScope.categories.resultModel(1).setResultCount(50);
493+ mockScope.categories.setLayout(0, "grid");
494+ mockScope.categories.setLayout(1, "grid");
495+ mockScope.categories.setHeaderLink(0, "");
496+ mockScope.categories.setHeaderLink(1, "");
497+ genericScopeView.scope = mockScope;
498+ waitForRendering(genericScopeView.categoryView);
499+
500+ var category0 = findChild(genericScopeView, "dashCategory0")
501+ var seeAll0 = findChild(category0, "seeAll")
502+
503+ waitForRendering(seeAll0);
504+ verify(category0.expandable);
505+ verify(!category0.expanded);
506+
507+ mouseClick(seeAll0, seeAll0.width / 2, seeAll0.height / 2);
508+ verify(category0.expanded);
509+ tryCompare(category0, "height", category0.item.expandedHeight + seeAll0.height);
510+
511+ scrollToEnd();
512+
513+ var category1 = findChild(genericScopeView, "dashCategory1")
514+ var seeAll1 = findChild(category1, "seeAll")
515+ verify(category1.expandable);
516+ verify(!category1.expanded);
517+
518+ mouseClick(seeAll1, seeAll1.width / 2, seeAll1.height / 2);
519+ verify(!category0.expanded);
520+ verify(category1.expanded);
521+ tryCompare(category1, "height", category1.item.expandedHeight + seeAll1.height);
522+ tryCompare(category1, "y", units.gu(5));
523+ }
524+
429 function test_favorite_data() {
525 function test_favorite_data() {
430 return [
526 return [
431 { tag: "People", id: "MockScope1", favorite: true },
527 { tag: "People", id: "MockScope1", favorite: true },