on xenial help sign in
Dash: Fix issue when expanding a category and collapsing another one at the same time.

We need to first run the shrink+grow animations in the two category items, and later, if needed run the maximizeVisibleArea in the one we expanded. It does not fix *everything* related to seeAll growing/shrinking + maximization, it may need some rethinking/rearchitecture to fix all issues, so let's just get these in that fix some very broken behaviour Fixes: 1357358 Approved by: Andrea Cimitan

commitacb66537fdb2cf39d801002758a7003b9f5f5696 authorMichał Sawicz <michal.sawicz@canonical.com> dateThu 18 Sep 2014, 21:23 (12 years ago) parent318e81a02e7905
10 files changed+317−179
plugins/Dash/listviewwithpageheader.cpp+11−1 plugins/Dash/listviewwithpageheader.h+2 qml/Dash/GenericScopeView.qml+74−29 tests/mocks/Unity/fake_categories.cpp+95−136 tests/mocks/Unity/fake_categories.h+7−12 tests/mocks/Unity/fake_resultsmodel.cpp+9 tests/mocks/Unity/fake_resultsmodel.h+3 tests/mocks/Unity/fake_scope.cpp+17 tests/mocks/Unity/fake_scope.h+2 tests/qmltests/Dash/tst_GenericScopeView.qml+97−1
plugins/Dash/listviewwithpageheader.cpp+11−1
@@ -409,6 +409,16 @@ void ListViewWithPageHeader::showHeader()
409409 }
410410}
411411
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+
412422QQuickItem *ListViewWithPageHeader::item(int modelIndex) const
413423{
414424 ListItem *item = itemAtIndex(modelIndex);
@@ -520,7 +530,7 @@ void ListViewWithPageHeader::adjustHeader(qreal diff)
520530 // (but the header was not shown by it's own position)
521531 // or the header is partially shown and we are not doing a maximizeVisibleArea either
522532 const bool scrolledUp = m_previousContentY > contentY();
523− const bool notRebounding = contentY() + height() < contentHeight();
533+ const bool notRebounding = qRound(contentY() + height()) < qRound(contentHeight());
524534 const bool notShownByItsOwn = contentY() + diff >= m_headerItem->y() + m_headerItem->height();
525535 const bool maximizeVisibleAreaRunning = m_contentYAnimation->isRunning() && contentYAnimationType == ContentYAnimationMaximizeVisibleArea;
526536
plugins/Dash/listviewwithpageheader.h+2
@@ -86,6 +86,8 @@ public:
8686
8787 Q_INVOKABLE void positionAtBeginning();
8888 Q_INVOKABLE void showHeader();
89+ Q_INVOKABLE int firstCreatedIndex() const;
90+ Q_INVOKABLE int createdItemCount() const;
8991 Q_INVOKABLE QQuickItem *item(int modelIndex) const;
9092
9193 // The index has to be created for this to try to do something
qml/Dash/GenericScopeView.qml+74−29
@@ -47,6 +47,10 @@ FocusScope {
4747
4848 signal backClicked()
4949
50+ onScopeChanged: {
51+ floatingSeeLess.companionBase = null;
52+ }
53+
5054 function positionAtBeginning() {
5155 categoryView.positionAtBeginning()
5256 }
@@ -152,11 +156,54 @@ FocusScope {
152156 forceNoClip: subPageLoader.open
153157 pixelAligned: true
154158
155− property Item expandedCategoryItem: null
159+ property string expandedCategoryId: ""
160+ property int runMaximizeAfterSizeChanges: 0
156161
157162 readonly property bool pageHeaderTotallyVisible: scopeView.showPageHeader &&
158163 ((headerItemShownHeight == 0 && categoryView.contentY <= categoryView.originY) || (headerItemShownHeight == pageHeaderLoader.item.height))
159164
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+
160207 delegate: ListItems.Base {
161208 id: baseItem
162209 objectName: "dashCategory" + category
@@ -194,7 +241,7 @@ FocusScope {
194241 // This can happen with the VJ that doesn't know how height it will be on creation
195242 // so doesn't set expandable until a bit too late for onLoaded
196243 if (expandable) {
197− var shouldExpand = baseItem === categoryView.expandedCategoryItem;
244+ var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
198245 baseItem.expand(shouldExpand, false /*animate*/);
199246 }
200247 }
@@ -215,9 +262,23 @@ FocusScope {
215262 id: heightBehaviour
216263 enabled: false
217264 animation: UbuntuNumberAnimation {
265+ duration: UbuntuAnimation.FastDuration
218266 onRunningChanged: {
219267 if (!running) {
220268 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+ }
221282 }
222283 }
223284 }
@@ -245,7 +306,7 @@ FocusScope {
245306 item.objectName = Qt.binding(function() { return categoryId })
246307 item.scopeStyle = scopeView.scopeStyle;
247308 if (baseItem.expandable) {
248− var shouldExpand = baseItem === categoryView.expandedCategoryItem;
309+ var shouldExpand = baseItem.category === categoryView.expandedCategoryId;
249310 baseItem.expand(shouldExpand, false /*animate*/);
250311 }
251312 updateDelegateCreationRange();
@@ -282,27 +343,6 @@ FocusScope {
282343 }
283344 Connections {
284345 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− }
306346 onOriginYChanged: rendererLoader.updateDelegateCreationRange();
307347 onContentYChanged: rendererLoader.updateDelegateCreationRange();
308348 onHeightChanged: rendererLoader.updateDelegateCreationRange();
@@ -352,10 +392,11 @@ FocusScope {
352392 height: seeAllLabel.visible ? seeAllLabel.font.pixelSize + units.gu(4) : 0
353393
354394 onClicked: {
355− if (categoryView.expandedCategoryItem !== baseItem) {
356− categoryView.expandedCategoryItem = baseItem;
395+ if (categoryView.expandedCategoryId !== baseItem.category) {
396+ categoryView.expandedCategoryId = baseItem.category;
397+ floatingSeeLess.companionBase = baseItem;
357398 } else {
358− categoryView.expandedCategoryItem = null;
399+ categoryView.expandedCategoryId = "";
359400 }
360401 }
361402
@@ -455,7 +496,7 @@ FocusScope {
455496 objectName: "floatingSeeLess"
456497
457498 property Item companionTo: companionBase ? companionBase.seeAllButton : null
458− property Item companionBase: categoryView.expandedCategoryItem
499+ property Item companionBase: null
459500 property bool showBecausePosition: false
460501 property real yOffset: 0
461502
@@ -467,7 +508,7 @@ FocusScope {
467508 height: seeLessLabel.font.pixelSize + units.gu(4)
468509 visible: companionTo && showBecausePosition
469510
470− onClicked: categoryView.expandedCategoryItem = null;
511+ onClicked: categoryView.expandedCategoryId = "";
471512
472513 function updateVisibility() {
473514 var companionPos = companionTo.mapToItem(floatingSeeLess, 0, 0);
@@ -476,6 +517,10 @@ FocusScope {
476517 var posToBase = floatingSeeLess.mapToItem(companionBase, 0, -yOffset).y;
477518 yOffset = Math.max(0, companionBase.item.collapsedHeight - posToBase);
478519 yOffset = Math.min(yOffset, height);
520+
521+ if (!showBecausePosition && categoryView.expandedCategoryId === "") {
522+ companionBase = null;
523+ }
479524 }
480525
481526 Label {
tests/mocks/Unity/fake_categories.cpp+95−136
@@ -29,25 +29,12 @@ Categories::Categories(int category_count, QObject* parent)
2929
3030int Categories::rowCount(const QModelIndex& /*parent*/) const
3131{
32− return m_category_count + m_specialCategories.count();
32+ return m_category_count;
3333}
3434
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*/)
3636{
37− CategoryData catData;
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− }
37+ qFatal("Using un-implemented Categories::addSpecialCategory");
5138}
5239
5340bool Categories::overrideCategoryJson(QString const& /* categoryId */, QString const& /* json */)
@@ -55,23 +42,6 @@ bool Categories::overrideCategoryJson(QString const& /* categoryId */, QString c
5542 qFatal("Using un-implemented Categories::overrideCategoryJson");
5643}
5744
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−
7545QVariant
7646Categories::data(const QModelIndex& index, int role) const
7747{
@@ -79,115 +49,74 @@ Categories::data(const QModelIndex& index, int role) const
7949 return QVariant();
8050 }
8151
82− ResultsModel *resultsModel = m_resultsModels[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) {
90− case RoleCategoryId:
91− return catData.categoryId;
92− case RoleName:
93− return catData.name;
94− case RoleIcon:
95− return catData.icon;
96− case RoleRawRendererTemplate:
97− return catData.rawTemplate;
98− case RoleRenderer:
99− {
100− QVariantMap map;
101− if (index.row() % 2 == 0) {
102− map["category-layout"] = "grid";
103− } else {
104− map["category-layout"] = "carousel";
105− map["overlay"] = true;
106− }
107− map["card-size"] = "small";
108− return map;
52+ ResultsModel *resultsModel = resultModel(index.row());
53+ switch (role) {
54+ case RoleCategoryId:
55+ return QString("%1").arg(index.row());
56+ case RoleName:
57+ return QString("Category %1").arg(index.row());
58+ case RoleIcon:
59+ return "gtk-apply";
60+ case RoleRawRendererTemplate:
61+ qFatal("Using un-implemented RoleRawRendererTemplate Categories role");
62+ return QVariant();
63+ case RoleRenderer:
64+ {
65+ QVariantMap map;
66+ if (index.row() % 2 == 0) {
67+ map["category-layout"] = "grid";
68+ } else {
69+ map["category-layout"] = "carousel";
10970 }
110− case RoleComponents:
111− {
112− QVariantMap map, artMap, attributeMap;
113− artMap["aspect-ratio"] = "1.0";
114− artMap["field"] = "art";
115− map["art"] = artMap;
116− map["title"] = "HOLA";
117− attributeMap["field"] = "attribute";
118− map["attributes"] = attributeMap;
119− return map;
71+ if (index.row() == 18) {
72+ map["category-layout"] = "horizontal-list";
73+ }
74+ if (index.row() == 19) {
75+ map["category-layout"] = "grid";
76+ map["collapsed-rows"] = 0;
77+ }
78+ map["card-size"] = "small";
79+
80+ map["category-layout"] = m_layouts.value(index.row(), map["category-layout"].toString());
81+
82+ if (map["category-layout"] == "carousel") {
83+ map["overlay"] = true;
12084 }
121− case RoleHeaderLink:
122− return QString();
123− case RoleResults:
124− return QVariant();
125− case RoleCount:
126− return catData.countObject->property("count");
127− default:
128− qFatal("Using un-implemented Categories role");
129− return QVariant();
85+
86+ return map;
13087 }
131− } else {
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− {
144− QVariantMap map;
145− if (index.row() % 2 == 0) {
146− map["category-layout"] = "grid";
147− } else {
148− map["category-layout"] = "carousel";
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;
88+ case RoleComponents:
89+ {
90+ QVariantMap map, artMap, attributeMap;
91+ if (index.row() % 2 != 0) {
92+ artMap["aspect-ratio"] = QString("1.%1").arg(index.row());
93+ } else {
94+ artMap["aspect-ratio"] = "1.0";
16195 }
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";
171− map["art"] = artMap;
172− map["title"] = "HOLA";
173− map["subtitle"] = "HOLA";
174− attributeMap["field"] = "attribute";
175− map["attributes"] = attributeMap;
176− return map;
96+ artMap["field"] = "art";
97+ map["art"] = artMap;
98+ map["title"] = "HOLA";
99+ map["subtitle"] = "HOLA";
100+ attributeMap["field"] = "attribute";
101+ map["attributes"] = attributeMap;
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");
177109 }
178− case RoleHeaderLink:
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();
110+ res = m_headerLinks.value(index.row(), res);
111+ return res;
190112 }
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();
191120 }
192121}
193122
@@ -196,3 +125,33 @@ Categories::data(int row, int role) const
196125{
197126 return data(index(row, 0), role);
198127}
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:
3939
4040 Q_INVOKABLE QVariant data(int row, int role) const;
4141
42−private Q_SLOTS:
43− void countChanged();
42+ // For testing purposes
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);
4447
4548private:
4649 mutable QHash<int, ResultsModel*> m_resultsModels;
50+ QHash<int, QString> m_layouts;
51+ QHash<int, QString> m_headerLinks;
4752 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;
5853};
5954
6055#endif // FAKE_CATEGORIES_H
tests/mocks/Unity/fake_resultsmodel.cpp+9
@@ -44,6 +44,15 @@ void ResultsModel::setCategoryId(QString const& /*id*/)
4444 qFatal("Calling un-implemented ResultsModel::setCategoryId");
4545}
4646
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+
4756int ResultsModel::rowCount(const QModelIndex& parent) const
4857{
4958 Q_UNUSED(parent);
tests/mocks/Unity/fake_resultsmodel.h+3
@@ -41,6 +41,9 @@ public:
4141 /* setters */
4242 void setCategoryId(QString const& id) override;
4343
44+ // For testing purposes
45+ Q_INVOKABLE void setResultCount(int result_count);
46+
4447private:
4548 int m_result_count;
4649 int m_categoryId;
tests/mocks/Unity/fake_scope.cpp+17
@@ -145,6 +145,23 @@ void Scope::setFavorite(const bool favorite)
145145 Q_EMIT favoriteChanged();
146146 }
147147}
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+
148165void Scope::setSearchInProgress(const bool inProg)
149166{
150167 if (inProg != m_searching) {
tests/mocks/Unity/fake_scope.h+2
@@ -56,6 +56,8 @@ public:
5656 void setFormFactor(const QString& form_factor) override;
5757 void setActive(const bool) override;
5858 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
5961 Q_INVOKABLE void setSearchInProgress(const bool inProg); // This is not invokable in the Interface, here for testing benefits
6062
6163 Q_INVOKABLE void activate(QVariant const& result) override;
tests/qmltests/Dash/tst_GenericScopeView.qml+97−1
@@ -1,5 +1,5 @@
11/*
2− * Copyright 2013 Canonical Ltd.
2+ * Copyright 2014 Canonical Ltd.
33 *
44 * This program is free software; you can redistribute it and/or modify
55 * it under the terms of the GNU General Public License as published by
@@ -43,6 +43,10 @@ Item {
4343 onLoadedChanged: if (loaded) genericScopeView.scope = scopes.getScope(2);
4444 }
4545
46+ MockScope {
47+ id: mockScope
48+ }
49+
4650 SignalSpy {
4751 id: spy
4852 }
@@ -92,6 +96,17 @@ Item {
9296 return findChild(genericScopeView, category);
9397 }
9498
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+
95110 function test_isActive() {
96111 tryCompare(genericScopeView.scope, "isActive", false)
97112 genericScopeView.isCurrent = true
@@ -426,6 +441,87 @@ Item {
426441 compare(image.source, data.logo, "Title image has the wrong source");
427442 }
428443
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+
429525 function test_favorite_data() {
430526 return [
431527 { tag: "People", id: "MockScope1", favorite: true },