on xenial help sign in
Introduce the Card and integrate with the new plugin.
commitb9a4eab2876dc11109c4388ccdac3b9b7ec0a54f authorMichał Sawicz <michal.sawicz@canonical.com> dateTue 10 Dec 2013, 10:40 (13 years ago) parentbf4f7eaa3e784d
12 files changed+678−1
Dash/Card.qml+66 Dash/CardFilterGrid.qml+44 Dash/CardHeader.qml+96 Dash/DashFilterGrid.qml+2 Dash/DashRenderer.qml+6 Dash/GenericScopeView.qml+4−1 tests/qmltests/CMakeLists.txt+2 tests/qmltests/Dash/CardHelpers.js+59 tests/qmltests/Dash/artwork/avatar@12.png tests/qmltests/Dash/artwork/music-player-design.png tests/qmltests/Dash/tst_Card.qml+278 tests/qmltests/Dash/tst_CardHeader.qml+121
Dash/Card.qmlnew file+66
@@ -0,0 +1,66 @@
1+import QtQuick 2.0
2+import Ubuntu.Components 0.1
3+
4+Item {
5+ id: root
6+ property var template
7+ property var components
8+ property var cardData
9+
10+ width: {
11+ if (template !== undefined) {
12+ switch (template['card-size']) {
13+ case "small": return units.gu(12);
14+ case "large": return units.gu(38);
15+ }
16+ }
17+ return units.gu(18.5);
18+ }
19+ height: childrenRect.height
20+
21+ UbuntuShape {
22+ id: artShape
23+ objectName: "artShape"
24+ width: image.fillMode === Image.PreserveAspectCrop || aspect < image.aspect ? image.width : height * image.aspect
25+ height: image.fillMode === Image.PreserveAspectCrop || aspect > image.aspect ? image.height : width / image.aspect
26+ anchors.horizontalCenter: parent.horizontalCenter
27+
28+ property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1
29+
30+ image: Image {
31+ width: root.width
32+ height: width / artShape.aspect
33+ objectName: "artImage"
34+ source: cardData && cardData["art"] || ""
35+ // FIXME uncomment when having investigated / fixed the crash
36+ //sourceSize.width: width > height ? width : 0
37+ //sourceSize.height: height > width ? height : 0
38+ fillMode: components["art"]["fill-mode"] == "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
39+
40+ property real aspect: implicitWidth / implicitHeight
41+ }
42+ }
43+
44+ CardHeader {
45+ id: header
46+ objectName: "cardHeader"
47+ anchors {
48+ top: artShape.bottom
49+ left: parent.left
50+ right: parent.right
51+ }
52+
53+ mascot: cardData && cardData["mascot"] || ""
54+ title: cardData && cardData["title"] || ""
55+ subtitle: cardData && cardData["subtitle"] || ""
56+ }
57+
58+ Label {
59+ objectName: "summaryLabel"
60+ anchors { top: header.bottom; left: parent.left; right: parent.right }
61+ wrapMode: Text.Wrap
62+ maximumLineCount: 5
63+ elide: Text.ElideRight
64+ text: cardData && cardData["summary"] || ""
65+ }
66+}
Dash/CardFilterGrid.qmlnew file+44
@@ -0,0 +1,44 @@
1+/*
2+ * Copyright (C) 2013 Canonical, Ltd.
3+ *
4+ * This program is free software; you can redistribute it and/or modify
5+ * it under the terms of the GNU General Public License as published by
6+ * the Free Software Foundation; version 3.
7+ *
8+ * This program is distributed in the hope that it will be useful,
9+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
10+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+ * GNU General Public License for more details.
12+ *
13+ * You should have received a copy of the GNU General Public License
14+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
15+ */
16+
17+import QtQuick 2.0
18+import Ubuntu.Components 0.1
19+
20+DashFilterGrid {
21+ id: genericFilterGrid
22+
23+ minimumHorizontalSpacing: units.gu(0.5)
24+ // FIXME calculate the size correctly
25+ delegateWidth: grid.currentItem.width
26+ delegateHeight: grid.currentItem.height
27+ verticalSpacing: units.gu(2)
28+
29+ delegate: Card {
30+ id: tile
31+ objectName: "delegate" + index
32+ cardData: model
33+ template: genericFilterGrid.template
34+ components: {
35+ "art": {
36+ "aspect-ratio": 1.0,
37+ "fill-mode": "crop"
38+ }
39+ }
40+
41+ //onClicked: genericFilterGrid.clicked(index, tile.y)
42+ //onPressAndHold: genericFilterGrid.pressAndHold(index, tile.y)
43+ }
44+}
Dash/CardHeader.qmlnew file+96
@@ -0,0 +1,96 @@
1+import QtQuick 2.0
2+import Ubuntu.Components 0.1
3+
4+Item {
5+ id: root
6+ property alias mascot: mascotImage.source
7+ property alias title: titleLabel.text
8+ property alias subtitle: subtitleLabel.text
9+ property alias price: priceLabel.text
10+ property alias oldPrice: oldPriceLabel.text
11+ property alias altPrice: altPriceLabel.text
12+
13+ visible: mascotImage.status === Image.Ready || title || price
14+ height: row.height > 0 ? row.height + row.spacing * 2 : 0
15+
16+ Row {
17+ id: row
18+ objectName: "outerRow"
19+
20+ anchors { top: parent.top; left: parent.left; right: parent.right; margins: spacing }
21+ spacing: units.gu(1)
22+
23+ UbuntuShape {
24+ id: mascotShape
25+ objectName: "mascotShape"
26+
27+ width: units.gu(8)
28+ height: units.gu(8)
29+ visible: image.status === Image.Ready
30+
31+ image: Image {
32+ id: mascotImage
33+ sourceSize { width: mascotShape.width; height: mascotShape.height }
34+ }
35+ }
36+
37+ Column {
38+ objectName: "column"
39+ width: parent.width - x
40+
41+ Label {
42+ id: titleLabel
43+ anchors { left: parent.left; right: parent.right }
44+ elide: Text.ElideRight
45+ objectName: "titleLabel"
46+ font.weight: Font.DemiBold
47+ wrapMode: Text.Wrap
48+ maximumLineCount: 2
49+ }
50+
51+ Label {
52+ id: subtitleLabel
53+ anchors { left: parent.left; right: parent.right }
54+ elide: Text.ElideRight
55+ visible: titleLabel.text && text
56+ }
57+
58+ Row {
59+ id: prices
60+ objectName: "prices"
61+ anchors { left: parent.left; right: parent.right }
62+
63+ property int labels: {
64+ var labels = 1; // price always visible
65+ if (oldPriceLabel.text !== "") labels += 1;
66+ if (altPriceLabel.text !== "") labels += 1;
67+ return labels;
68+ }
69+ property real labelWidth: width / labels
70+
71+ Label {
72+ id: priceLabel
73+ width: parent.labelWidth
74+ elide: Text.ElideRight
75+ font.weight: Font.DemiBold
76+ color: Theme.palette.selected.foreground
77+ }
78+
79+ Label {
80+ id: oldPriceLabel
81+ objectName: "oldPriceLabel"
82+ width: parent.labelWidth
83+ elide: Text.ElideRight
84+ horizontalAlignment: parent.labels === 3 ? Text.AlignHCenter : Text.AlignRight
85+ }
86+
87+ Label {
88+ id: altPriceLabel
89+ width: parent.labelWidth
90+ elide: Text.ElideRight
91+ horizontalAlignment: Text.AlignRight
92+ }
93+ }
94+ }
95+ }
96+}
Dash/DashFilterGrid.qml+2
@@ -29,6 +29,8 @@ DashRenderer {
2929 property alias maximumNumberOfColumns: filterGrid.maximumNumberOfColumns
3030 property alias minimumHorizontalSpacing: filterGrid.minimumHorizontalSpacing
3131
32+ property FilterGrid grid: filterGrid
33+
3234 collapsedHeight: filterGrid.collapsedHeight
3335 collapsedRowCount: filterGrid.collapsedRowCount
3436 columns: filterGrid.columns
Dash/DashRenderer.qml+6
@@ -62,4 +62,10 @@ Item {
6262
6363 function startFilterAnimation(filter) {
6464 }
65+
66+ /// Category template definition from the scope
67+ property var template
68+
69+ /// Component mapping and configuration from the scope
70+ property var components
6571}
Dash/GenericScopeView.qml+4−1
@@ -164,6 +164,9 @@ FocusScope {
164164 }
165165 }
166166 updateDelegateCreationRange();
167+ // FIXME: should be "template", not "renderer"
168+ item.template = Qt.binding(function() { return model.renderer });
169+ item.components = Qt.binding(function() { return model.components });
167170 }
168171
169172 Component.onDestruction: {
@@ -342,7 +345,7 @@ FocusScope {
342345 default: return "Generic/GenericFilterGrid.qml";
343346 }
344347 }
345− default: return "Generic/GenericFilterGrid.qml";
348+ default: return "CardFilterGrid.qml";
346349 }
347350 }
348351
tests/qmltests/CMakeLists.txt+2
@@ -43,6 +43,8 @@ add_qml_test(Dash DashBar IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRE
4343add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4444add_qml_test(Dash DashPreview)
4545add_qml_test(Dash GenericPreview)
46+add_qml_test(Dash Card)
47+add_qml_test(Dash CardHeader)
4648add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4749add_qml_test(Dash FilterGrids IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_SOURCE_DIR}/plugins
4850 ${CMAKE_BINARY_DIR}/tests/mocks)
tests/qmltests/Dash/CardHelpers.jsnew file+59
@@ -0,0 +1,59 @@
1+.pragma library
2+
3+var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary"]
4+
5+function tryParse(json, errorLabel) {
6+ var o = undefined;
7+ if (errorLabel !== undefined) {
8+ errorLabel.text = "";
9+ }
10+ try {
11+ o = JSON.parse(json)
12+ } catch(err) {
13+ if (errorLabel !== undefined) {
14+ errorLabel.text = err + "";
15+ } else {
16+ console.debug(err);
17+ }
18+ }
19+ return o;
20+}
21+
22+function mapData(json, layout, errorLabel) {
23+ var o = tryParse(json, errorLabel);
24+ var d = undefined;
25+
26+ if (o !== undefined) {
27+ d = Object();
28+ for (var k in components) {
29+ try {
30+ if (typeof layout[components[k]] == "object") {
31+ d[components[k]] = o[layout[components[k]]['field']];
32+ } else {
33+ d[components[k]] = o[layout[components[k]]];
34+ }
35+ } catch(err) {
36+ d[components[k]] = undefined;
37+ }
38+ }
39+ }
40+ return d;
41+}
42+
43+function update(object, overrides) {
44+ for (var k in overrides) {
45+ if (typeof object[k] == "string" && typeof overrides[k] == "object") {
46+ if (!overrides[k].hasOwnProperty('field')) overrides[k]['field'] = object[k];
47+ }
48+ if (object[k] === null) {
49+ object[k] = overrides[k];
50+ } else if (typeof object[k] == "object" && typeof overrides[k] == "string") {
51+ object[k]['field'] = overrides[k];
52+ } else if (typeof object[k] == "object") {
53+ update(object[k], overrides[k]);
54+ } else {
55+ object[k] = overrides[k];
56+ }
57+ }
58+ return object;
59+}
tests/qmltests/Dash/artwork/avatar@12.pngnew file

Binary file not shown.

tests/qmltests/Dash/artwork/music-player-design.pngnew file

Binary file not shown.

tests/qmltests/Dash/tst_Card.qmlnew file+278
@@ -0,0 +1,278 @@
1+import QtQuick 2.0
2+import QtTest 1.0
3+import Ubuntu.Components 0.1
4+import Unity.Test 0.1 as UT
5+import "../../../Dash"
6+import "CardHelpers.js" as Helpers
7+
8+Rectangle {
9+ id: root
10+ width: units.gu(80)
11+ height: units.gu(72)
12+ color: "#88FFFFFF"
13+
14+ property string defaultLayout: '
15+ {
16+ "schema-version": 1,
17+ "template": {
18+ "category-layout": "grid",
19+ "card-layout": "vertical",
20+ "card-size": "medium",
21+ "overlay-mode": null,
22+ "collapsed-rows": 2
23+ },
24+ "components": {
25+ "title": null,
26+ "art": {
27+ "aspect-ratio": 1.0,
28+ "fill-mode": "crop"
29+ },
30+ "subtitle": null,
31+ "mascot": null,
32+ "emblem": null,
33+ "old-price": null,
34+ "price": null,
35+ "alt-price": null,
36+ "rating": {
37+ "type": "stars",
38+ "range": [0, 5],
39+ "full": "image://theme/rating-star-full",
40+ "half": "image://theme/rating-star-half",
41+ "empty": "image://theme/rating-star-empty"
42+ },
43+ "alt-rating": null,
44+ "summary": null
45+ },
46+ "resources": {}
47+ }'
48+
49+ property string cardData: '
50+ {
51+ "art": "../tests/qmltests/Dash/artwork/music-player-design.png",
52+ "mascot": "../tests/qmltests/Dash/artwork/avatar.png",
53+ "title": "foo",
54+ "subtitle": "bar",
55+ "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."
56+ }'
57+
58+ property string fullMapping: '
59+ {
60+ "title": "title",
61+ "art": "art",
62+ "subtitle": "subtitle",
63+ "mascot": "mascot",
64+ "summary": "summary"
65+ }'
66+
67+ property var cardsModel: [
68+ {
69+ "name": "Art, header, summary - vertical",
70+ "layout": { "components": JSON.parse(fullMapping) }
71+ },
72+ {
73+ "name": "Art, header, summary - vertical, small",
74+ "layout": { "template": { "card-size": "small" }, "components": JSON.parse(fullMapping) }
75+ },
76+ {
77+ "name": "Art, header, summary - vertical, large",
78+ "layout": { "template": { "card-size": "large" }, "components": JSON.parse(fullMapping) }
79+ },
80+ {
81+ "name": "Art, header, summary - vertical, wide",
82+ "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "art": { "aspect-ratio": 2 } }) }
83+ },
84+ {
85+ "name": "Art, title - vertical, fitted",
86+ "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "art": { "fill-mode": "fit" } }) }
87+ }
88+ ]
89+
90+ Card {
91+ id: card
92+ anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
93+
94+ template: Helpers.update(JSON.parse(root.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
95+ components: Helpers.update(JSON.parse(root.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
96+ cardData: Helpers.mapData(dataArea.text, components, dataError)
97+ }
98+
99+ Rectangle {
100+ anchors { top: parent.top; bottom: parent.bottom; right: parent.right}
101+ width: units.gu(40)
102+ color: "lightgrey"
103+
104+ Column {
105+ anchors { fill: parent; margins: units.gu(1) }
106+ spacing: units.gu(1)
107+
108+ OptionSelector {
109+ id: selector
110+ model: cardsModel
111+ delegate: OptionSelectorDelegate { text: modelData.name }
112+ onSelectedIndexChanged: updateAreas()
113+ Component.onCompleted: updateAreas()
114+
115+ function updateAreas() {
116+ var element = cardsModel[selectedIndex];
117+ if (element) {
118+ layoutArea.text = JSON.stringify(element.layout, undefined, 2) || "{}";
119+ // FIXME: don't overwrite data
120+ var data = JSON.parse(root.cardData);
121+ Helpers.update(data, element.data);
122+ dataArea.text = JSON.stringify(data, undefined, 2) || "{}";
123+ } else {
124+ layoutArea.text = "";
125+ dataArea.text = "";
126+ }
127+
128+ }
129+ }
130+
131+ TextArea {
132+ id: layoutArea
133+ anchors { left: parent.left; right: parent.right }
134+ height: units.gu(25)
135+ }
136+
137+ Label {
138+ id: layoutError
139+ anchors { left: parent.left; right: parent.right }
140+ height: units.gu(4)
141+ color: "orange"
142+ }
143+
144+ TextArea {
145+ id: dataArea
146+ anchors { left: parent.left; right: parent.right }
147+ height: units.gu(25)
148+ }
149+
150+ Label {
151+ id: dataError
152+ anchors { left: parent.left; right: parent.right }
153+ height: units.gu(4)
154+ color: "orange"
155+ }
156+ }
157+ }
158+
159+ UT.UnityTestCase {
160+ id: testCase
161+
162+ when: windowShown
163+
164+ property Item header: findChild(card, "cardHeader")
165+ property Item art: findChild(card, "artShape")
166+ property Item artImage: findChild(card, "artImage")
167+ property Item summary: findChild(card, "summaryLabel")
168+
169+ function initTestCase() {
170+ verify(testCase.header !== undefined, "Couldn't find header object.");
171+ }
172+
173+ function cleanup() {
174+ selector.selectedIndex = -1;
175+ }
176+
177+
178+ function test_header_binding_data() {
179+ return [
180+ { tag: "Mascot", property: "mascot", value: Qt.resolvedUrl("artwork/avatar.png"), index: 0 },
181+ { tag: "Title", property: "title", value: "foo", index: 0 },
182+ { tag: "Subtitle", property: "subtitle", value: "bar", index: 0 },
183+ ];
184+ }
185+
186+ function test_header_binding(data) {
187+ selector.selectedIndex = data.index;
188+ tryCompare(testCase.header, data.property, data.value);
189+ }
190+
191+ function test_card_binding_data() {
192+ return [
193+ { tag: "Art", object: artImage, property: "source", value: Qt.resolvedUrl("artwork/music-player-design.png"), index: 0 },
194+ { tag: "Summary", object: summary, property: "text", field: "summary", index: 0 },
195+ { tag: "Fit", object: art, fill: Image.PreserveAspectFit, index: 4 },
196+ ];
197+ }
198+
199+ function test_card_binding(data) {
200+ selector.selectedIndex = data.index;
201+
202+ if (data.hasOwnProperty('value')) {
203+ tryCompare(data.object, data.property, data.value);
204+ }
205+
206+ if (data.hasOwnProperty('field')) {
207+ tryCompare(data.object, data.property, card.cardData[data.field]);
208+ }
209+ }
210+
211+ function test_card_size_data() {
212+ return [
213+ { tag: "Medium", width: units.gu(18.5), index: 0 },
214+ { tag: "Small", width: units.gu(12), size: "small", index: 0 },
215+ { tag: "Large", width: units.gu(38), size: "large", index: 0 },
216+ { tag: "Wide", width: units.gu(18.5), aspect: 0.5, index: 0 },
217+ ]
218+ }
219+
220+ function test_card_size(data) {
221+ selector.selectedIndex = data.index;
222+
223+ if (data.hasOwnProperty("size")) {
224+ card.template['card-size'] = data.size;
225+ card.templateChanged();
226+ }
227+
228+ if (data.hasOwnProperty("aspect")) {
229+ card.components['art']['aspect-ratio'] = data.aspect;
230+ card.componentsChanged();
231+ }
232+
233+ if (data.hasOwnProperty("width")) {
234+ tryCompare(card, "width", data.width);
235+ }
236+
237+ if (data.hasOwnProperty("height")) {
238+ tryCompare(card, "height", data.height);
239+ }
240+ }
241+
242+ function test_art_size_data() {
243+ return [
244+ { tag: "Medium", width: units.gu(18.5), fill: Image.PreserveAspectCrop, index: 0 },
245+ { tag: "Small", width: units.gu(12), index: 1 },
246+ { tag: "Large", width: units.gu(38), index: 2 },
247+ { tag: "Wide", height: units.gu(19), size: "large", index: 3 },
248+ { tag: "Fit", height: units.gu(38), size: "large", width: units.gu(19), index: 4 },
249+ ]
250+ }
251+
252+ function test_art_size(data) {
253+ selector.selectedIndex = data.index;
254+
255+ if (data.hasOwnProperty("size")) {
256+ card.template['card-size'] = data.size;
257+ card.templateChanged();
258+ }
259+
260+ if (data.hasOwnProperty("aspect")) {
261+ card.components['art']['aspect-ratio'] = data.aspect;
262+ card.componentsChanged();
263+ }
264+
265+ if (data.hasOwnProperty("width")) {
266+ tryCompare(art, "width", data.width);
267+ }
268+
269+ if (data.hasOwnProperty("height")) {
270+ tryCompare(art, "height", data.height);
271+ }
272+
273+ if (data.hasOwnProperty("fill")) {
274+ tryCompare(artImage, "fillMode", data.fill);
275+ }
276+ }
277+ }
278+}
tests/qmltests/Dash/tst_CardHeader.qmlnew file+121
@@ -0,0 +1,121 @@
1+import QtQuick 2.0
2+import QtTest 1.0
3+import Ubuntu.Components 0.1
4+import Unity.Test 0.1 as UT
5+import "../../../Dash"
6+
7+
8+Rectangle {
9+ width: units.gu(40)
10+ height: units.gu(72)
11+ color: "lightgrey"
12+
13+ CardHeader {
14+ id: cardHeader
15+ anchors { left: parent.left; right: parent.right }
16+ }
17+
18+ Rectangle {
19+ anchors.fill: cardHeader
20+ color: "lightblue"
21+ opacity: 0.5
22+ }
23+
24+ UT.UnityTestCase {
25+ id: testCase
26+
27+ when: windowShown
28+
29+ property Item mascot: findChild(cardHeader, "mascotShape")
30+ property Item titleLabel: findChild(cardHeader, "titleLabel")
31+ property Item subtitleLabel: findChild(cardHeader, "subtitleLabel")
32+ property Item prices: findChild(cardHeader, "prices")
33+ property Item oldPriceLabel: findChild(cardHeader, "oldPriceLabel")
34+ property Item outerRow: findChild(cardHeader, "outerRow")
35+ property Item column: findChild(cardHeader, "column")
36+
37+ function initTestCase() {
38+ verify(testCase.mascot !== undefined, "Couldn't find mascot object.");
39+ verify(testCase.titleLabel !== undefined, "Couldn't find titleLabel object.");
40+ verify(testCase.subtitleLabel !== undefined, "Couldn't find subtitleLabel object.");
41+ verify(testCase.prices !== undefined, "Couldn't find prices object.");
42+ verify(testCase.oldPriceLabel !== undefined, "Couldn't find oldPriceLabel object.");
43+ }
44+
45+ function cleanup() {
46+ cardHeader.mascot = "";
47+ cardHeader.title = "";
48+ cardHeader.subtitle = "";
49+ cardHeader.price = "";
50+ cardHeader.oldPrice = "";
51+ cardHeader.altPrice = "";
52+ }
53+
54+ function test_mascot_data() {
55+ return [
56+ { tag: "Empty", source: "", visible: false },
57+ { tag: "Invalid", source: "bad_path", visible: false },
58+ { tag: "Valid", source: "artwork/avatar.png", visible: true },
59+ ]
60+ }
61+
62+ function test_mascot(data) {
63+ cardHeader.mascot = data.source;
64+ tryCompare(testCase.mascot, "visible", data.visible);
65+ }
66+
67+ function test_labels_data() {
68+ return [
69+ { tag: "Empty", visible: false },
70+ { tag: "Title only", title: "Foo", visible: true },
71+ { tag: "Subtitle only", subtitle: "Bar", visible: false },
72+ { tag: "Both", title: "Foo", subtitle: "Bar", visible: true }
73+ ]
74+ }
75+
76+ function test_labels(data) {
77+ cardHeader.title = data.title !== undefined ? data.title : "";
78+ cardHeader.subtitle = data.subtitle !== undefined ? data.subtitle : "";
79+ tryCompare(cardHeader, "visible", data.visible);
80+ }
81+
82+ function test_prices_data() {
83+ return [
84+ { tag: "Main", main: "$1.25", visible: true },
85+ { tag: "Alt", alt: "€1.00", visible: false },
86+ { tag: "Old", old: "€2.00", visible: false },
87+ { tag: "Main and Alt", main: "$1.25", alt: "€1.00", visible: true },
88+ { tag: "Main and Old", main: "$1.25", old: "$2.00", visible: true, oldAlign: Text.AlignRight },
89+ { tag: "Alt and Old", alt: "€1.00", old: "$2.00", visible: false },
90+ { tag: "All", main: "$1.25", alt: "€1.00", old: "$2.00", visible: true, oldAlign: Text.AlignHCenter }
91+ ]
92+ }
93+
94+ function test_prices(data) {
95+ cardHeader.price = data.main !== undefined ? data.main : "";
96+ cardHeader.oldPrice = data.old !== undefined ? data.old : "";
97+ cardHeader.altPrice = data.alt !== undefined ? data.alt : "";
98+ tryCompare(cardHeader, "visible", data.visible);
99+ if (data.hasOwnProperty("oldAlign")) {
100+ compare(testCase.oldPriceLabel.horizontalAlignment, data.oldAlign, "Old price label is aligned wrong.")
101+ }
102+ }
103+
104+ function test_dimensions_data() {
105+ return [
106+ { tag: "Column width", object: column, width: cardHeader.width - testCase.outerRow.spacing * 2 },
107+ { tag: "Column width", object: column, width: cardHeader.width - mascot.width - testCase.outerRow.spacing * 3, mascot: "artwork/avatar.png" }
108+ ]
109+ }
110+
111+ function test_dimensions(data) {
112+ if (data.hasOwnProperty("mascot")) {
113+ cardHeader.mascot = data.mascot;
114+ }
115+
116+ if (data.hasOwnProperty("width")) {
117+ tryCompare(data.object, "width", data.width);
118+ }
119+ }
120+ }
121+}