Merge lp:~saviq/unity8/new-scopes-integrate-card into lp:~unity-team/unity8/new-scopes
- new-scopes-integrate-card
- Merge into new-scopes
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Michael Zanetti (community) | Approve | ||
Michal Hruby (community) | Needs Fixing | ||
Review via email:
|
Commit message
Introduce the Card and integrate with the new plugin.
Description of the change
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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.
>
> 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
-
;--
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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".
- 569. By Michał Sawicz
-
Get rid of FIXME for icon vs. art
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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(
722 + if (data.hasOwnPro
723 + compare(
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?
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michael Zanetti (mzanetti) wrote : | # |
Got a test failure here.... fuzzyCompare() needed?
FAIL! : qmltestrunner:
Actual (): 166.5
Expected (): 167
Loc: [/home/
- 570. By Michał Sawicz
-
No dynamic maxLineCount.
- 571. By Michał Sawicz
-
Do not use quarters of GUs.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
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:
> Actual (): 166.5
> Expected (): 167
> Loc: [/home/
Nah, I just need to go for integer values there. Fixed.
--
Michał (Saviq) Sawicz <email address hidden>
Canonical Services Ltd.
![](/+icing/build/overlay/assets/skins/sam/images/close.gif)
Michael Zanetti (mzanetti) wrote : | # |
Ok. other one is gone. but here's another:
FAIL! : qmltestrunner:
Actual (): 342
Expected (): 333
Loc: [/home/
Segmentation fault (core dumped)
- 572. By Michał Sawicz
-
38/2 != 18.5, 38/2 == 19 !!
Preview Diff
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' |
352 | Binary 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' |
354 | Binary 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 | +} |
40 + //sourceSize.width: width > height ? width : 0 height: height > width ? height : -1
41 + //sourceSize.
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