on xenial help sign in
Merge card-tool
commit0bf39e2ffef7d1add64f418d0556cc0608698157 authorMichał Sawicz <michal.sawicz@canonical.com> dateFri 31 Jan 2014, 17:26 (13 years ago) parent286ee5aa383ae6
9 files changed+555−88
qml/Dash/Card.qml+6−13 qml/Dash/CardHeader.qml+14−3 qml/Dash/CardTool.qml+136 qml/Dash/graphics/checkers.png tests/qmltests/CMakeLists.txt+1 tests/qmltests/Dash/CardHelpers.js+45 tests/qmltests/Dash/tst_Card.qml+50−69 tests/qmltests/Dash/tst_CardHeader.qml+3−3 tests/qmltests/Dash/tst_CardTool.qml+300
qml/Dash/Card.qml+6−13
@@ -23,20 +23,12 @@ AbstractButton {
2323 property var components
2424 property var cardData
2525
26− width: {
27− if (template !== undefined) {
28− if (template["card-layout"] === "horizontal") return units.gu(38);
29− switch (template['card-size']) {
30− case "small": return units.gu(12);
31− case "large": return units.gu(38);
32− }
33− }
34− return units.gu(18.5);
35− }
36− height: childrenRect.height
26+ implicitWidth: childrenRect.width
27+ implicitHeight: childrenRect.height
3728
3829 UbuntuShape {
3930 id: artShape
31+ radius: "medium"
4032 objectName: "artShape"
4133 width: {
4234 if (!visible) return 0
@@ -60,7 +52,7 @@ AbstractButton {
6052 // FIXME uncomment when having investigated / fixed the crash
6153 //sourceSize.width: width > height ? width : 0
6254 //sourceSize.height: height > width ? height : 0
63− fillMode: components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
55+ fillMode: components && components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
6456
6557 property real aspect: implicitWidth / implicitHeight
6658 }
@@ -82,11 +74,12 @@ AbstractButton {
8274
8375 Label {
8476 objectName: "summaryLabel"
85− anchors { top: header.bottom; left: parent.left; right: parent.right }
77+ anchors { top: header.visible ? header.bottom : artShape.bottom; left: parent.left; right: parent.right }
8678 wrapMode: Text.Wrap
8779 maximumLineCount: 5
8880 elide: Text.ElideRight
8981 text: cardData && cardData["summary"] || ""
9082 height: text ? implicitHeight : 0
83+ fontSize: "small"
9184 }
9285}
qml/Dash/CardHeader.qml+14−3
@@ -27,14 +27,21 @@ Item {
2727 property alias altPrice: altPriceLabel.text
2828
2929 visible: mascotImage.status === Image.Ready || title || price
30− height: row.height > 0 ? row.height + row.spacing * 2 : 0
30+ height: row.height > 0 ? row.height + row.margins * 2 : 0
3131
3232 Row {
3333 id: row
3434 objectName: "outerRow"
3535
36− anchors { top: parent.top; left: parent.left; right: parent.right; margins: spacing }
37− spacing: units.gu(1)
36+ property real margins: units.gu(1)
37+
38+ anchors {
39+ top: parent.top; left: parent.left; right: parent.right
40+ margins: margins
41+ leftMargin: spacing
42+ rightMargin: spacing
43+ }
44+ spacing: mascotShape.visible ? margins : 0
3845
3946 UbuntuShape {
4047 id: mascotShape
@@ -53,6 +60,7 @@ Item {
5360 Column {
5461 objectName: "column"
5562 width: parent.width - x
63+ spacing: units.gu(0.5)
5664
5765 Label {
5866 id: titleLabel
@@ -62,6 +70,7 @@ Item {
6270 font.weight: Font.DemiBold
6371 wrapMode: Text.Wrap
6472 maximumLineCount: 2
73+ fontSize: "small"
6574 }
6675
6776 Label {
@@ -69,7 +78,9 @@ Item {
6978 objectName: "subtitleLabel"
7079 anchors { left: parent.left; right: parent.right }
7180 elide: Text.ElideRight
81+ font.weight: Font.Light
7282 visible: titleLabel.text && text
83+ fontSize: "x-small"
7384 }
7485
7586 Row {
qml/Dash/CardTool.qmlnew file+136
@@ -0,0 +1,136 @@
1+/*
2+ * Copyright (C) 2014 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+
19+/*!
20+ \brief Tool for introspecting Card properties.
21+
22+ Some properties of Cards we need to determine category-wide (like card sizes in grid),
23+ so we should not do them per-Card but in the category renderer.
24+
25+ This component creates an invisible card filled with maximum mapped data and calculates
26+ or measures card properties for this configuration.
27+ */
28+
29+Item {
30+ id: cardTool
31+ /*!
32+ Template supplied for the category.
33+ */
34+ property var template
35+
36+ /*!
37+ Component mapping supplied for the category.
38+ */
39+ property var components
40+
41+ /*!
42+ Width of the view, based on which carousel width is calculated.
43+ */
44+ property var viewWidth
45+
46+ // FIXME: Saviq
47+ // Only way for the card below to actually be laid out completely.
48+ // If invisible or in "data" array, some components are not taken into account.
49+ width: 0
50+ height: 0
51+ clip: true
52+
53+ /*!
54+ type:real \brief Width to be enforced on the card in this configuration.
55+
56+ If undefined, should use implicit width of the actual card.
57+ */
58+ property var cardWidth: {
59+ switch (template !== undefined && template["category-layout"]) {
60+ case "grid":
61+ case "vertical-journal":
62+ if (template["card-layout"] === "horizontal") return units.gu(38);
63+ switch (template["card-size"]) {
64+ case "small": return units.gu(12);
65+ case "large": return units.gu(38);
66+ }
67+ return units.gu(18.5);
68+ case "carousel":
69+ if (viewWidth === undefined) return undefined
70+ if (viewWidth <= units.gu(40)) return units.gu(18)
71+ if (viewWidth >= units.gu(128)) return units.gu(26)
72+ return units.gu(18 + Math.round((viewWidth - units.gu(40))/ units.gu(11)))
73+ case undefined:
74+ case "organic-grid":
75+ case "journal":
76+ default:
77+ return undefined;
78+ }
79+ }
80+
81+ /*!
82+ type:real \brief Height to be enforced on the card in this configuration.
83+
84+ If undefined, should use implicit height of the actual card.
85+ */
86+ property var cardHeight: {
87+ switch (template !== undefined && template["category-layout"]) {
88+ case "journal":
89+ if (template["card-size"] >= 12 && template["card-size"] <= 38) return units.gu(template["card-size"]);
90+ return units.gu(18.5);
91+ case "grid":
92+ return card.implicitHeight
93+ case "carousel":
94+ return cardWidth / (components ? components["art"]["aspect-ratio"] : 1)
95+ case undefined:
96+ case "organic-grid":
97+ case "vertical-journal":
98+ default:
99+ return undefined;
100+ }
101+ }
102+
103+ property QtObject priv: card
104+
105+ Card {
106+ id: card
107+ template: cardTool.template
108+ components: cardTool.components
109+
110+ width: cardTool.cardWidth || implicitWidth
111+ height: cardTool.cardHeight || implicitHeight
112+
113+ property var fields: ["art", "mascot", "title", "subtitle", "summary"]
114+ property var maxData: {
115+ "art": Qt.resolvedUrl("graphics/checkers.png"),
116+ "mascot": Qt.resolvedUrl("graphics/checkers.png"),
117+ "title": "—\n—",
118+ "subtitle": "—",
119+ "summary": "—\n—\n—\n—\n—"
120+ }
121+
122+ onComponentsChanged: {
123+ var data = {};
124+ for (var k in fields) {
125+ var component = components[fields[k]];
126+ var key = fields[k];
127+ if ((typeof component === "string" && component.length > 0) ||
128+ (typeof component === "object" && component !== null
129+ && typeof component["field"] === "string" && component["field"].length > 0)) {
130+ data[key] = maxData[key];
131+ }
132+ }
133+ cardData = data;
134+ }
135+ }
136+}
qml/Dash/graphics/checkers.pngnew file

Binary file not shown.

tests/qmltests/CMakeLists.txt+1
@@ -44,6 +44,7 @@ add_qml_test(Dash DashPreview)
4444add_qml_test(Dash GenericPreview)
4545add_qml_test(Dash Card)
4646add_qml_test(Dash CardHeader)
47+add_qml_test(Dash CardTool)
4748add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
4849add_qml_test(Dash FilterGrids IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_SOURCE_DIR}/plugins
4950 ${CMAKE_BINARY_DIR}/tests/mocks)
tests/qmltests/Dash/CardHelpers.js+45
@@ -18,6 +18,51 @@
1818
1919var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary"]
2020
21+var defaultLayout = ' \
22+{ \
23+ "schema-version": 1, \
24+ "template": { \
25+ "category-layout": "grid", \
26+ "card-layout": "vertical", \
27+ "card-size": "medium", \
28+ "overlay-mode": null, \
29+ "collapsed-rows": 2 \
30+ }, \
31+ "components": { \
32+ "title": null, \
33+ "art": { \
34+ "aspect-ratio": 1.0, \
35+ "fill-mode": "crop" \
36+ }, \
37+ "subtitle": null, \
38+ "mascot": null, \
39+ "emblem": null, \
40+ "old-price": null, \
41+ "price": null, \
42+ "alt-price": null, \
43+ "rating": { \
44+ "type": "stars", \
45+ "range": [0, 5], \
46+ "full": "image://theme/rating-star-full", \
47+ "half": "image://theme/rating-star-half", \
48+ "empty": "image://theme/rating-star-empty" \
49+ }, \
50+ "alt-rating": null, \
51+ "summary": null \
52+ }, \
53+ "resources": {} \
54+}'
55+
56+var fullMapping = ' \
57+{ \
58+ "title": "title", \
59+ "art": "art", \
60+ "subtitle": "subtitle", \
61+ "mascot": "mascot", \
62+ "summary": "summary" \
63+}'
64+
65+
2166function tryParse(json, errorLabel) {
2267 var o = undefined;
2368 if (errorLabel !== undefined) {
tests/qmltests/Dash/tst_Card.qml+50−69
@@ -27,41 +27,6 @@ Rectangle {
2727 height: units.gu(72)
2828 color: "#88FFFFFF"
2929
30− property string defaultLayout: '
31− {
32− "schema-version": 1,
33− "template": {
34− "category-layout": "grid",
35− "card-layout": "vertical",
36− "card-size": "medium",
37− "overlay-mode": null,
38− "collapsed-rows": 2
39− },
40− "components": {
41− "title": null,
42− "art": {
43− "aspect-ratio": 1.0,
44− "fill-mode": "crop"
45− },
46− "subtitle": null,
47− "mascot": null,
48− "emblem": null,
49− "old-price": null,
50− "price": null,
51− "alt-price": null,
52− "rating": {
53− "type": "stars",
54− "range": [0, 5],
55− "full": "image://theme/rating-star-full",
56− "half": "image://theme/rating-star-half",
57− "empty": "image://theme/rating-star-empty"
58− },
59− "alt-rating": null,
60− "summary": null
61− },
62− "resources": {}
63− }'
64−
6530 property string cardData: '
6631 {
6732 "art": "../../tests/qmltests/Dash/artwork/music-player-design.png",
@@ -71,58 +36,61 @@ Rectangle {
7136 "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."
7237 }'
7338
74− property string fullMapping: '
75− {
76− "title": "title",
77− "art": "art",
78− "subtitle": "subtitle",
79− "mascot": "mascot",
80− "summary": "summary"
81− }'
82−
8339 property var cardsModel: [
8440 {
85− "name": "Art, header, summary - vertical",
86− "layout": { "components": JSON.parse(fullMapping) }
41+ "name": "Art, header, summary",
42+ "layout": { "components": JSON.parse(Helpers.fullMapping) }
8743 },
8844 {
89− "name": "Art, header, summary - vertical, small",
90− "layout": { "template": { "card-size": "small" }, "components": JSON.parse(fullMapping) }
45+ "name": "Art, header, summary - small",
46+ "layout": { "template": { "card-size": "small" }, "components": JSON.parse(Helpers.fullMapping) }
9147 },
9248 {
93− "name": "Art, header, summary - vertical, large",
94− "layout": { "template": { "card-size": "large" }, "components": JSON.parse(fullMapping) }
49+ "name": "Art, header, summary - large",
50+ "layout": { "template": { "card-size": "large" }, "components": JSON.parse(Helpers.fullMapping) }
9551 },
9652 {
97− "name": "Art, header, summary - vertical, wide",
98− "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "art": { "aspect-ratio": 2 } }) }
53+ "name": "Art, header, summary - wide",
54+ "layout": { "components": Helpers.update(JSON.parse(Helpers.fullMapping), { "art": { "aspect-ratio": 2 } }) }
9955 },
10056 {
101− "name": "Art, title - vertical, fitted",
102− "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "art": { "fill-mode": "fit" } }) }
57+ "name": "Art, title - fitted",
58+ "layout": { "components": Helpers.update(JSON.parse(Helpers.fullMapping), { "art": { "fill-mode": "fit" } }) }
10359 },
10460 {
10561 "name": "Art, header, summary - horizontal",
10662 "layout": { "template": { "card-layout": "horizontal" },
107− "components": JSON.parse(fullMapping) }
63+ "components": JSON.parse(Helpers.fullMapping) }
10864 },
10965 {
110− "name": "Art, header - vertical",
111− "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "summary": undefined }) }
66+ "name": "Art, header",
67+ "layout": { "components": Helpers.update(JSON.parse(Helpers.fullMapping), { "summary": undefined }) }
11268 },
11369 {
114− "name": "Header title only - horizontal",
115− "layout": { "template": { "card-layout": "horizontal" },
116− "components": { "title": "title" } }
70+ "name": "Art, summary",
71+ "layout": { "components": { "art": "art", "summary": "summary" } }
72+ },
73+ {
74+ "name": "Header title only",
75+ "layout": { "components": { "title": "title" } }
11776 },
11877 ]
11978
79+ CardTool {
80+ id: cardTool
81+ template: card.template
82+ components: card.components
83+ }
84+
12085 Card {
12186 id: card
12287 anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
12388
124− template: Helpers.update(JSON.parse(root.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
125− components: Helpers.update(JSON.parse(root.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
89+ width: cardTool.cardWidth || implicitWidth
90+ height: cardTool.cardHeight || implicitHeight
91+
92+ template: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
93+ components: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
12694 cardData: Helpers.mapData(dataArea.text, components, dataError)
12795 }
12896
@@ -245,8 +213,8 @@ Rectangle {
245213 function test_card_size_data() {
246214 return [
247215 { tag: "Medium", width: units.gu(18.5), index: 0 },
248− { tag: "Small", width: units.gu(12), size: "small", index: 0 },
249− { tag: "Large", width: units.gu(38), size: "large", index: 0 },
216+ { tag: "Small", width: units.gu(12), index: 1 },
217+ { tag: "Large", width: units.gu(38), index: 2 },
250218 { tag: "Wide", width: units.gu(18.5), aspect: 0.5, index: 0 },
251219 { tag: "Horizontal", width: units.gu(38), index: 5 },
252220 // Make sure card ends with header when there's no summary
@@ -356,13 +324,26 @@ Rectangle {
356324 tryCompareFunction(function() { return testCase.header.x === data.left() }, true);
357325 }
358326
327+ function test_summary_layout_data() {
328+ return [
329+ { tag: "With header", top: function() { return header.y + header.height }, index: 0 },
330+ { tag: "Without header", top: function() { return art.y + art.height }, index: 7 },
331+ ]
332+ }
333+
334+ function test_summary_layout(data) {
335+ selector.selectedIndex = data.index;
336+
337+ tryCompareFunction(function() { return testCase.summary.y === data.top() }, true);
338+ }
339+
359340 function test_art_visibility() {
360− selector.selectedIndex = 7
341+ selector.selectedIndex = 8;
361342
362− tryCompare(testCase.artImage, "source", "")
363− compare(testCase.art.visible, false)
364− compare(testCase.art.height, 0)
365− compare(testCase.art.width, 0)
343+ tryCompare(testCase.artImage, "source", "");
344+ compare(testCase.art.visible, false);
345+ compare(testCase.art.height, 0);
346+ compare(testCase.art.width, 0);
366347 }
367348 }
368349}
tests/qmltests/Dash/tst_CardHeader.qml+3−3
@@ -120,9 +120,9 @@ Rectangle {
120120
121121 function test_dimensions_data() {
122122 return [
123− { tag: "Column width", object: column, width: cardHeader.width - testCase.outerRow.spacing * 2 },
124− { tag: "Column width", object: column, width: cardHeader.width - mascot.width - testCase.outerRow.spacing * 3, mascot: "artwork/avatar.png" },
125− { tag: "Header height", object: cardHeader, height: function() { return subtitleLabel.y + subtitleLabel.height + outerRow.spacing * 2 } },
123+ { tag: "Column width", object: column, width: cardHeader.width },
124+ { tag: "Column width with mascot", object: column, width: cardHeader.width - mascot.width - outerRow.margins * 3, mascot: "artwork/avatar.png" },
125+ { tag: "Header height", object: cardHeader, height: function() { return subtitleLabel.y + subtitleLabel.height + outerRow.margins * 2 } },
126126 ]
127127 }
128128
tests/qmltests/Dash/tst_CardTool.qmlnew file+300
@@ -0,0 +1,300 @@
1+/*
2+ * Copyright (C) 2014 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 QtTest 1.0
19+import Ubuntu.Components 0.1
20+import Unity.Test 0.1 as UT
21+import "../../../qml/Dash"
22+import "CardHelpers.js" as Helpers
23+
24+Rectangle {
25+ id: root
26+ width: units.gu(80)
27+ height: units.gu(72)
28+ color: "#88FFFFFF"
29+
30+ property var cardsModel: [
31+ {
32+ "name": "Art, header, summary",
33+ "layout": { "components": JSON.parse(Helpers.fullMapping) }
34+ },
35+ {
36+ "name": "Header, summary",
37+ "layout": { "components": Helpers.update(JSON.parse(Helpers.fullMapping), { "art": undefined }) }
38+ },
39+ {
40+ "name": "Art, header",
41+ "layout": { "components": Helpers.update(JSON.parse(Helpers.fullMapping), { "summary": undefined }) }
42+ },
43+ {
44+ "name": "Header only",
45+ "layout": { "components": Helpers.update(JSON.parse(Helpers.fullMapping), { "art": undefined, "summary": undefined }) }
46+ },
47+ {
48+ "name": "Header title only",
49+ "layout": { "components": { "title": "title" } }
50+ },
51+ {
52+ "name": "Header title and subtitle",
53+ "layout": { "components": { "title": "title", "subtitle": "subtitle" } }
54+ },
55+ {
56+ "name": "Header title and mascot",
57+ "layout": { "components": { "title": "title", "mascot": "mascot" } }
58+ },
59+ {
60+ "name": "Art, header, summary - small",
61+ "layout": { "template": { "card-size": "small" }, "components": JSON.parse(Helpers.fullMapping) }
62+ },
63+ {
64+ "name": "Art, header, summary - large",
65+ "layout": { "template": { "card-size": "large" }, "components": JSON.parse(Helpers.fullMapping) }
66+ },
67+ {
68+ "name": "Art, header, summary - horizontal",
69+ "layout": { "template": { "card-layout": "horizontal" }, "components": JSON.parse(Helpers.fullMapping) }
70+ },
71+ {
72+ "name": "Art, header - portrait",
73+ "layout": { "components": Helpers.update(JSON.parse(Helpers.fullMapping), { "art": {"aspect-ratio": 0.5, "summary": undefined }}) }
74+ },
75+ ]
76+
77+ CardTool {
78+ id: cardTool
79+
80+ template: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
81+ components: Helpers.update(JSON.parse(Helpers.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
82+ viewWidth: units.gu(Math.round(widthSlider.value))
83+ }
84+
85+ Column {
86+ width: units.gu(38)
87+ anchors { left: parent.left; top: parent.top; margins: units.gu(1) }
88+
89+ Repeater {
90+ model: [
91+ { label: "View width", value: cardTool.viewWidth && cardTool.viewWidth / units.gu(1) },
92+ { label: "Card width", value: cardTool.cardWidth && cardTool.cardWidth / units.gu(1) },
93+ { label: "Card height", value: cardTool.cardHeight && cardTool.cardHeight / units.gu(1) },
94+ ]
95+
96+ delegate: Row {
97+ anchors { left: parent.left; right: parent.right; margins: units.gu(3) }
98+
99+ Label {
100+ height: units.gu(5)
101+ width: units.gu(25)
102+ text: modelData.label
103+ verticalAlignment: Text.AlignVCenter
104+ }
105+
106+ Label {
107+ height: units.gu(5)
108+ text: modelData.value || "undefined"
109+ verticalAlignment: Text.AlignVCenter
110+ }
111+ }
112+ }
113+
114+ Card {
115+ id: card
116+ template: cardTool.template;
117+ components: cardTool.components;
118+ cardData: Helpers.mapData(dataArea.text, components, dataError)
119+
120+ width: cardTool.cardWidth || implicitWidth
121+ height: cardTool.cardHeight || implicitHeight
122+
123+ clip: true
124+
125+ Rectangle {
126+ anchors.fill: parent
127+ color: "transparent"
128+ border.width: 1
129+ border.color: "green"
130+ }
131+ }
132+ }
133+
134+ Rectangle {
135+ anchors { top: parent.top; bottom: parent.bottom; right: parent.right}
136+ width: units.gu(40)
137+ color: "lightgrey"
138+
139+ Column {
140+ anchors { fill: parent; margins: units.gu(1) }
141+ spacing: units.gu(1)
142+
143+ OptionSelector {
144+ id: selector
145+ model: cardsModel
146+ delegate: OptionSelectorDelegate { text: modelData.name }
147+ onSelectedIndexChanged: updateAreas()
148+ Component.onCompleted: updateAreas()
149+
150+ function updateAreas() {
151+ var element = cardsModel[selectedIndex];
152+ if (element) {
153+ layoutArea.text = JSON.stringify(element.layout, undefined, 2) || "{}";
154+ } else {
155+ layoutArea.text = "";
156+ }
157+ }
158+ }
159+
160+ OptionSelector {
161+ id: layoutSelector
162+ model: [
163+ { label: "Grid", layout: "grid" },
164+ { label: "Organic Grid", layout: "organic-grid" },
165+ { label: "Horizontal Journal", layout: "journal" },
166+ { label: "Vertical Journal", layout: "vertical-journal" },
167+ { label: "Carousel", layout: "carousel" },
168+ ]
169+ delegate: OptionSelectorDelegate { text: modelData.label }
170+ onSelectedIndexChanged: {
171+ var element = model[selectedIndex];
172+ if (element && cardTool.template["category-layout"] !== element.layout) {
173+ var current = JSON.parse(layoutArea.text);
174+ var update = { "template": { "category-layout": element.layout }}
175+ layoutArea.text = JSON.stringify(Helpers.update(current, update), undefined, 2) || "{}";
176+ }
177+ }
178+ }
179+
180+ Label {
181+ anchors { left: parent.left; right: parent.right }
182+ height: units.gu(3)
183+ verticalAlignment: Text.AlignBottom
184+ text: "View width:"
185+ }
186+
187+ Slider {
188+ id: widthSlider
189+ minimumValue: 30
190+ maximumValue: 140
191+ live: true
192+ value: 40
193+ }
194+
195+ TextArea {
196+ id: layoutArea
197+ anchors { left: parent.left; right: parent.right }
198+ height: units.gu(25)
199+ }
200+
201+ Label {
202+ id: layoutError
203+ anchors { left: parent.left; right: parent.right }
204+ height: units.gu(4)
205+ color: "orange"
206+ }
207+
208+ TextArea {
209+ id: dataArea
210+ anchors { left: parent.left; right: parent.right }
211+ height: units.gu(25)
212+ text: JSON.stringify(cardTool.priv.cardData, undefined, 2)
213+ }
214+
215+ Label {
216+ id: dataError
217+ anchors { left: parent.left; right: parent.right }
218+ height: units.gu(4)
219+ color: "orange"
220+ }
221+ }
222+
223+ Connections {
224+ target: cardTool
225+ onTemplateChanged: {
226+ switch(cardTool.template["category-layout"]) {
227+ case "organic-grid": layoutSelector.selectedIndex = 1; break;
228+ case "journal": layoutSelector.selectedIndex = 2; break;
229+ case "vertical-journal": layoutSelector.selectedIndex = 3; break;
230+ case "carousel": layoutSelector.selectedIndex = 4; break;
231+ case "grid":
232+ default: layoutSelector.selectedIndex = 0; break;
233+ }
234+ }
235+ }
236+ }
237+
238+ UT.UnityTestCase {
239+ id: testCase
240+ name: "Card"
241+
242+ when: windowShown
243+
244+ function cleanup() {
245+ selector.selectedIndex = -1;
246+ layoutSelector.selectedIndex = -1;
247+ }
248+
249+ function test_card_size_data() {
250+ return [
251+ { tag: "Medium", width: units.gu(18.5), height: function() { return card.height }, index: 0 },
252+ { tag: "No art", width: units.gu(18.5), height: function() { return card.height }, index: 1 },
253+ { tag: "No summary", width: units.gu(18.5), height: function() { return card.height }, index: 2 },
254+ { tag: "Just header", width: units.gu(18.5), height: function() { return card.height }, index: 3 },
255+ { tag: "Just title", width: units.gu(18.5), height: function() { return card.height }, index: 4 },
256+ { tag: "Title and subtitle", width: units.gu(18.5), height: function() { return card.height }, index: 5 },
257+ { tag: "Title and mascot", width: units.gu(18.5), height: function() { return card.height }, index: 6 },
258+ { tag: "Small", width: units.gu(12), height: function() { return card.height }, index: 7 },
259+ { tag: "Large", width: units.gu(38), height: function() { return card.height }, index: 8 },
260+ { tag: "Horizontal", width: units.gu(38), height: function() { return card.height }, index: 9 },
261+ { tag: "OrganicGrid", width: undefined, height: undefined, index: 0, layout_index: 1},
262+ { tag: "Journal", width: undefined, height: units.gu(20), size: 20, index: 0, layout_index: 2 },
263+ { tag: "OversizedJournal", width: undefined, height: units.gu(18.5), size: 40, index: 0, layout_index: 2 },
264+ { tag: "VerticalJournal", width: units.gu(18.5), height: undefined, index: 0, layout_index: 3 },
265+ { tag: "SmallCarousel", width: units.gu(18), height: units.gu(18), viewWidth: 30, index: 0, layout_index: 4},
266+ { tag: "MediumCarousel", width: units.gu(22), height: units.gu(22), viewWidth: 84, index: 0, layout_index: 4},
267+ { tag: "LargeCarousel", width: units.gu(26), height: units.gu(26), viewWidth: 140, index: 0, layout_index: 4},
268+ { tag: "PortraitCarousel", width: units.gu(22), height: units.gu(44), viewWidth: 84, index: 10, layout_index: 4},
269+ ]
270+ }
271+
272+ function test_card_size(data) {
273+ selector.selectedIndex = data.index;
274+ if (data.hasOwnProperty("layout_index")) {
275+ layoutSelector.selectedIndex = data.layout_index;
276+ }
277+
278+ if (data.hasOwnProperty("viewWidth")) {
279+ widthSlider.value = data.viewWidth;
280+ }
281+
282+ if (data.hasOwnProperty("size")) {
283+ cardTool.template['card-size'] = data.size;
284+ cardTool.templateChanged();
285+ }
286+
287+ if (data.hasOwnProperty("width")) {
288+ if (typeof data.width === "function") {
289+ tryCompareFunction(function() { return cardTool.cardWidth === data.width() }, true);
290+ } else tryCompare(cardTool, "cardWidth", data.width);
291+ }
292+
293+ if (data.hasOwnProperty("height")) {
294+ if (typeof data.height === "function") {
295+ tryCompareFunction(function() { return cardTool.cardHeight === data.height() }, true);
296+ } else tryCompare(cardTool, "cardHeight", data.height);
297+ }
298+ }
299+ }
300+}