Merge lp:~stolowski/unity-scope-click/two-columns-layout into lp:unity-scope-click

Proposed by Paweł Stołowski
Status: Merged
Approved by: dobey
Approved revision: 415
Merged at revision: 407
Proposed branch: lp:~stolowski/unity-scope-click/two-columns-layout
Merge into: lp:unity-scope-click
Diff against target: 451 lines (+238/-12)
3 files modified
libclickscope/click/preview.cpp (+121/-10)
libclickscope/click/preview.h (+38/-1)
libclickscope/tests/test_preview.cpp (+79/-1)
To merge this branch: bzr merge lp:~stolowski/unity-scope-click/two-columns-layout
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
dobey (community) Approve
Review via email: mp+283670@code.launchpad.net

Commit message

Support two column layouts in the previews.

Description of the change

Support two column layouts in the previews.

To post a comment you must log in.
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
410. By Paweł Stołowski

Fixed InstallingPreview

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

I'm a bit worried that we have multiple new methods being added here, and some changes to some previews, but there are no changes to the tests. Can we get some tests added for the new methods, and tests that ensure the correct version of the pushPackagePreviewWidgets() method is called for the different preview types, perhaps?

review: Needs Fixing
411. By Paweł Stołowski

pushWidgets method not needed after all

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
412. By Paweł Stołowski

Check that register_layout is called

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
413. By Paweł Stołowski

Basic check for widget caching

414. By Paweł Stołowski

Real test for column layout and cache

415. By Paweł Stołowski

Match preview widget ids too

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Paweł Stołowski (stolowski) wrote :

> I'm a bit worried that we have multiple new methods being added here, and some
> changes to some previews, but there are no changes to the tests. Can we get
> some tests added for the new methods, and tests that ensure the correct
> version of the pushPackagePreviewWidgets() method is called for the different
> preview types, perhaps?

Ok, I added some tests, mostly for the new pushPackagePreviewWidgets to confirm it does the right thing, plus also added simple checks for calls to register_layout in other two existing tests.

The best test would be to just set call expectations on register_layout() and push() of the reply objects, but that's not possible without spendings tons of time on enhancing current tests. The problem is it's not currently easily possible with InstalledPreview test, because of the entire "run with qt" story in InstalledPreview::run (multiple calls to run various Qt tasks there).

Revision history for this message
dobey (dobey) wrote :

Looks ok.

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

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'libclickscope/click/preview.cpp'
2--- libclickscope/click/preview.cpp 2015-11-24 18:23:23 +0000
3+++ libclickscope/click/preview.cpp 2016-01-28 14:37:47 +0000
4@@ -45,6 +45,7 @@
5 #include <unity/scopes/PreviewReply.h>
6 #include <unity/scopes/Variant.h>
7 #include <unity/scopes/VariantBuilder.h>
8+#include <unity/scopes/ColumnLayout.h>
9
10 #include <QDebug>
11
12@@ -56,6 +57,70 @@
13
14 namespace click {
15
16+void CachedPreviewWidgets::push(unity::scopes::PreviewWidget const &widget)
17+{
18+ widgets.push_back(widget);
19+ widgets_lookup.insert(widget.id());
20+}
21+
22+void CachedPreviewWidgets::push(unity::scopes::PreviewWidgetList const &widgetList)
23+{
24+ for (auto const& widget: widgetList)
25+ {
26+ push(widget);
27+ }
28+}
29+
30+void CachedPreviewWidgets::flush(unity::scopes::PreviewReplyProxy const& reply)
31+{
32+ // A safeguard: if a new widget gets added but missing in the layout, we will get a warning
33+ // in the logs and layouts will not be registered (single column with all widgets will be used).
34+ if (widgets.size() != layout.singleColumn.column1.size() ||
35+ widgets.size() != layout.twoColumns.column1.size() + layout.twoColumns.column2.size())
36+ {
37+ qWarning() << "Number of column layouts doesn't match the number of widgets";
38+ }
39+ else
40+ {
41+ layout.registerLayouts(reply);
42+ }
43+ reply->push(widgets);
44+ widgets.clear();
45+ widgets_lookup.clear();
46+}
47+
48+bool CachedPreviewWidgets::has(std::string const& widget) const
49+{
50+ return widgets_lookup.find(widget) != widgets_lookup.end();
51+}
52+
53+void WidgetsInColumns::registerLayouts(unity::scopes::PreviewReplyProxy const& reply)
54+{
55+ unity::scopes::ColumnLayout layout1col(1);
56+ layout1col.add_column(singleColumn.column1);
57+
58+ unity::scopes::ColumnLayout layout2col(2);
59+ layout2col.add_column(twoColumns.column1);
60+ layout2col.add_column(twoColumns.column2);
61+
62+ try
63+ {
64+ reply->register_layout({layout1col, layout2col});
65+ }
66+ catch (unity::LogicException const& e)
67+ {
68+ qWarning() << "Failed to register layout:" << QString::fromStdString(e.what());
69+ }
70+}
71+
72+void WidgetsInColumns::appendToColumn(std::vector<std::string>& column, unity::scopes::PreviewWidgetList const& widgets)
73+{
74+ for (auto const& widget: widgets)
75+ {
76+ column.push_back(widget.id());
77+ }
78+}
79+
80 DepartmentUpdater::DepartmentUpdater(const std::shared_ptr<click::DepartmentsDb>& depts)
81 : depts(depts)
82 {
83@@ -237,6 +302,39 @@
84 reply->push(descriptionWidgets(details));
85 }
86
87+void PreviewStrategy::pushPackagePreviewWidgets(CachedPreviewWidgets &cache,
88+ const PackageDetails& details,
89+ const scopes::PreviewWidgetList& button_area_widgets)
90+{
91+ cache.push(headerWidgets(details));
92+ cache.layout.singleColumn.column1.push_back("hdr");
93+ cache.layout.twoColumns.column1.push_back("hdr");
94+
95+ cache.push(button_area_widgets);
96+ cache.layout.appendToColumn(cache.layout.singleColumn.column1, button_area_widgets);
97+ cache.layout.appendToColumn(cache.layout.twoColumns.column1, button_area_widgets);
98+
99+ auto const screenshots = screenshotsWidgets(details);
100+ cache.push(screenshots);
101+ cache.layout.appendToColumn(cache.layout.singleColumn.column1, screenshots);
102+ cache.layout.appendToColumn(cache.layout.twoColumns.column1, screenshots);
103+
104+ auto descr = descriptionWidgets(details);
105+ if (!descr.empty())
106+ {
107+ cache.push(descr);
108+ cache.layout.appendToColumn(cache.layout.singleColumn.column1, descr);
109+
110+ // for two-columns we need to split the widgets, summary goes to 1st column, everything else to 2nd
111+ if (descr.front().id() == "summary")
112+ {
113+ descr.pop_front();
114+ cache.layout.twoColumns.column1.push_back("summary");
115+ }
116+ cache.layout.appendToColumn(cache.layout.twoColumns.column2, descr);
117+ }
118+}
119+
120 PreviewStrategy::~PreviewStrategy()
121 {
122 }
123@@ -463,7 +561,7 @@
124 {
125 scopes::PreviewWidgetList widgets;
126
127- scopes::PreviewWidget rating("summary", "reviews");
128+ scopes::PreviewWidget rating("reviews", "reviews");
129 scopes::VariantBuilder builder;
130
131 if (reviewlist.size() > 0) {
132@@ -647,7 +745,7 @@
133 if (login_error) {
134 reply->push(loginErrorWidgets(details));
135 } else {
136- pushPackagePreviewWidgets(reply, details, progressBarWidget(object_path));
137+ pushPackagePreviewWidgets(cachedWidgets, details, progressBarWidget(object_path));
138 startLauncherAnimation(details);
139 }
140 },
141@@ -655,11 +753,15 @@
142 click::Reviews::Error error) {
143 if (!login_error) {
144 if (error == click::Reviews::Error::NoError) {
145- reply->push(reviewsWidgets(reviewlist));
146+ auto const revs = reviewsWidgets(reviewlist);
147+ cachedWidgets.push(revs);
148+ cachedWidgets.layout.appendToColumn(cachedWidgets.layout.singleColumn.column1, revs);
149+ cachedWidgets.layout.appendToColumn(cachedWidgets.layout.twoColumns.column1, revs);
150 } else {
151 qDebug() << "There was an error getting reviews for:" << result["name"].get_string().c_str();
152 }
153 }
154+ cachedWidgets.flush(reply);
155 reply->finished();
156 });
157 break;
158@@ -732,7 +834,7 @@
159 review.rating = 0;
160 std::string widget_id;
161 // We use a try/catch here, as scope_data() can be a dict, but not have
162- // the values we need, which will result in an exception thrown.
163+ // the values we need, which will result in an exception thrown.
164 try {
165 auto metadict = metadata.scope_data().get_dict();
166 review.rating = metadict["rating"].get_int();
167@@ -801,7 +903,7 @@
168 getApplicationUri(manifest, [this, reply, manifest, app_name, &review, userid](const std::string& uri) {
169 populateDetails([this, reply, uri, manifest, app_name](const PackageDetails &details){
170 store_department(details);
171- pushPackagePreviewWidgets(reply, details, createButtons(uri, manifest));
172+ pushPackagePreviewWidgets(cachedWidgets, details, createButtons(uri, manifest));
173 },
174 [this, reply, &review, manifest, userid](const ReviewList& reviewlist,
175 click::Reviews::Error error) {
176@@ -828,13 +930,19 @@
177 review_input.push_back(rating);
178 }
179 }
180- reply->push(review_input);
181+ cachedWidgets.push(review_input);
182+ cachedWidgets.layout.appendToColumn(cachedWidgets.layout.singleColumn.column1, review_input);
183+ cachedWidgets.layout.appendToColumn(cachedWidgets.layout.twoColumns.column1, review_input);
184
185 if (error == click::Reviews::Error::NoError) {
186- reply->push(reviewsWidgets(reviews));
187+ auto const revs = reviewsWidgets(reviews);
188+ cachedWidgets.push(revs);
189+ cachedWidgets.layout.appendToColumn(cachedWidgets.layout.singleColumn.column1, revs);
190+ cachedWidgets.layout.appendToColumn(cachedWidgets.layout.twoColumns.column1, revs);
191 } else {
192 qDebug() << "There was an error getting reviews for:" << result["name"].get_string().c_str();
193 }
194+ cachedWidgets.flush(reply);
195 reply->finished();
196 });
197 });
198@@ -972,7 +1080,6 @@
199 });
200 }
201
202-
203 scopes::PreviewWidgetList PurchasingPreview::purchasingWidgets(const PackageDetails &/*details*/)
204 {
205 scopes::PreviewWidgetList widgets;
206@@ -1130,14 +1237,18 @@
207 button_widgets = progressBarWidget(found_object_path);
208 }
209 qDebug() << "Pushed button action widgets.";
210- pushPackagePreviewWidgets(reply, found_details, button_widgets);
211+ pushPackagePreviewWidgets(cachedWidgets, found_details, button_widgets);
212 qDebug() << "Pushed package details widgets.";
213 if (reviewserror == click::Reviews::Error::NoError) {
214 qDebug() << "Pushing reviews widgets.";
215- reply->push(reviewsWidgets(reviewlist));
216+ auto const revs = reviewsWidgets(reviewlist);
217+ cachedWidgets.push(revs);
218+ cachedWidgets.layout.appendToColumn(cachedWidgets.layout.singleColumn.column1, revs);
219+ cachedWidgets.layout.appendToColumn(cachedWidgets.layout.twoColumns.column1, revs);
220 } else {
221 qDebug() << "There was an error getting reviews for:" << result["name"].get_string().c_str();
222 }
223+ cachedWidgets.flush(reply);
224 reply->finished();
225 qDebug() << "---------- Finished reply for:" << result["name"].get_string().c_str();
226 });
227
228=== modified file 'libclickscope/click/preview.h'
229--- libclickscope/click/preview.h 2015-11-24 18:23:23 +0000
230+++ libclickscope/click/preview.h 2016-01-28 14:37:47 +0000
231@@ -45,6 +45,7 @@
232 #include <unity/scopes/Result.h>
233 #include <unity/scopes/ScopeBase.h>
234 #include <unity/util/DefinesPtrs.h>
235+#include <set>
236
237 namespace scopes = unity::scopes;
238
239@@ -54,6 +55,35 @@
240 class PreviewStrategy;
241 class DepartmentsDb;
242
243+struct WidgetsInColumns
244+{
245+ struct {
246+ std::vector<std::string> column1;
247+ } singleColumn;
248+ struct {
249+ std::vector<std::string> column1;
250+ std::vector<std::string> column2;
251+ } twoColumns;
252+
253+ void registerLayouts(unity::scopes::PreviewReplyProxy const& reply);
254+ void appendToColumn(std::vector<std::string>& column, unity::scopes::PreviewWidgetList const& widgets);
255+};
256+
257+class CachedPreviewWidgets
258+{
259+public:
260+ void push(unity::scopes::PreviewWidget const &widget);
261+ void push(unity::scopes::PreviewWidgetList const &widgetList);
262+ void flush(unity::scopes::PreviewReplyProxy const& reply);
263+ bool has(std::string const& widget) const;
264+
265+ WidgetsInColumns layout;
266+
267+private:
268+ std::list<unity::scopes::PreviewWidget> widgets;
269+ std::unordered_set<std::string> widgets_lookup;
270+};
271+
272 class DepartmentUpdater
273 {
274 protected:
275@@ -137,6 +167,10 @@
276
277 virtual void cancelled();
278 virtual void run(unity::scopes::PreviewReplyProxy const& reply) = 0;
279+ virtual void pushPackagePreviewWidgets(CachedPreviewWidgets &reply,
280+ const PackageDetails& details,
281+ const scopes::PreviewWidgetList& button_area_widgets);
282+
283 protected:
284 virtual void populateDetails(std::function<void(const PackageDetails &)> details_callback,
285 std::function<void(const click::ReviewList&,
286@@ -164,6 +198,7 @@
287 virtual void invalidateScope(const std::string& scope_id);
288
289 scopes::Result result;
290+
291 QSharedPointer<click::web::Client> client;
292 QSharedPointer<click::Index> index;
293 click::web::Cancellable index_operation;
294@@ -205,6 +240,7 @@
295 std::string download_sha512;
296 QSharedPointer<click::Downloader> downloader;
297 std::shared_ptr<click::DepartmentsDb> depts_db;
298+ CachedPreviewWidgets cachedWidgets;
299 void startLauncherAnimation(const PackageDetails& details);
300 };
301
302@@ -228,6 +264,7 @@
303 const click::Manifest& manifest);
304 private:
305 scopes::ActionMetadata metadata;
306+ CachedPreviewWidgets cachedWidgets;
307 };
308
309 class InstalledScopePreview : public PreviewStrategy
310@@ -272,7 +309,6 @@
311 virtual ~UninstallConfirmationPreview();
312
313 void run(unity::scopes::PreviewReplyProxy const& reply) override;
314-
315 };
316
317 class UninstalledPreview : public PreviewStrategy, public DepartmentUpdater
318@@ -290,6 +326,7 @@
319 void run(unity::scopes::PreviewReplyProxy const& reply) override;
320 protected:
321 PackageDetails found_details;
322+ CachedPreviewWidgets cachedWidgets;
323 std::string found_object_path;
324 virtual click::Downloader* get_downloader(const QSharedPointer<click::network::AccessManager>& nam);
325 virtual scopes::PreviewWidgetList uninstalledActionButtonWidgets(const PackageDetails &details);
326
327=== modified file 'libclickscope/tests/test_preview.cpp'
328--- libclickscope/tests/test_preview.cpp 2015-11-24 18:23:23 +0000
329+++ libclickscope/tests/test_preview.cpp 2016-01-28 14:37:47 +0000
330@@ -42,6 +42,7 @@
331 #include <boost/locale/time_zone.hpp>
332
333 using namespace ::testing;
334+using ::testing::Matcher;
335 using namespace unity::scopes;
336
337 class FakeResult : public Result
338@@ -158,6 +159,81 @@
339
340 }
341
342+MATCHER_P(PreviewWidgetsListMatchers, widgets, "") {
343+ if (arg.size() == widgets.size()) {
344+ auto it1 = widgets.begin();
345+ auto it2 = arg.begin();
346+ while (*it1 != it2->id()) {
347+ *result_listener << "Preview widgets don't match: " << it2->id() << ", expected " << *it1;
348+ return false;
349+ }
350+ return true;
351+ }
352+ *result_listener << "Preview widgets list don't match: ";
353+ for (auto const& widget: arg) {
354+ *result_listener << widget.id() << ", ";
355+ }
356+ *result_listener << "expected: ";
357+ for (auto const& widget: widgets) {
358+ *result_listener << widget << ", ";
359+ }
360+ return false;
361+}
362+
363+MATCHER_P(LayoutMatches, layouts, "") {
364+ if (arg.size() != layouts.size()) {
365+ *result_listener << "Layout lists sizes don't match, " << arg.size() << " vs " << layouts.size();
366+ return false;
367+ }
368+ auto it1 = layouts.begin();
369+ auto it2 = arg.begin();
370+ while (it1 != layouts.end()) {
371+ if (it1->serialize() != it2->serialize()) {
372+ *result_listener << "Layouts don't match: " << unity::scopes::Variant(it1->serialize()).serialize_json() << " vs "
373+ << unity::scopes::Variant(it2->serialize()).serialize_json();
374+ return false;
375+ }
376+ it1++;
377+ it2++;
378+ }
379+ return true;
380+}
381+
382+TEST_F(PreviewStrategyTest, testPushCachedWidgets)
383+{
384+ unity::scopes::testing::MockPreviewReply reply;
385+ std::shared_ptr<unity::scopes::testing::MockPreviewReply> replyptr{&reply, [](unity::scopes::testing::MockPreviewReply*){}};
386+
387+ FakeResult result{vm};
388+ FakePreview preview{result};
389+ click::PackageDetails details;
390+ details.main_screenshot_url = "sshot1";
391+ details.license = "GPL";
392+ details.company_name = "Ubuntu";
393+ details.website = "http://ubuntu.com";
394+ details.changelog = "Foo";
395+ details.version = "0.1";
396+ details.download_url = "http://ubuntu.com/";
397+ details.description = "Foo";
398+
399+ click::CachedPreviewWidgets cache;
400+ scopes::PreviewWidget buttons("buttons", "actions");
401+
402+ unity::scopes::ColumnLayout single_column(1);
403+ single_column.add_column({"hdr","buttons","screenshots","summary","other_metadata","updates_table","whats_new"});
404+ unity::scopes::ColumnLayout two_columns(2);
405+ two_columns.add_column({"hdr","buttons","screenshots","summary"});
406+ two_columns.add_column({"other_metadata","updates_table","whats_new"});
407+ unity::scopes::ColumnLayoutList expected_layout {single_column, two_columns};
408+
409+ std::vector<std::string> expected_widgets {"hdr", "buttons", "screenshots", "summary", "other_metadata", "updates_table", "whats_new"};
410+
411+ EXPECT_CALL(*replyptr, register_layout(Matcher<unity::scopes::ColumnLayoutList const&>(LayoutMatches(expected_layout))));
412+ EXPECT_CALL(*replyptr, push(Matcher<unity::scopes::PreviewWidgetList const&>(PreviewWidgetsListMatchers(expected_widgets))));
413+ preview.pushPackagePreviewWidgets(cache, details, {buttons});
414+ cache.flush(replyptr);
415+}
416+
417 class PreviewStrategyDescriptionTest : public PreviewStrategyTest
418 {
419 public:
420@@ -391,6 +467,7 @@
421 EXPECT_CALL(preview, progressBarWidget(_))
422 .Times(1)
423 .WillOnce(Return(response));
424+ EXPECT_CALL(*replyptr, register_layout(_));
425 preview.run(replyptr);
426 preview.fake_downloader->activate_callback();
427 }
428@@ -404,6 +481,7 @@
429 EXPECT_CALL(preview, uninstalledActionButtonWidgets(_))
430 .Times(1)
431 .WillOnce(Return(response));
432+ EXPECT_CALL(*replyptr, register_layout(_));
433 preview.run(replyptr);
434 preview.fake_downloader->activate_callback();
435 }
436@@ -483,6 +561,7 @@
437 : click::InstalledPreview(result, metadata, client, pay_package, depts) {
438
439 }
440+
441 using click::InstalledPreview::createButtons;
442 MOCK_METHOD0(isRefundable, bool());
443 };
444@@ -520,7 +599,6 @@
445 ASSERT_EQ(get_action_from_widgets(widgets, 0, 1), "uninstall_click");
446 }
447
448-
449 class FakeCancelPurchasePreview : public click::CancelPurchasePreview {
450 public:
451 FakeCancelPurchasePreview(const unity::scopes::Result& result, bool installed)

Subscribers

People subscribed via source and target branches