Merge lp:~saviq/unity8/new-scopes-integrate-card into lp:~unity-team/unity8/new-scopes

Proposed by Michał Sawicz
Status: Merged
Approved by: Michael Zanetti
Approved revision: 572
Merged at revision: 562
Proposed branch: lp:~saviq/unity8/new-scopes-integrate-card
Merge into: lp:~unity-team/unity8/new-scopes
Diff against target: 762 lines (+678/-1)
10 files modified
Dash/Card.qml (+66/-0)
Dash/CardFilterGrid.qml (+44/-0)
Dash/CardHeader.qml (+96/-0)
Dash/DashFilterGrid.qml (+2/-0)
Dash/DashRenderer.qml (+6/-0)
Dash/GenericScopeView.qml (+4/-1)
tests/qmltests/CMakeLists.txt (+2/-0)
tests/qmltests/Dash/CardHelpers.js (+59/-0)
tests/qmltests/Dash/tst_Card.qml (+278/-0)
tests/qmltests/Dash/tst_CardHeader.qml (+121/-0)
To merge this branch: bzr merge lp:~saviq/unity8/new-scopes-integrate-card
Reviewer Review Type Date Requested Status
Michael Zanetti (community) Approve
Michal Hruby (community) Needs Fixing
Review via email: mp+197930@code.launchpad.net

Commit message

Introduce the Card and integrate with the new plugin.

To post a comment you must log in.
Revision history for this message
Michael Zanetti (mzanetti) wrote :

40 + //sourceSize.width: width > height ? width : 0
41 + //sourceSize.height: height > width ? height : -1

Why commented? Why 0 and -1?

=====

Can we align avatar/mascot?

====
161 + width: parent.width - x - row.spacing

I don't think subtracting the spacing is needed as x will take into account the spacing at the left of the row and width would take into account the margins on the right (unless you really want to have 2*spacing on the right)

===
195 + width: parent.labelWidth;

some lost semicolons (on each price label)

===
292 +function tryParse(json, errorLabel) {

Actually I'm wondering if it wouldn't make more sense for the scope to ship a QVariantMap instead of a JSON string as I think QJsonDocument would parse it much faster than JavaScript

review: Needs Information
Revision history for this message
Michał Sawicz (saviq) wrote :

On 06.12.2013 13:52, Michael Zanetti wrote:
> Review: Needs Information
>
> 40 + //sourceSize.width: width > height ? width : 0
> 41 + //sourceSize.height: height > width ? height : -1
>
> Why commented? Why 0 and -1?

'Cause it crashes, added FIXME to not lose track. Fixed to both being 0.

> =====
>
> Can we align avatar/mascot?

Of course, that was just before we were calling it a mascot...

> ====
> 161 + width: parent.width - x - row.spacing
>
> I don't think subtracting the spacing is needed as x will take into account the spacing at the left of the row and width would take into account the margins on the right (unless you really want to have 2*spacing on the right)

Fixed, tested.

> ===
> 195 + width: parent.labelWidth;
>
> some lost semicolons (on each price label)

Fixed.

> ===
> 292 +function tryParse(json, errorLabel) {
>
> Actually I'm wondering if it wouldn't make more sense for the scope to ship a QVariantMap instead of a JSON string as I think QJsonDocument would parse it much faster than JavaScript

This is only there in the tests, the real implementation gives up
QVariantMaps directly.

--
Michał (Saviq) Sawicz <email address hidden>
Canonical Services Ltd.

564. By Michał Sawicz

Add FIXME for sourceSize.

565. By Michał Sawicz

s/avatar/mascot/

566. By Michał Sawicz

Fix header width.

567. By Michał Sawicz

Use hasOwnProperty instead of != undefined.

568. By Michał Sawicz

;--

Revision history for this message
Michal Hruby (mhr3) wrote :

38 + // FIXME should be no need for "icon"
39 + source: cardData && cardData["art"] || cardData["icon"] || ""

Just remove the "icon" fallback, I'll have a branch soon that will drop "icon" and use "art".

review: Needs Fixing
569. By Michał Sawicz

Get rid of FIXME for icon vs. art

Revision history for this message
Michael Zanetti (mzanetti) wrote :

709 + function test_labels_data() {
710 + return [
711 + { tag: "Empty", visible: false },
712 + { tag: "Title only", title: "Foo", visible: true },
713 + { tag: "Subtitle only", subtitle: "Bar", visible: false },
714 + { tag: "Both", title: "Foo", subtitle: "Bar", visible: true }
715 + ]
716 + }
717 +
718 + function test_labels(data) {
719 + cardHeader.title = data.title !== undefined ? data.title : "";
720 + cardHeader.subtitle = data.subtitle !== undefined ? data.subtitle : "";
721 + tryCompare(cardHeader, "visible", data.visible);
722 + if (data.hasOwnProperty("maxLineCount")) {
723 + compare(testCase.titleLabel.maximumLineCount, data.maxLineCount, "titleLabel maximumLineCount should be %1".arg(data.maxLineCount));
724 + }
725 + }

hmm... is this something you started but didn't finish? there doesn't seem to be any data with maxLineCount. Should we add some? If not, drop this check?

review: Needs Information
Revision history for this message
Michael Zanetti (mzanetti) wrote :

Got a test failure here.... fuzzyCompare() needed?

FAIL! : qmltestrunner::test_art_size(Wide) property height
   Actual (): 166.5
   Expected (): 167
   Loc: [/home/mzanetti/Development/reviews/new-scopes-integrate-card/tests/qmltests/Dash/tst_Card.qml(270)]

review: Needs Fixing
570. By Michał Sawicz

No dynamic maxLineCount.

571. By Michał Sawicz

Do not use quarters of GUs.

Revision history for this message
Michał Sawicz (saviq) wrote :

On 09.12.2013 17:21, Michael Zanetti wrote:
> hmm... is this something you started but didn't finish? there doesn't seem to be any data with maxLineCount. Should we add some? If not, drop this check?

Right, this stopped being dynamic since... Removed.

> Got a test failure here.... fuzzyCompare() needed?
>
>
> FAIL! : qmltestrunner::test_art_size(Wide) property height
> Actual (): 166.5
> Expected (): 167
> Loc: [/home/mzanetti/Development/reviews/new-scopes-integrate-card/tests/qmltests/Dash/tst_Card.qml(270)]

Nah, I just need to go for integer values there. Fixed.

--
Michał (Saviq) Sawicz <email address hidden>
Canonical Services Ltd.

Revision history for this message
Michael Zanetti (mzanetti) wrote :

Ok. other one is gone. but here's another:

FAIL! : qmltestrunner::test_art_size(Wide) property height
   Actual (): 342
   Expected (): 333
   Loc: [/home/mzanetti/Development/reviews/new-scopes-integrate-card/tests/qmltests/Dash/tst_Card.qml(270)]
Segmentation fault (core dumped)

review: Needs Fixing
572. By Michał Sawicz

38/2 != 18.5, 38/2 == 19 !!

Revision history for this message
Michael Zanetti (mzanetti) wrote :

oki doki

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'Dash/Card.qml'
2--- Dash/Card.qml 1970-01-01 00:00:00 +0000
3+++ Dash/Card.qml 2013-12-10 09:30:53 +0000
4@@ -0,0 +1,66 @@
5+import QtQuick 2.0
6+import Ubuntu.Components 0.1
7+
8+Item {
9+ id: root
10+ property var template
11+ property var components
12+ property var cardData
13+
14+ width: {
15+ if (template !== undefined) {
16+ switch (template['card-size']) {
17+ case "small": return units.gu(12);
18+ case "large": return units.gu(38);
19+ }
20+ }
21+ return units.gu(18.5);
22+ }
23+ height: childrenRect.height
24+
25+ UbuntuShape {
26+ id: artShape
27+ objectName: "artShape"
28+ width: image.fillMode === Image.PreserveAspectCrop || aspect < image.aspect ? image.width : height * image.aspect
29+ height: image.fillMode === Image.PreserveAspectCrop || aspect > image.aspect ? image.height : width / image.aspect
30+ anchors.horizontalCenter: parent.horizontalCenter
31+
32+ property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1
33+
34+ image: Image {
35+ width: root.width
36+ height: width / artShape.aspect
37+ objectName: "artImage"
38+ source: cardData && cardData["art"] || ""
39+ // FIXME uncomment when having investigated / fixed the crash
40+ //sourceSize.width: width > height ? width : 0
41+ //sourceSize.height: height > width ? height : 0
42+ fillMode: components["art"]["fill-mode"] == "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
43+
44+ property real aspect: implicitWidth / implicitHeight
45+ }
46+ }
47+
48+ CardHeader {
49+ id: header
50+ objectName: "cardHeader"
51+ anchors {
52+ top: artShape.bottom
53+ left: parent.left
54+ right: parent.right
55+ }
56+
57+ mascot: cardData && cardData["mascot"] || ""
58+ title: cardData && cardData["title"] || ""
59+ subtitle: cardData && cardData["subtitle"] || ""
60+ }
61+
62+ Label {
63+ objectName: "summaryLabel"
64+ anchors { top: header.bottom; left: parent.left; right: parent.right }
65+ wrapMode: Text.Wrap
66+ maximumLineCount: 5
67+ elide: Text.ElideRight
68+ text: cardData && cardData["summary"] || ""
69+ }
70+}
71
72=== added file 'Dash/CardFilterGrid.qml'
73--- Dash/CardFilterGrid.qml 1970-01-01 00:00:00 +0000
74+++ Dash/CardFilterGrid.qml 2013-12-10 09:30:53 +0000
75@@ -0,0 +1,44 @@
76+/*
77+ * Copyright (C) 2013 Canonical, Ltd.
78+ *
79+ * This program is free software; you can redistribute it and/or modify
80+ * it under the terms of the GNU General Public License as published by
81+ * the Free Software Foundation; version 3.
82+ *
83+ * This program is distributed in the hope that it will be useful,
84+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
85+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
86+ * GNU General Public License for more details.
87+ *
88+ * You should have received a copy of the GNU General Public License
89+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
90+ */
91+
92+import QtQuick 2.0
93+import Ubuntu.Components 0.1
94+
95+DashFilterGrid {
96+ id: genericFilterGrid
97+
98+ minimumHorizontalSpacing: units.gu(0.5)
99+ // FIXME calculate the size correctly
100+ delegateWidth: grid.currentItem.width
101+ delegateHeight: grid.currentItem.height
102+ verticalSpacing: units.gu(2)
103+
104+ delegate: Card {
105+ id: tile
106+ objectName: "delegate" + index
107+ cardData: model
108+ template: genericFilterGrid.template
109+ components: {
110+ "art": {
111+ "aspect-ratio": 1.0,
112+ "fill-mode": "crop"
113+ }
114+ }
115+
116+ //onClicked: genericFilterGrid.clicked(index, tile.y)
117+ //onPressAndHold: genericFilterGrid.pressAndHold(index, tile.y)
118+ }
119+}
120
121=== added file 'Dash/CardHeader.qml'
122--- Dash/CardHeader.qml 1970-01-01 00:00:00 +0000
123+++ Dash/CardHeader.qml 2013-12-10 09:30:53 +0000
124@@ -0,0 +1,96 @@
125+import QtQuick 2.0
126+import Ubuntu.Components 0.1
127+
128+Item {
129+ id: root
130+ property alias mascot: mascotImage.source
131+ property alias title: titleLabel.text
132+ property alias subtitle: subtitleLabel.text
133+ property alias price: priceLabel.text
134+ property alias oldPrice: oldPriceLabel.text
135+ property alias altPrice: altPriceLabel.text
136+
137+ visible: mascotImage.status === Image.Ready || title || price
138+ height: row.height > 0 ? row.height + row.spacing * 2 : 0
139+
140+ Row {
141+ id: row
142+ objectName: "outerRow"
143+
144+ anchors { top: parent.top; left: parent.left; right: parent.right; margins: spacing }
145+ spacing: units.gu(1)
146+
147+ UbuntuShape {
148+ id: mascotShape
149+ objectName: "mascotShape"
150+
151+ width: units.gu(8)
152+ height: units.gu(8)
153+ visible: image.status === Image.Ready
154+
155+ image: Image {
156+ id: mascotImage
157+ sourceSize { width: mascotShape.width; height: mascotShape.height }
158+ }
159+ }
160+
161+ Column {
162+ objectName: "column"
163+ width: parent.width - x
164+
165+ Label {
166+ id: titleLabel
167+ anchors { left: parent.left; right: parent.right }
168+ elide: Text.ElideRight
169+ objectName: "titleLabel"
170+ font.weight: Font.DemiBold
171+ wrapMode: Text.Wrap
172+ maximumLineCount: 2
173+ }
174+
175+ Label {
176+ id: subtitleLabel
177+ anchors { left: parent.left; right: parent.right }
178+ elide: Text.ElideRight
179+ visible: titleLabel.text && text
180+ }
181+
182+ Row {
183+ id: prices
184+ objectName: "prices"
185+ anchors { left: parent.left; right: parent.right }
186+
187+ property int labels: {
188+ var labels = 1; // price always visible
189+ if (oldPriceLabel.text !== "") labels += 1;
190+ if (altPriceLabel.text !== "") labels += 1;
191+ return labels;
192+ }
193+ property real labelWidth: width / labels
194+
195+ Label {
196+ id: priceLabel
197+ width: parent.labelWidth
198+ elide: Text.ElideRight
199+ font.weight: Font.DemiBold
200+ color: Theme.palette.selected.foreground
201+ }
202+
203+ Label {
204+ id: oldPriceLabel
205+ objectName: "oldPriceLabel"
206+ width: parent.labelWidth
207+ elide: Text.ElideRight
208+ horizontalAlignment: parent.labels === 3 ? Text.AlignHCenter : Text.AlignRight
209+ }
210+
211+ Label {
212+ id: altPriceLabel
213+ width: parent.labelWidth
214+ elide: Text.ElideRight
215+ horizontalAlignment: Text.AlignRight
216+ }
217+ }
218+ }
219+ }
220+}
221
222=== modified file 'Dash/DashFilterGrid.qml'
223--- Dash/DashFilterGrid.qml 2013-11-26 16:32:47 +0000
224+++ Dash/DashFilterGrid.qml 2013-12-10 09:30:53 +0000
225@@ -29,6 +29,8 @@
226 property alias maximumNumberOfColumns: filterGrid.maximumNumberOfColumns
227 property alias minimumHorizontalSpacing: filterGrid.minimumHorizontalSpacing
228
229+ property FilterGrid grid: filterGrid
230+
231 collapsedHeight: filterGrid.collapsedHeight
232 collapsedRowCount: filterGrid.collapsedRowCount
233 columns: filterGrid.columns
234
235=== modified file 'Dash/DashRenderer.qml'
236--- Dash/DashRenderer.qml 2013-11-26 16:32:47 +0000
237+++ Dash/DashRenderer.qml 2013-12-10 09:30:53 +0000
238@@ -62,4 +62,10 @@
239
240 function startFilterAnimation(filter) {
241 }
242+
243+ /// Category template definition from the scope
244+ property var template
245+
246+ /// Component mapping and configuration from the scope
247+ property var components
248 }
249
250=== modified file 'Dash/GenericScopeView.qml'
251--- Dash/GenericScopeView.qml 2013-12-04 09:46:59 +0000
252+++ Dash/GenericScopeView.qml 2013-12-10 09:30:53 +0000
253@@ -163,6 +163,9 @@
254 }
255 }
256 updateDelegateCreationRange();
257+ // FIXME: should be "template", not "renderer"
258+ item.template = Qt.binding(function() { return model.renderer });
259+ item.components = Qt.binding(function() { return model.components });
260 }
261
262 Component.onDestruction: {
263@@ -340,7 +343,7 @@
264 default: return "Generic/GenericFilterGrid.qml";
265 }
266 }
267- default: return "Generic/GenericFilterGrid.qml";
268+ default: return "CardFilterGrid.qml";
269 }
270 }
271
272
273=== modified file 'tests/qmltests/CMakeLists.txt'
274--- tests/qmltests/CMakeLists.txt 2013-11-30 11:40:30 +0000
275+++ tests/qmltests/CMakeLists.txt 2013-12-10 09:30:53 +0000
276@@ -43,6 +43,8 @@
277 add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
278 add_qml_test(Dash DashPreview)
279 add_qml_test(Dash GenericPreview)
280+add_qml_test(Dash Card)
281+add_qml_test(Dash CardHeader)
282 add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
283 add_qml_test(Dash FilterGrids IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_SOURCE_DIR}/plugins
284 ${CMAKE_BINARY_DIR}/tests/mocks)
285
286=== added file 'tests/qmltests/Dash/CardHelpers.js'
287--- tests/qmltests/Dash/CardHelpers.js 1970-01-01 00:00:00 +0000
288+++ tests/qmltests/Dash/CardHelpers.js 2013-12-10 09:30:53 +0000
289@@ -0,0 +1,59 @@
290+.pragma library
291+
292+var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary"]
293+
294+function tryParse(json, errorLabel) {
295+ var o = undefined;
296+ if (errorLabel !== undefined) {
297+ errorLabel.text = "";
298+ }
299+ try {
300+ o = JSON.parse(json)
301+ } catch(err) {
302+ if (errorLabel !== undefined) {
303+ errorLabel.text = err + "";
304+ } else {
305+ console.debug(err);
306+ }
307+ }
308+ return o;
309+}
310+
311+function mapData(json, layout, errorLabel) {
312+ var o = tryParse(json, errorLabel);
313+ var d = undefined;
314+
315+ if (o !== undefined) {
316+ d = Object();
317+ for (var k in components) {
318+ try {
319+ if (typeof layout[components[k]] == "object") {
320+ d[components[k]] = o[layout[components[k]]['field']];
321+ } else {
322+ d[components[k]] = o[layout[components[k]]];
323+ }
324+ } catch(err) {
325+ d[components[k]] = undefined;
326+ }
327+ }
328+ }
329+ return d;
330+}
331+
332+function update(object, overrides) {
333+ for (var k in overrides) {
334+ if (typeof object[k] == "string" && typeof overrides[k] == "object") {
335+ if (!overrides[k].hasOwnProperty('field')) overrides[k]['field'] = object[k];
336+ }
337+ if (object[k] === null) {
338+ object[k] = overrides[k];
339+ } else if (typeof object[k] == "object" && typeof overrides[k] == "string") {
340+ object[k]['field'] = overrides[k];
341+ } else if (typeof object[k] == "object") {
342+ update(object[k], overrides[k]);
343+ } else {
344+ object[k] = overrides[k];
345+ }
346+ }
347+ return object;
348+}
349
350=== added directory 'tests/qmltests/Dash/artwork'
351=== added file 'tests/qmltests/Dash/artwork/avatar@12.png'
352Binary files tests/qmltests/Dash/artwork/avatar@12.png 1970-01-01 00:00:00 +0000 and tests/qmltests/Dash/artwork/avatar@12.png 2013-12-10 09:30:53 +0000 differ
353=== added file 'tests/qmltests/Dash/artwork/music-player-design.png'
354Binary files tests/qmltests/Dash/artwork/music-player-design.png 1970-01-01 00:00:00 +0000 and tests/qmltests/Dash/artwork/music-player-design.png 2013-12-10 09:30:53 +0000 differ
355=== added file 'tests/qmltests/Dash/tst_Card.qml'
356--- tests/qmltests/Dash/tst_Card.qml 1970-01-01 00:00:00 +0000
357+++ tests/qmltests/Dash/tst_Card.qml 2013-12-10 09:30:53 +0000
358@@ -0,0 +1,278 @@
359+import QtQuick 2.0
360+import QtTest 1.0
361+import Ubuntu.Components 0.1
362+import Unity.Test 0.1 as UT
363+import "../../../Dash"
364+import "CardHelpers.js" as Helpers
365+
366+Rectangle {
367+ id: root
368+ width: units.gu(80)
369+ height: units.gu(72)
370+ color: "#88FFFFFF"
371+
372+ property string defaultLayout: '
373+ {
374+ "schema-version": 1,
375+ "template": {
376+ "category-layout": "grid",
377+ "card-layout": "vertical",
378+ "card-size": "medium",
379+ "overlay-mode": null,
380+ "collapsed-rows": 2
381+ },
382+ "components": {
383+ "title": null,
384+ "art": {
385+ "aspect-ratio": 1.0,
386+ "fill-mode": "crop"
387+ },
388+ "subtitle": null,
389+ "mascot": null,
390+ "emblem": null,
391+ "old-price": null,
392+ "price": null,
393+ "alt-price": null,
394+ "rating": {
395+ "type": "stars",
396+ "range": [0, 5],
397+ "full": "image://theme/rating-star-full",
398+ "half": "image://theme/rating-star-half",
399+ "empty": "image://theme/rating-star-empty"
400+ },
401+ "alt-rating": null,
402+ "summary": null
403+ },
404+ "resources": {}
405+ }'
406+
407+ property string cardData: '
408+ {
409+ "art": "../tests/qmltests/Dash/artwork/music-player-design.png",
410+ "mascot": "../tests/qmltests/Dash/artwork/avatar.png",
411+ "title": "foo",
412+ "subtitle": "bar",
413+ "summary": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
414+ }'
415+
416+ property string fullMapping: '
417+ {
418+ "title": "title",
419+ "art": "art",
420+ "subtitle": "subtitle",
421+ "mascot": "mascot",
422+ "summary": "summary"
423+ }'
424+
425+ property var cardsModel: [
426+ {
427+ "name": "Art, header, summary - vertical",
428+ "layout": { "components": JSON.parse(fullMapping) }
429+ },
430+ {
431+ "name": "Art, header, summary - vertical, small",
432+ "layout": { "template": { "card-size": "small" }, "components": JSON.parse(fullMapping) }
433+ },
434+ {
435+ "name": "Art, header, summary - vertical, large",
436+ "layout": { "template": { "card-size": "large" }, "components": JSON.parse(fullMapping) }
437+ },
438+ {
439+ "name": "Art, header, summary - vertical, wide",
440+ "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "art": { "aspect-ratio": 2 } }) }
441+ },
442+ {
443+ "name": "Art, title - vertical, fitted",
444+ "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "art": { "fill-mode": "fit" } }) }
445+ }
446+ ]
447+
448+ Card {
449+ id: card
450+ anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
451+
452+ template: Helpers.update(JSON.parse(root.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
453+ components: Helpers.update(JSON.parse(root.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
454+ cardData: Helpers.mapData(dataArea.text, components, dataError)
455+ }
456+
457+ Rectangle {
458+ anchors { top: parent.top; bottom: parent.bottom; right: parent.right}
459+ width: units.gu(40)
460+ color: "lightgrey"
461+
462+ Column {
463+ anchors { fill: parent; margins: units.gu(1) }
464+ spacing: units.gu(1)
465+
466+ OptionSelector {
467+ id: selector
468+ model: cardsModel
469+ delegate: OptionSelectorDelegate { text: modelData.name }
470+ onSelectedIndexChanged: updateAreas()
471+ Component.onCompleted: updateAreas()
472+
473+ function updateAreas() {
474+ var element = cardsModel[selectedIndex];
475+ if (element) {
476+ layoutArea.text = JSON.stringify(element.layout, undefined, 2) || "{}";
477+ // FIXME: don't overwrite data
478+ var data = JSON.parse(root.cardData);
479+ Helpers.update(data, element.data);
480+ dataArea.text = JSON.stringify(data, undefined, 2) || "{}";
481+ } else {
482+ layoutArea.text = "";
483+ dataArea.text = "";
484+ }
485+
486+ }
487+ }
488+
489+ TextArea {
490+ id: layoutArea
491+ anchors { left: parent.left; right: parent.right }
492+ height: units.gu(25)
493+ }
494+
495+ Label {
496+ id: layoutError
497+ anchors { left: parent.left; right: parent.right }
498+ height: units.gu(4)
499+ color: "orange"
500+ }
501+
502+ TextArea {
503+ id: dataArea
504+ anchors { left: parent.left; right: parent.right }
505+ height: units.gu(25)
506+ }
507+
508+ Label {
509+ id: dataError
510+ anchors { left: parent.left; right: parent.right }
511+ height: units.gu(4)
512+ color: "orange"
513+ }
514+ }
515+ }
516+
517+ UT.UnityTestCase {
518+ id: testCase
519+
520+ when: windowShown
521+
522+ property Item header: findChild(card, "cardHeader")
523+ property Item art: findChild(card, "artShape")
524+ property Item artImage: findChild(card, "artImage")
525+ property Item summary: findChild(card, "summaryLabel")
526+
527+ function initTestCase() {
528+ verify(testCase.header !== undefined, "Couldn't find header object.");
529+ }
530+
531+ function cleanup() {
532+ selector.selectedIndex = -1;
533+ }
534+
535+
536+ function test_header_binding_data() {
537+ return [
538+ { tag: "Mascot", property: "mascot", value: Qt.resolvedUrl("artwork/avatar.png"), index: 0 },
539+ { tag: "Title", property: "title", value: "foo", index: 0 },
540+ { tag: "Subtitle", property: "subtitle", value: "bar", index: 0 },
541+ ];
542+ }
543+
544+ function test_header_binding(data) {
545+ selector.selectedIndex = data.index;
546+ tryCompare(testCase.header, data.property, data.value);
547+ }
548+
549+ function test_card_binding_data() {
550+ return [
551+ { tag: "Art", object: artImage, property: "source", value: Qt.resolvedUrl("artwork/music-player-design.png"), index: 0 },
552+ { tag: "Summary", object: summary, property: "text", field: "summary", index: 0 },
553+ { tag: "Fit", object: art, fill: Image.PreserveAspectFit, index: 4 },
554+ ];
555+ }
556+
557+ function test_card_binding(data) {
558+ selector.selectedIndex = data.index;
559+
560+ if (data.hasOwnProperty('value')) {
561+ tryCompare(data.object, data.property, data.value);
562+ }
563+
564+ if (data.hasOwnProperty('field')) {
565+ tryCompare(data.object, data.property, card.cardData[data.field]);
566+ }
567+ }
568+
569+ function test_card_size_data() {
570+ return [
571+ { tag: "Medium", width: units.gu(18.5), index: 0 },
572+ { tag: "Small", width: units.gu(12), size: "small", index: 0 },
573+ { tag: "Large", width: units.gu(38), size: "large", index: 0 },
574+ { tag: "Wide", width: units.gu(18.5), aspect: 0.5, index: 0 },
575+ ]
576+ }
577+
578+ function test_card_size(data) {
579+ selector.selectedIndex = data.index;
580+
581+ if (data.hasOwnProperty("size")) {
582+ card.template['card-size'] = data.size;
583+ card.templateChanged();
584+ }
585+
586+ if (data.hasOwnProperty("aspect")) {
587+ card.components['art']['aspect-ratio'] = data.aspect;
588+ card.componentsChanged();
589+ }
590+
591+ if (data.hasOwnProperty("width")) {
592+ tryCompare(card, "width", data.width);
593+ }
594+
595+ if (data.hasOwnProperty("height")) {
596+ tryCompare(card, "height", data.height);
597+ }
598+ }
599+
600+ function test_art_size_data() {
601+ return [
602+ { tag: "Medium", width: units.gu(18.5), fill: Image.PreserveAspectCrop, index: 0 },
603+ { tag: "Small", width: units.gu(12), index: 1 },
604+ { tag: "Large", width: units.gu(38), index: 2 },
605+ { tag: "Wide", height: units.gu(19), size: "large", index: 3 },
606+ { tag: "Fit", height: units.gu(38), size: "large", width: units.gu(19), index: 4 },
607+ ]
608+ }
609+
610+ function test_art_size(data) {
611+ selector.selectedIndex = data.index;
612+
613+ if (data.hasOwnProperty("size")) {
614+ card.template['card-size'] = data.size;
615+ card.templateChanged();
616+ }
617+
618+ if (data.hasOwnProperty("aspect")) {
619+ card.components['art']['aspect-ratio'] = data.aspect;
620+ card.componentsChanged();
621+ }
622+
623+ if (data.hasOwnProperty("width")) {
624+ tryCompare(art, "width", data.width);
625+ }
626+
627+ if (data.hasOwnProperty("height")) {
628+ tryCompare(art, "height", data.height);
629+ }
630+
631+ if (data.hasOwnProperty("fill")) {
632+ tryCompare(artImage, "fillMode", data.fill);
633+ }
634+ }
635+ }
636+}
637
638=== added file 'tests/qmltests/Dash/tst_CardHeader.qml'
639--- tests/qmltests/Dash/tst_CardHeader.qml 1970-01-01 00:00:00 +0000
640+++ tests/qmltests/Dash/tst_CardHeader.qml 2013-12-10 09:30:53 +0000
641@@ -0,0 +1,121 @@
642+import QtQuick 2.0
643+import QtTest 1.0
644+import Ubuntu.Components 0.1
645+import Unity.Test 0.1 as UT
646+import "../../../Dash"
647+
648+
649+Rectangle {
650+ width: units.gu(40)
651+ height: units.gu(72)
652+ color: "lightgrey"
653+
654+ CardHeader {
655+ id: cardHeader
656+ anchors { left: parent.left; right: parent.right }
657+ }
658+
659+ Rectangle {
660+ anchors.fill: cardHeader
661+ color: "lightblue"
662+ opacity: 0.5
663+ }
664+
665+ UT.UnityTestCase {
666+ id: testCase
667+
668+ when: windowShown
669+
670+ property Item mascot: findChild(cardHeader, "mascotShape")
671+ property Item titleLabel: findChild(cardHeader, "titleLabel")
672+ property Item subtitleLabel: findChild(cardHeader, "subtitleLabel")
673+ property Item prices: findChild(cardHeader, "prices")
674+ property Item oldPriceLabel: findChild(cardHeader, "oldPriceLabel")
675+ property Item outerRow: findChild(cardHeader, "outerRow")
676+ property Item column: findChild(cardHeader, "column")
677+
678+ function initTestCase() {
679+ verify(testCase.mascot !== undefined, "Couldn't find mascot object.");
680+ verify(testCase.titleLabel !== undefined, "Couldn't find titleLabel object.");
681+ verify(testCase.subtitleLabel !== undefined, "Couldn't find subtitleLabel object.");
682+ verify(testCase.prices !== undefined, "Couldn't find prices object.");
683+ verify(testCase.oldPriceLabel !== undefined, "Couldn't find oldPriceLabel object.");
684+ }
685+
686+ function cleanup() {
687+ cardHeader.mascot = "";
688+ cardHeader.title = "";
689+ cardHeader.subtitle = "";
690+ cardHeader.price = "";
691+ cardHeader.oldPrice = "";
692+ cardHeader.altPrice = "";
693+ }
694+
695+ function test_mascot_data() {
696+ return [
697+ { tag: "Empty", source: "", visible: false },
698+ { tag: "Invalid", source: "bad_path", visible: false },
699+ { tag: "Valid", source: "artwork/avatar.png", visible: true },
700+ ]
701+ }
702+
703+ function test_mascot(data) {
704+ cardHeader.mascot = data.source;
705+ tryCompare(testCase.mascot, "visible", data.visible);
706+ }
707+
708+ function test_labels_data() {
709+ return [
710+ { tag: "Empty", visible: false },
711+ { tag: "Title only", title: "Foo", visible: true },
712+ { tag: "Subtitle only", subtitle: "Bar", visible: false },
713+ { tag: "Both", title: "Foo", subtitle: "Bar", visible: true }
714+ ]
715+ }
716+
717+ function test_labels(data) {
718+ cardHeader.title = data.title !== undefined ? data.title : "";
719+ cardHeader.subtitle = data.subtitle !== undefined ? data.subtitle : "";
720+ tryCompare(cardHeader, "visible", data.visible);
721+ }
722+
723+ function test_prices_data() {
724+ return [
725+ { tag: "Main", main: "$1.25", visible: true },
726+ { tag: "Alt", alt: "€1.00", visible: false },
727+ { tag: "Old", old: "€2.00", visible: false },
728+ { tag: "Main and Alt", main: "$1.25", alt: "€1.00", visible: true },
729+ { tag: "Main and Old", main: "$1.25", old: "$2.00", visible: true, oldAlign: Text.AlignRight },
730+ { tag: "Alt and Old", alt: "€1.00", old: "$2.00", visible: false },
731+ { tag: "All", main: "$1.25", alt: "€1.00", old: "$2.00", visible: true, oldAlign: Text.AlignHCenter }
732+ ]
733+ }
734+
735+ function test_prices(data) {
736+ cardHeader.price = data.main !== undefined ? data.main : "";
737+ cardHeader.oldPrice = data.old !== undefined ? data.old : "";
738+ cardHeader.altPrice = data.alt !== undefined ? data.alt : "";
739+ tryCompare(cardHeader, "visible", data.visible);
740+ if (data.hasOwnProperty("oldAlign")) {
741+ compare(testCase.oldPriceLabel.horizontalAlignment, data.oldAlign, "Old price label is aligned wrong.")
742+ }
743+ }
744+
745+ function test_dimensions_data() {
746+ return [
747+ { tag: "Column width", object: column, width: cardHeader.width - testCase.outerRow.spacing * 2 },
748+ { tag: "Column width", object: column, width: cardHeader.width - mascot.width - testCase.outerRow.spacing * 3, mascot: "artwork/avatar.png" }
749+ ]
750+ }
751+
752+ function test_dimensions(data) {
753+ if (data.hasOwnProperty("mascot")) {
754+ cardHeader.mascot = data.mascot;
755+ }
756+
757+ if (data.hasOwnProperty("width")) {
758+ tryCompare(data.object, "width", data.width);
759+ }
760+ }
761+ }
762+}

Subscribers

People subscribed via source and target branches

to all changes: