on xenial help sign in
Tests
56f8ab5Andrea Cimitan
qml/Components/Rating.qml+1−1
@@ -34,7 +34,7 @@ StyledItem {
34 //! Current rating.
34 //! Current rating.
35 property real value: -1
35 property real value: -1
36
36
37− readonly property int effectiveValue: MathUtils.clamp(size * value / maximumValue, 0, maximumValue)
37+ readonly property real effectiveValue: MathUtils.clamp(size * value / maximumValue, 0, maximumValue)
38
38
39 style: RatingStyle {}
39 style: RatingStyle {}
40
40
qml/Components/RatingStyle.qml+17−1
@@ -21,11 +21,27 @@ Row {
21 anchors.fill: parent
21 anchors.fill: parent
22
22
23 Repeater {
23 Repeater {
24+ id: repeater
24 model: styledItem.size
25 model: styledItem.size
25
26
27+ property int indexHalfValue: {
28+ var integerPart = Math.floor(styledItem.effectiveValue);
29+ var fractionalPart = styledItem.effectiveValue - integerPart;
30+
31+ if (fractionalPart < 0.5) return -1;
32+ else return integerPart;
33+ }
34+ property url urlIconEmpty: styledItem.urlIconEmpty || "graphics/icon_star_empty.png"
35+ property url urlIconFull: styledItem.urlIconFull || "graphics/icon_star_full.png"
36+ property url urlIconHalf: styledItem.urlIconHalf || "graphics/icon_star_half.png"
37+
26 Image {
38 Image {
27 opacity: styledItem.value < 0 ? 0.4 : 1 // Let's make the stars look inactive for a not-set value
39 opacity: styledItem.value < 0 ? 0.4 : 1 // Let's make the stars look inactive for a not-set value
40+ source: {
41+ if (index === repeater.indexHalfValue) return repeater.urlIconHalf;
28− source: index < styledItem.effectiveValue ? "graphics/icon_star_on.png" : "graphics/icon_star_off.png"
42+ else if (index < styledItem.effectiveValue) return repeater.urlIconFull;
43+ else return repeater.urlIconEmpty;
44+ }
29 }
45 }
30 }
46 }
31}
47}
qml/Components/graphics/icon_star_empty@20.pngnew file

Binary file not shown.

qml/Components/graphics/icon_star_full@20.pngnew file

Binary file not shown.

qml/Components/graphics/icon_star_half@20.pngnew file

Binary file not shown.

qml/Components/graphics/icon_star_off@20.pngdeleted

Binary file not shown.

qml/Components/graphics/icon_star_on@20.pngdeleted

Binary file not shown.

qml/Dash/Previews/PreviewRating.qml+82−17
@@ -25,15 +25,15 @@ PreviewWidget {
25
25
26 function submit() {
26 function submit() {
27 // checks rating-input requirements
27 // checks rating-input requirements
28− if ((widgetData["required"] === "both" ||
28+ if (((widgetData["required"] === "both" ||
29− widgetData["required"] === "rating") &&
29+ widgetData["required"] === "rating") &&
30− rating.value < 0) return;
30+ rating.value < 0) ||
31− if ((widgetData["required"] === "both" ||
31+ ((widgetData["required"] === "both" ||
32− widgetData["required"] === "review") &&
32+ widgetData["required"] === "review") &&
33− reviewTextArea.text === "") return;
33+ reviewField.text === "")) return;
34−
34+
35− var data = [{"rating": rating.value, "review": reviewTextArea.text, "author": null}]
35+ var data = {"rating": rating.value, "review": reviewField.text, "author": null};
36− triggered(root.widgetId, null, data)
36+ triggered(root.widgetId, widgetData["required"], data);
37 }
37 }
38
38
39 Item {
39 Item {
@@ -41,41 +41,106 @@ PreviewWidget {
41 anchors {
41 anchors {
42 left: parent.left
42 left: parent.left
43 right: parent.right
43 right: parent.right
44− margins: units.gu(1)
45 }
44 }
46 height: childrenRect.height
45 height: childrenRect.height
47 visible: widgetData["visible"] !== "review"
46 visible: widgetData["visible"] !== "review"
48
47
49 Label {
48 Label {
49+ objectName: "ratingLabel"
50 anchors {
50 anchors {
51− top: parent.top
51+ verticalCenter: parent.verticalCenter
52 left: parent.left
52 left: parent.left
53 }
53 }
54 color: Theme.palette.selected.backgroundText
54 color: Theme.palette.selected.backgroundText
55 opacity: .8
55 opacity: .8
56− text: widgetData["review-label"]
56+ text: widgetData["rating-label"] || i18n.tr("Rate this")
57 }
57 }
58
58
59 Rating {
59 Rating {
60 id: rating
60 id: rating
61+ objectName: "rating"
61 anchors {
62 anchors {
62− top: parent.top
63+ verticalCenter: parent.verticalCenter
63 right: parent.right
64 right: parent.right
64 }
65 }
65 size: 5
66 size: 5
66− onValueChanged: submit()
67+ onValueChanged: {
68+ if (widgetData["visible"] === "rating") root.submit();
69+ }
70+
71+ property var urlIconEmpty: widgetData["rating-icon-empty"]
72+ property var urlIconFull: widgetData["rating-icon-full"]
67 }
73 }
68 }
74 }
69
75
70− TextArea {
76+ Item {
71− id: reviewTextArea
77+ id: reviewContainer
78+
79+ readonly property real innerMargin: units.gu(1)
80+
72 anchors {
81 anchors {
73 left: parent.left
82 left: parent.left
74 right: parent.right
83 right: parent.right
75 top: ratingLabelAndWidgetContainer.visible ? ratingLabelAndWidgetContainer.bottom : parent.top
84 top: ratingLabelAndWidgetContainer.visible ? ratingLabelAndWidgetContainer.bottom : parent.top
76 bottom: parent.bottom
85 bottom: parent.bottom
86+ topMargin: ratingLabelAndWidgetContainer.visible ? reviewContainer.innerMargin : 0
77 }
87 }
78 visible: widgetData["visible"] !== "rating"
88 visible: widgetData["visible"] !== "rating"
89+
90+ Label {
91+ objectName: "reviewLabel"
92+ id: reviewLabel
93+ anchors {
94+ top: parent.top
95+ left: parent.left
96+ right: parent.right
97+ }
79− color: Theme.palette.selected.backgroundText
98+ color: Theme.palette.selected.backgroundText
99+ opacity: .8
100+ text: widgetData["review-label"] || i18n.tr("Add a review")
101+ }
102+
103+ Item {
104+ anchors {
105+ top: reviewLabel.bottom
106+ left: parent.left
107+ right: parent.right
108+ topMargin: reviewContainer.innerMargin
109+ }
110+ implicitHeight: childrenRect.height
111+
112+ TextField {
113+ id: reviewField
114+ objectName: "reviewField"
115+ anchors {
116+ top: parent.top
117+ bottom: parent.bottom
118+ left: parent.left
119+ right: submitButton.left
120+ rightMargin: reviewContainer.innerMargin
121+ }
122+ }
123+
124+ Button {
125+ id: submitButton
126+ objectName: "submitButton"
127+
128+ readonly property bool readyToSubmit: {
129+ if (reviewField.text === "" ||
130+ (widgetData["visible"] === "both" && rating.value < 0)) return false;
131+ else return true;
132+ }
133+
134+ anchors {
135+ top: parent.top
136+ right: parent.right
137+ }
138+ color: readyToSubmit ? Theme.palette.selected.base : Theme.palette.normal.base
139+ text: widgetData["submit-label"] || i18n.tr("Send")
140+ onClicked: {
141+ if (readyToSubmit) root.submit()
142+ }
143+ }
144+ }
80 }
145 }
81}
146}
tests/qmltests/CMakeLists.txt−1
@@ -60,7 +60,6 @@ add_qml_test(Dash/Previews PreviewActions IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_
60add_qml_test(Dash/Previews PreviewAudioPlayback IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
60add_qml_test(Dash/Previews PreviewAudioPlayback IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
61add_qml_test(Dash/Previews PreviewHeader IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
61add_qml_test(Dash/Previews PreviewHeader IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
62add_qml_test(Dash/Previews PreviewImageGallery IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
62add_qml_test(Dash/Previews PreviewImageGallery IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
63−add_qml_test(Dash/Previews PreviewHeader IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
64add_qml_test(Dash/Previews PreviewProgress IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
63add_qml_test(Dash/Previews PreviewProgress IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
65add_qml_test(Dash/Previews PreviewRating IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
64add_qml_test(Dash/Previews PreviewRating IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
66add_qml_test(Dash/Previews PreviewTextSummary IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
65add_qml_test(Dash/Previews PreviewTextSummary IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/tests/mocks)
tests/qmltests/Components/tst_Rating.qml+3−3
@@ -24,7 +24,7 @@ Rectangle {
24 id: root
24 id: root
25 width: units.gu(40)
25 width: units.gu(40)
26 height: units.gu(10)
26 height: units.gu(10)
27− color: "black"
27+ color: Theme.palette.selected.background
28
28
29 Rating {
29 Rating {
30 id: rating
30 id: rating
@@ -73,13 +73,13 @@ Rectangle {
73 {tag: "ranged value", value: 2, expectedValue: 2},
73 {tag: "ranged value", value: 2, expectedValue: 2},
74 {tag: "big", value: 200, expectedValue: rating.maximumValue},
74 {tag: "big", value: 200, expectedValue: rating.maximumValue},
75 {tag: "min", value: 0, expectedValue: 0},
75 {tag: "min", value: 0, expectedValue: 0},
76− {tag: "max", value: rating.maximumValue, expectedValue: rating.maximumValue}
76+ {tag: "max", value: rating.maximumValue, expectedValue: rating.maximumValue},
77+ {tag: "half", value: 2.5, expectedValue: 2.5}
77 ];
78 ];
78 }
79 }
79
80
80 function test_effectiveValue(data) {
81 function test_effectiveValue(data) {
81 rating.value = data.value;
82 rating.value = data.value;
82− wait(1000)
83 compare(rating.effectiveValue, data.expectedValue, "effectiveValue not calculated correctly")
83 compare(rating.effectiveValue, data.expectedValue, "effectiveValue not calculated correctly")
84 }
84 }
85 }
85 }
tests/qmltests/Dash/Previews/tst_PreviewRating.qml+146−2
@@ -24,15 +24,159 @@ Rectangle {
24 id: root
24 id: root
25 width: units.gu(40)
25 width: units.gu(40)
26 height: units.gu(80)
26 height: units.gu(80)
27− color: "black" //Theme.palette.selected.background
27+ color: Theme.palette.selected.background
28+
29+ property real validInputRating: 1
30+ property real invalidInputRating: -1
31+ property string validInputText: "Great!"
32+ property string invalidInputText: ""
33+
34+ property var triggeredDataBoth: { "rating": validInputRating, "review": validInputText, "author": null }
35+ property var triggeredDataRatingOnly: { "rating": validInputRating, "review": invalidInputText, "author": null }
36+ property var triggeredDataReviewOnly: { "rating": invalidInputRating, "review": validInputText, "author": null }
37+
38+ property var widgetDataBoth: { "visible": "both", "required": "both" }
39+ property var widgetDataRating: { "visible": "rating", "required": "rating" }
40+ property var widgetDataRatingBroken: { "visible": "rating", "required": "review" }
41+ property var widgetDataReview: { "visible": "review", "required": "review" }
42+ property var widgetDataReviewBroken: { "visible": "review", "required": "rating" }
43+
44+ property var widgetDataNewLabels: { "visible": "both", "required": "both", "rating-label": "TestRatingLabel", "review-label": "TestReviewLabel", "submit-label": "TestSubmitLabel" }
28
45
29 PreviewRating {
46 PreviewRating {
30 id: previewRating
47 id: previewRating
31 anchors.fill: parent
48 anchors.fill: parent
49+ widgetData: widgetDataBoth
50+ widgetId: "previewRating"
51+ }
52+
53+ SignalSpy {
54+ id: spy
55+ signalName: "triggered"
32 }
56 }
33
57
34 UT.UnityTestCase {
58 UT.UnityTestCase {
35− name: "PreviewTextSummaryTest"
59+ name: "PreviewRatingTest"
36 when: windowShown
60 when: windowShown
61+
62+ property var rating: findChild(previewRating, "rating")
63+ property var reviewField: findChild(previewRating, "reviewField")
64+ property var submitButton: findChild(previewRating, "submitButton")
65+
66+ function init() {
67+ rating.value = -1;
68+ reviewField.text = "";
69+ }
70+
71+ function test_labels() {
72+ var ratingLabel = findChild(previewRating, "ratingLabel");
73+ var reviewLabel = findChild(previewRating, "reviewLabel");
74+
75+ previewRating.widgetData = widgetDataNewLabels;
76+ compare(ratingLabel.text, widgetDataNewLabels["rating-label"]);
77+ compare(reviewLabel.text, widgetDataNewLabels["review-label"]);
78+ compare(submitButton.text, widgetDataNewLabels["submit-label"]);
79+ }
80+
81+ function test_submit_and_visibility_data() {
82+ return [
83+ {tag: "both, null review", widgetData: widgetDataBoth, inputRating: invalidInputRating, inputText: invalidInputText, emitted: false},
84+ {tag: "both, rating-only review", widgetData: widgetDataBoth, inputRating: validInputRating, inputText: invalidInputText, emitted: false},
85+ {tag: "both, review-only review", widgetData: widgetDataBoth, inputRating: invalidInputRating, inputText: validInputText, emitted: false},
86+ {tag: "both, complete review", widgetData: widgetDataBoth, inputRating: validInputRating, inputText: validInputText, emitted: true},
87+ {tag: "rating, null review", widgetData: widgetDataRating, inputRating: invalidInputRating, inputText: invalidInputText, emitted: false},
88+ {tag: "rating, rating-only review", widgetData: widgetDataRating, inputRating: validInputRating, inputText: invalidInputText, emitted: true},
89+ {tag: "rating, review-only review", widgetData: widgetDataRating, inputRating: invalidInputRating, inputText: validInputText, emitted: false},
90+ {tag: "rating, complete review", widgetData: widgetDataRating, inputRating: validInputRating, inputText: validInputText, emitted: true},
91+ {tag: "rating broken, null review", widgetData: widgetDataRatingBroken, inputRating: invalidInputRating, inputText: invalidInputText, emitted: false},
92+ {tag: "rating broken, rating-only review", widgetData: widgetDataRatingBroken, inputRating: validInputRating, inputText: invalidInputText, emitted: false},
93+ {tag: "rating broken, review-only review", widgetData: widgetDataRatingBroken, inputRating: invalidInputRating, inputText: validInputText, emitted: false},
94+ {tag: "rating broken, complete review", widgetData: widgetDataRatingBroken, inputRating: validInputRating, inputText: validInputText, emitted: false},
95+ {tag: "review, null review", widgetData: widgetDataReview, inputRating: invalidInputRating, inputText: invalidInputText, emitted: false},
96+ {tag: "review, rating-only review", widgetData: widgetDataReview, inputRating: validInputRating, inputText: invalidInputText, emitted: false},
97+ {tag: "review, review-only review", widgetData: widgetDataReview, inputRating: invalidInputRating, inputText: validInputText, emitted: true},
98+ {tag: "review, complete review", widgetData: widgetDataReview, inputRating: validInputRating, inputText: validInputText, emitted: true},
99+ {tag: "review broken, null review", widgetData: widgetDataReviewBroken, inputRating: invalidInputRating, inputText: invalidInputText, emitted: false},
100+ {tag: "review broken, rating-only review", widgetData: widgetDataReviewBroken, inputRating: validInputRating, inputText: invalidInputText, emitted: false},
101+ {tag: "review broken, review-only review", widgetData: widgetDataReviewBroken, inputRating: invalidInputRating, inputText: validInputText, emitted: false},
102+ {tag: "review broken, complete review", widgetData: widgetDataReviewBroken, inputRating: validInputRating, inputText: validInputText, emitted: false},
103+ ];
104+ }
105+
106+ function test_submit_and_visibility(data) {
107+ spy.clear();
108+ spy.target = previewRating;
109+
110+ previewRating.widgetData = data.widgetData;
111+
112+ if (data.widgetData["visible"] !== "review") {
113+ verify(rating.visible === true);
114+
115+ rating.value = data.inputRating;
116+ if (data.widgetData["required"] !== "rating" || data.inputRating < 0) {
117+ compare(spy.count, 0);
118+ } else {
119+ compare(spy.count, 1);
120+ }
121+ } else {
122+ verify(rating.visible === false);
123+ }
124+
125+ if (data.widgetData["visible"] !== "rating") {
126+ verify(reviewField.visible === true);
127+
128+ reviewField.text = data.inputText;
129+ mouseClick(submitButton, submitButton.width / 2, submitButton.height / 2);
130+ switch (data.widgetData["required"]) {
131+ case "rating": compare(spy.count, 0); break;
132+ case "both":
133+ default: {
134+ if (data.inputRating < 0 || data.inputText === "") {
135+ compare(spy.count, 0); break;
136+ }
137+ }
138+ case "review": {
139+ if (data.inputText === "") {
140+ compare(spy.count, 0); break;
141+ } else {
142+ compare(spy.count, 1); break;
143+ }
144+ }
145+ }
146+ } else {
147+ verify(reviewField.visible === false);
148+ }
149+
150+ compare(spy.count === 1, data.emitted);
151+ }
152+
153+ function test_triggered_data() {
154+ return [
155+ {tag: "complete review", widgetData: widgetDataBoth, inputRating: validInputRating, inputText: validInputText, triggeredData: triggeredDataBoth},
156+ {tag: "rating-only review", widgetData: widgetDataRating, inputRating: validInputRating, inputText: invalidInputText, triggeredData: triggeredDataRatingOnly},
157+ {tag: "review-only review", widgetData: widgetDataReview, inputRating: invalidInputRating, inputText: validInputText, triggeredData: triggeredDataReviewOnly}
158+ ];
159+ }
160+
161+ function test_triggered(data) {
162+ spy.clear();
163+ spy.target = previewRating;
164+
165+ previewRating.widgetData = data.widgetData;
166+
167+ if (data.inputRating > 0) rating.value = data.inputRating;
168+ if (data.inputText !== "") {
169+ reviewField.text = data.inputText;
170+ mouseClick(submitButton, submitButton.width / 2, submitButton.height / 2);
171+ }
172+
173+ compare(spy.count, 1);
174+ var args = spy.signalArguments[0];
175+ compare(args[0], previewRating.widgetId);
176+ compare(args[1], data.widgetData["required"]);
177+ compare(args[2]["rating"], data.triggeredData["rating"]);
178+ compare(args[2]["review"], data.triggeredData["review"]);
179+ verify(args[2]["author"]); // Just verifying it exists now
180+ }
37 }
181 }
38}
182}