on xenial help sign in
merged with parent
3938582Nick Dedekind
@@ -166,3 +166,32 @@ $ make tryDragHandle
166NB: Manual test or interation doesn't make sense for all components or classes,
166NB: Manual test or interation doesn't make sense for all components or classes,
167particularly the non-graphical ones. In such cases no "tryFoo" make target is
167particularly the non-graphical ones. In such cases no "tryFoo" make target is
168provided.
168provided.
169+
170+NB: The tests expect the locale to be the C locale, they may fail if run
171+under a different locale, if you get failures others can not reproduce
172+prepend 'LC_ALL=C' to the 'make test' command
173+
174+Running autopilot tests
175+=======================
176+
177+Autopilot tests are run from the build directory (which will be ./builddir if you used
178+the ./build script).
179+
180+$ make autopilot
181+
182+If you want you can also run tests individually, for that you need to install first,
183+otherwise the tests will run on the system-installed version
184+
185+$ make install
186+
187+To see the list of existing autopilot tests you can do
188+
189+$ PYTHONPATH=../tests/autopilot autopilot list unity8
190+
191+And then run a single test with
192+
193+$ PYTHONPATH=../tests/autopilot autopilot run unity8.some.very.long.test.name
194+
195+or run them all with
196+
197+$ PYTHONPATH=../tests/autopilot autopilot run unity8
cmake/modules/autopilot.cmake+5−1
@@ -4,7 +4,11 @@ function(declare_autopilot_test TEST_NAME TEST_SUITE WORKING_DIR)
4 add_custom_target(autopilot-${TEST_NAME}
4 add_custom_target(autopilot-${TEST_NAME}
5 COMMAND LANG=C UBUNTU_ICON_THEME=ubuntu-mobile QML2_IMPORT_PATH=${SHELL_INSTALL_QML}/mocks autopilot run ${TEST_SUITE}
5 COMMAND LANG=C UBUNTU_ICON_THEME=ubuntu-mobile QML2_IMPORT_PATH=${SHELL_INSTALL_QML}/mocks autopilot run ${TEST_SUITE}
6 WORKING_DIRECTORY ${WORKING_DIR}
6 WORKING_DIRECTORY ${WORKING_DIR}
7− DEPENDS install
7+ DEPENDS fake_install
8+ )
9+
10+ add_custom_target(fake_install
11+ COMMAND cmake --build ${CMAKE_BINARY_DIR} --target install
8 )
12 )
9
13
10 add_dependencies(autopilot autopilot-${TEST_NAME})
14 add_dependencies(autopilot autopilot-${TEST_NAME})
debian/control+1
@@ -104,6 +104,7 @@ Depends: gir1.2-glib-2.0,
104 libqt5widgets5,
104 libqt5widgets5,
105 python-evdev,
105 python-evdev,
106 python-gi,
106 python-gi,
107+ python-mock,
107 unity8 (>= ${source:Version}),
108 unity8 (>= ${source:Version}),
108 unity8-fake-env (>= ${source:Version}),
109 unity8-fake-env (>= ${source:Version}),
109 ${misc:Depends},
110 ${misc:Depends},
plugins/DashViews/horizontaljournal.cpp+2−2
@@ -172,7 +172,7 @@ void HorizontalJournal::cleanupExistingItems()
172 releaseItem(item);
172 releaseItem(item);
173 m_visibleItems.clear();
173 m_visibleItems.clear();
174 m_lastInRowIndexPosition.clear();
174 m_lastInRowIndexPosition.clear();
175− m_firstVisibleIndex = 0;
175+ m_firstVisibleIndex = -1;
176}
176}
177
177
178void HorizontalJournal::calculateImplicitHeight()
178void HorizontalJournal::calculateImplicitHeight()
@@ -241,7 +241,7 @@ void HorizontalJournal::doRelayout()
241 }
241 }
242 m_visibleItems.clear();
242 m_visibleItems.clear();
243 m_lastInRowIndexPosition.clear();
243 m_lastInRowIndexPosition.clear();
244− m_firstVisibleIndex = 0;
244+ m_firstVisibleIndex = -1;
245 }
245 }
246}
246}
247
247
po/ca.po+7−7
@@ -8,15 +8,15 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-09-28 07:38+0000\n"
11+"PO-Revision-Date: 2014-01-10 07:45+0000\n"
12−"Last-Translator: David Planella <david.planella@ubuntu.com>\n"
12+"Last-Translator: Adolfo Jayme <fitoschido@gmail.com>\n"
13"Language-Team: Catalan <ca@li.org>\n"
13"Language-Team: Catalan <ca@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
17"Plural-Forms: nplurals=2; plural=n != 1;\n"
18−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
18+"X-Launchpad-Export-Date: 2014-01-11 06:38+0000\n"
19−"X-Generator: Launchpad (build 16877)\n"
19+"X-Generator: Launchpad (build 16890)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
@@ -143,7 +143,7 @@ msgstr "S'està parlant..."
143
143
144#: Notifications/NotificationMenuItemFactory.qml:89
144#: Notifications/NotificationMenuItemFactory.qml:89
145msgid "Show password"
145msgid "Show password"
146−msgstr ""
146+msgstr "Mostra la contrasenya"
147
147
148#: Panel/SearchIndicator.qml:27
148#: Panel/SearchIndicator.qml:27
149msgid "Search"
149msgid "Search"
@@ -152,11 +152,11 @@ msgstr "Cerca"
152#: plugins/Unity/Launcher/launcheritem.cpp:43
152#: plugins/Unity/Launcher/launcheritem.cpp:43
153#: plugins/Unity/Launcher/launcheritem.cpp:73
153#: plugins/Unity/Launcher/launcheritem.cpp:73
154msgid "Lock to Launcher"
154msgid "Lock to Launcher"
155−msgstr ""
155+msgstr "Mantén-la al llançador"
156
156
157#: plugins/Unity/Launcher/launcheritem.cpp:73
157#: plugins/Unity/Launcher/launcheritem.cpp:73
158msgid "Unlock from Launcher"
158msgid "Unlock from Launcher"
159−msgstr ""
159+msgstr "No la mantinguis al llançador"
160
160
161#: Shell.qml:439
161#: Shell.qml:439
162#, qt-format
162#, qt-format
po/lv.po+6−6
@@ -8,15 +8,15 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-09-28 16:29+0000\n"
11+"PO-Revision-Date: 2014-01-11 16:48+0000\n"
12"Last-Translator: Jānis-Marks Gailis <jeanmarc.gailis@gmail.com>\n"
12"Last-Translator: Jānis-Marks Gailis <jeanmarc.gailis@gmail.com>\n"
13"Language-Team: Latvian <lv@li.org>\n"
13"Language-Team: Latvian <lv@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n"
17"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n"
18−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
18+"X-Launchpad-Export-Date: 2014-01-12 05:52+0000\n"
19−"X-Generator: Launchpad (build 16877)\n"
19+"X-Generator: Launchpad (build 16890)\n"
20
20
21#: Components/EdgeDemoOverlay.qml:151
21#: Components/EdgeDemoOverlay.qml:151
22msgid "Skip intro"
22msgid "Skip intro"
@@ -32,7 +32,7 @@ msgstr ""
32
32
33#: Components/EdgeDemo.qml:144
33#: Components/EdgeDemo.qml:144
34msgid "Top edge"
34msgid "Top edge"
35−msgstr ""
35+msgstr "Augšējā mala"
36
36
37#: Components/EdgeDemo.qml:145
37#: Components/EdgeDemo.qml:145
38msgid "Try swiping from the top edge to access the indicators"
38msgid "Try swiping from the top edge to access the indicators"
@@ -40,7 +40,7 @@ msgstr ""
40
40
41#: Components/EdgeDemo.qml:170
41#: Components/EdgeDemo.qml:170
42msgid "Close"
42msgid "Close"
43−msgstr ""
43+msgstr "Aizvērt"
44
44
45#: Components/EdgeDemo.qml:171
45#: Components/EdgeDemo.qml:171
46msgid "Swipe up again to close the settings screen"
46msgid "Swipe up again to close the settings screen"
@@ -48,7 +48,7 @@ msgstr ""
48
48
49#: Components/EdgeDemo.qml:198
49#: Components/EdgeDemo.qml:198
50msgid "Left edge"
50msgid "Left edge"
51−msgstr ""
51+msgstr "Kreisā mala"
52
52
53#: Components/EdgeDemo.qml:199
53#: Components/EdgeDemo.qml:199
54msgid "Swipe from the left to reveal the launcher for quick access to apps"
54msgid "Swipe from the left to reveal the launcher for quick access to apps"
po/pl.po+7−7
@@ -8,16 +8,16 @@ msgstr ""
8"Project-Id-Version: unity\n"
8"Project-Id-Version: unity\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
9"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
10"POT-Creation-Date: 2013-11-29 09:21+0100\n"
11−"PO-Revision-Date: 2013-10-13 13:44+0000\n"
11+"PO-Revision-Date: 2014-01-10 11:38+0000\n"
12−"Last-Translator: GTriderXC <gtriderxc@yahoo.com>\n"
12+"Last-Translator: Michał Sawicz <michal.sawicz@canonical.com>\n"
13"Language-Team: Polish <pl@li.org>\n"
13"Language-Team: Polish <pl@li.org>\n"
14"MIME-Version: 1.0\n"
14"MIME-Version: 1.0\n"
15"Content-Type: text/plain; charset=UTF-8\n"
15"Content-Type: text/plain; charset=UTF-8\n"
16"Content-Transfer-Encoding: 8bit\n"
16"Content-Transfer-Encoding: 8bit\n"
17"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
17"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 "
18"|| n%100>=20) ? 1 : 2;\n"
18"|| n%100>=20) ? 1 : 2;\n"
19−"X-Launchpad-Export-Date: 2013-12-29 06:04+0000\n"
19+"X-Launchpad-Export-Date: 2014-01-11 06:38+0000\n"
20−"X-Generator: Launchpad (build 16877)\n"
20+"X-Generator: Launchpad (build 16890)\n"
21
21
22#: Components/EdgeDemoOverlay.qml:151
22#: Components/EdgeDemoOverlay.qml:151
23msgid "Skip intro"
23msgid "Skip intro"
@@ -143,7 +143,7 @@ msgstr "Rozpoznanie..."
143
143
144#: Notifications/NotificationMenuItemFactory.qml:89
144#: Notifications/NotificationMenuItemFactory.qml:89
145msgid "Show password"
145msgid "Show password"
146−msgstr ""
146+msgstr "Wyświetl hasło"
147
147
148#: Panel/SearchIndicator.qml:27
148#: Panel/SearchIndicator.qml:27
149msgid "Search"
149msgid "Search"
@@ -152,11 +152,11 @@ msgstr "Szukaj"
152#: plugins/Unity/Launcher/launcheritem.cpp:43
152#: plugins/Unity/Launcher/launcheritem.cpp:43
153#: plugins/Unity/Launcher/launcheritem.cpp:73
153#: plugins/Unity/Launcher/launcheritem.cpp:73
154msgid "Lock to Launcher"
154msgid "Lock to Launcher"
155−msgstr ""
155+msgstr "Dodaj do panelu uruchamiania"
156
156
157#: plugins/Unity/Launcher/launcheritem.cpp:73
157#: plugins/Unity/Launcher/launcheritem.cpp:73
158msgid "Unlock from Launcher"
158msgid "Unlock from Launcher"
159−msgstr ""
159+msgstr "Usuń z panelu uruchamiania"
160
160
161#: Shell.qml:439
161#: Shell.qml:439
162#, qt-format
162#, qt-format
qml/Dash/Card.qmlnew file+83
@@ -0,0 +1,83 @@
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+Item {
21+ id: root
22+ property var template
23+ property var components
24+ property var cardData
25+
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
37+
38+ UbuntuShape {
39+ id: artShape
40+ objectName: "artShape"
41+ width: image.fillMode === Image.PreserveAspectCrop || aspect < image.aspect ? image.width : height * image.aspect
42+ height: image.fillMode === Image.PreserveAspectCrop || aspect > image.aspect ? image.height : width / image.aspect
43+ anchors.horizontalCenter: template && template["card-layout"] === "horizontal" ? undefined : parent.horizontalCenter
44+
45+ property real aspect: components !== undefined ? components["art"]["aspect-ratio"] : 1
46+
47+ image: Image {
48+ width: template && template["card-layout"] === "horizontal" ? height * artShape.aspect : root.width
49+ height: template && template["card-layout"] === "horizontal" ? header.height : width / artShape.aspect
50+ objectName: "artImage"
51+ source: cardData && cardData["art"] || ""
52+ // FIXME uncomment when having investigated / fixed the crash
53+ //sourceSize.width: width > height ? width : 0
54+ //sourceSize.height: height > width ? height : 0
55+ fillMode: components["art"]["fill-mode"] === "fit" ? Image.PreserveAspectFit: Image.PreserveAspectCrop
56+
57+ property real aspect: implicitWidth / implicitHeight
58+ }
59+ }
60+
61+ CardHeader {
62+ id: header
63+ objectName: "cardHeader"
64+ anchors {
65+ top: template && template["card-layout"] === "horizontal" ? artShape.top : artShape.bottom
66+ left: template && template["card-layout"] === "horizontal" ? artShape.right : parent.left
67+ right: parent.right
68+ }
69+
70+ mascot: cardData && cardData["mascot"] || ""
71+ title: cardData && cardData["title"] || ""
72+ subtitle: cardData && cardData["subtitle"] || ""
73+ }
74+
75+ Label {
76+ objectName: "summaryLabel"
77+ anchors { top: header.bottom; left: parent.left; right: parent.right }
78+ wrapMode: Text.Wrap
79+ maximumLineCount: 5
80+ elide: Text.ElideRight
81+ text: cardData && cardData["summary"] || ""
82+ }
83+}
qml/Dash/CardHeader.qmlnew file+112
@@ -0,0 +1,112 @@
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+Item {
21+ id: root
22+ property alias mascot: mascotImage.source
23+ property alias title: titleLabel.text
24+ property alias subtitle: subtitleLabel.text
25+ property alias price: priceLabel.text
26+ property alias oldPrice: oldPriceLabel.text
27+ property alias altPrice: altPriceLabel.text
28+
29+ visible: mascotImage.status === Image.Ready || title || price
30+ height: row.height > 0 ? row.height + row.spacing * 2 : 0
31+
32+ Row {
33+ id: row
34+ objectName: "outerRow"
35+
36+ anchors { top: parent.top; left: parent.left; right: parent.right; margins: spacing }
37+ spacing: units.gu(1)
38+
39+ UbuntuShape {
40+ id: mascotShape
41+ objectName: "mascotShape"
42+
43+ width: units.gu(8)
44+ height: units.gu(8)
45+ visible: image.status === Image.Ready
46+
47+ image: Image {
48+ id: mascotImage
49+ sourceSize { width: mascotShape.width; height: mascotShape.height }
50+ }
51+ }
52+
53+ Column {
54+ objectName: "column"
55+ width: parent.width - x
56+
57+ Label {
58+ id: titleLabel
59+ anchors { left: parent.left; right: parent.right }
60+ elide: Text.ElideRight
61+ objectName: "titleLabel"
62+ font.weight: Font.DemiBold
63+ wrapMode: Text.Wrap
64+ maximumLineCount: 2
65+ }
66+
67+ Label {
68+ id: subtitleLabel
69+ anchors { left: parent.left; right: parent.right }
70+ elide: Text.ElideRight
71+ visible: titleLabel.text && text
72+ }
73+
74+ Row {
75+ id: prices
76+ objectName: "prices"
77+ anchors { left: parent.left; right: parent.right }
78+
79+ property int labels: {
80+ var labels = 1; // price always visible
81+ if (oldPriceLabel.text !== "") labels += 1;
82+ if (altPriceLabel.text !== "") labels += 1;
83+ return labels;
84+ }
85+ property real labelWidth: width / labels
86+
87+ Label {
88+ id: priceLabel
89+ width: parent.labelWidth
90+ elide: Text.ElideRight
91+ font.weight: Font.DemiBold
92+ color: Theme.palette.selected.foreground
93+ }
94+
95+ Label {
96+ id: oldPriceLabel
97+ objectName: "oldPriceLabel"
98+ width: parent.labelWidth
99+ elide: Text.ElideRight
100+ horizontalAlignment: parent.labels === 3 ? Text.AlignHCenter : Text.AlignRight
101+ }
102+
103+ Label {
104+ id: altPriceLabel
105+ width: parent.labelWidth
106+ elide: Text.ElideRight
107+ horizontalAlignment: Text.AlignRight
108+ }
109+ }
110+ }
111+ }
112+}
qml/Panel/IndicatorItem.qml+7
@@ -26,6 +26,7 @@ Item {
26 property bool dimmed: false
26 property bool dimmed: false
27 property var indicatorProperties: undefined
27 property var indicatorProperties: undefined
28 property bool indicatorVisible: loader.item ? loader.item.enabled : false
28 property bool indicatorVisible: loader.item ? loader.item.enabled : false
29+ property string identifier
29
30
30 opacity: dimmed ? 0.4 : 1
31 opacity: dimmed ? 0.4 : 1
31 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration } }
32 Behavior on opacity { UbuntuNumberAnimation { duration: UbuntuAnimation.BriskDuration } }
@@ -44,5 +45,11 @@ Item {
44 }
45 }
45 }
46 }
46 }
47 }
48+
49+ Binding {
50+ target: loader.item
51+ property: "objectName"
52+ value: identifier + "-widget"
53+ }
47 }
54 }
48}
55}
qml/Panel/IndicatorRow.qml+51−27
@@ -99,33 +99,56 @@ Item {
99 property bool overflow: row.width - itemWrapper.x > overFlowWidth
99 property bool overflow: row.width - itemWrapper.x > overFlowWidth
100
100
101 IndicatorItem {
101 IndicatorItem {
102− id: indicatorItem
102+ id: indicatorItem
103+ identifier: model.identifier
103− height: parent.height
104+ height: parent.height
104−
105+
105− dimmed: itemWrapper.dimmed
106+ dimmed: itemWrapper.dimmed
106−
107+
107− widgetSource: model.widgetSource
108+ widgetSource: model.widgetSource
108− indicatorProperties : model.indicatorProperties
109+ indicatorProperties : model.indicatorProperties
109−
110+
110− Component.onCompleted: {
111+ Component.onCompleted: {
111− if (visibleIndicators == undefined) {
112+ updateVisiblility();
112− visibleIndicators = {}
113− }
114− indicatorRow.visibleIndicators[model.identifier] = indicatorVisible;
115− indicatorRow.visibleIndicatorsChanged();
116− }
113+ }
117− onIndicatorVisibleChanged: {
114+ onIndicatorVisibleChanged: {
118− if (visibleIndicators == undefined) {
115+ updateVisiblility();
119− visibleIndicators = {}
116+
120− }
121− indicatorRow.visibleIndicators[model.identifier] = indicatorVisible;
122− indicatorRow.visibleIndicatorsChanged();
123−
124− if (indicatorVisible) {
117+ if (indicatorVisible) {
125− showAll = true;
118+ showAll = true;
126− allVisible.start();
119+ allVisible.start();
120+ }
121+ }
122+
123+ function updateVisiblility() {
124+ if (visibleIndicators == undefined) {
125+ visibleIndicators = {}
126+ }
127+ indicatorRow.visibleIndicators[model.identifier] = indicatorVisible;
128+
129+ // removed current index?
130+ if (indicatorRow.currentItemIndex === index && !indicatorVisible) {
131+ // find the next closest visible indicator (after current, else before)
132+ var newIndex = -1;
133+ for (var i = index+1; i < itemView.count; i++) {
134+ if (indicatorRow.visibleIndicators[i] === undefined || indicatorRow.visibleIndicators[model.identifier]) {
135+ newIndex = i;
136+ break;
127− }
137+ }
138+ }
139+ if (newIndex === -1) {
140+ for (i = index - 1; i >= 0; i--) {
141+ if (indicatorRow.visibleIndicators[i] === undefined || indicatorRow.visibleIndicators[model.identifier]) {
142+ newIndex = i;
143+ break;
144+ }
145+ }
146+ }
147+ indicatorRow.setCurrentItemIndex(newIndex);
148+ }
149+
150+ indicatorRow.visibleIndicatorsChanged();
128− }
151+ }
129 }
152 }
130
153
131 states: [
154 states: [
@@ -149,6 +172,7 @@ Item {
149 }
172 }
150 }
173 }
151
174
175+
152 Rectangle {
176 Rectangle {
153 id: highlight
177 id: highlight
154 color: Theme.palette.selected.foreground
178 color: Theme.palette.selected.foreground
qml/Panel/Indicators.qml+14−4
@@ -39,7 +39,7 @@ Showable {
39 property bool contentEnabled: true
39 property bool contentEnabled: true
40 property bool initalizeItem: true
40 property bool initalizeItem: true
41 readonly property alias content: menuContent
41 readonly property alias content: menuContent
42− property real unitProgress: 0.0
42+ property real unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
43
43
44 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
44 // TODO: Perhaps we need a animation standard for showing/hiding? Each showable seems to
45 // use its own values. Need to ask design about this.
45 // use its own values. Need to ask design about this.
@@ -193,7 +193,6 @@ Showable {
193 clip: !indicators.fullyOpened
193 clip: !indicators.fullyOpened
194 activeHeader: indicators.state == "hint" || indicators.state == "reveal"
194 activeHeader: indicators.state == "hint" || indicators.state == "reveal"
195 enabled: contentEnabled
195 enabled: contentEnabled
196− visibleIndicators: indicatorRow.visibleIndicators
197
196
198 //small shadow gradient at bottom of menu
197 //small shadow gradient at bottom of menu
199 Rectangle {
198 Rectangle {
@@ -249,8 +248,6 @@ Showable {
249
248
250 Indicators.IndicatorsModel {
249 Indicators.IndicatorsModel {
251 id: indicatorsModel
250 id: indicatorsModel
252−
253− Component.onCompleted: load()
254 }
251 }
255
252
256 IndicatorRow {
253 IndicatorRow {
@@ -265,6 +262,14 @@ Showable {
265 state: indicators.state
262 state: indicators.state
266 unitProgress: indicators.unitProgress
263 unitProgress: indicators.unitProgress
267
264
265+ onVisibleIndicatorsChanged: {
266+ // need to do it here so we can control sequence
267+ if (visibleIndicators !== undefined) {
268+ menuContent.visibleIndicators = visibleIndicators;
269+ menuContent.setCurrentMenuIndex(currentItemIndex);
270+ }
271+ }
272+
268 EdgeDragArea {
273 EdgeDragArea {
269 id: rowDragArea
274 id: rowDragArea
270 anchors.fill: indicatorRow
275 anchors.fill: indicatorRow
@@ -433,4 +438,9 @@ Showable {
433 NumberAnimation {targets: [indicatorRow, menuContent]; property: "y"; duration: 300; easing.type: Easing.OutCubic}
438 NumberAnimation {targets: [indicatorRow, menuContent]; property: "y"; duration: 300; easing.type: Easing.OutCubic}
434 }
439 }
435 ]
440 ]
441+
442+ Component.onCompleted: initialise();
443+ function initialise() {
444+ indicatorsModel.load();
445+ }
436}
446}
qml/Panel/MenuContent.qml+2−6
@@ -40,10 +40,10 @@ MainView {
40 height: units.gu(42)
40 height: units.gu(42)
41
41
42 function setCurrentMenuIndex(index) {
42 function setCurrentMenuIndex(index) {
43− var filteredIndex = filteredIndicators.mapFromSource(index)
43+ var filteredIndex = filteredIndicators.mapFromSource(index);
44
44
45 if (tabs.selectedTabIndex !== filteredIndex) {
45 if (tabs.selectedTabIndex !== filteredIndex) {
46− if (tabs.selectedTabIndex == -1 || !animateNextMenuChange) {
46+ if (tabs.selectedTabIndex === -1 || !animateNextMenuChange) {
47 tabs.tabBar.animate = false;
47 tabs.tabBar.animate = false;
48 }
48 }
49 tabs.selectedTabIndex = filteredIndex;
49 tabs.selectedTabIndex = filteredIndex;
@@ -75,10 +75,6 @@ MainView {
75
75
76 filterRole: Indicators.IndicatorsModelRole.IsVisible
76 filterRole: Indicators.IndicatorsModelRole.IsVisible
77 filterRegExp: RegExp("^true$")
77 filterRegExp: RegExp("^true$")
78−
79− onDataChanged: tabs.selectedTabIndexChanged();
80− onRowsInserted: tabs.selectedTabIndexChanged();
81− onRowsRemoved: tabs.selectedTabIndexChanged();
82 }
78 }
83
79
84 Indicators.VisibleIndicatorsModel {
80 Indicators.VisibleIndicatorsModel {
qml/Panel/Panel.qml−2
@@ -105,8 +105,6 @@ Item {
105 openedHeight: parent.height + (pinnedMode ? 0 : root.panelHeight)
105 openedHeight: parent.height + (pinnedMode ? 0 : root.panelHeight)
106 pinnedMode: !fullscreenMode
106 pinnedMode: !fullscreenMode
107 overFlowWidth: search.state=="hidden" ? parent.width : parent.width - search.width
107 overFlowWidth: search.state=="hidden" ? parent.width : parent.width - search.width
108−
109− unitProgress: (height - panelHeight) / (openedHeight - panelHeight)
110 }
108 }
111
109
112 PanelSeparatorLine {
110 PanelSeparatorLine {
qml/ScopeTool.qml+26−28
@@ -17,7 +17,9 @@
17import QtQuick 2.0
17import QtQuick 2.0
18import Ubuntu.Components 0.1
18import Ubuntu.Components 0.1
19import Ubuntu.Components.Popups 0.1
19import Ubuntu.Components.Popups 0.1
20+import Utils 0.1
20import Unity 0.1
21import Unity 0.1
22+import "Components"
21import "Dash"
23import "Dash"
22
24
23Rectangle {
25Rectangle {
@@ -43,18 +45,28 @@ Rectangle {
43 signal searchClicked
45 signal searchClicked
44 }
46 }
45
47
48+ SortFilterProxyModel {
49+ id: filteredScopes
50+ model: Scopes {
51+ id: scopes
52+ }
53+ dynamicSortFilter: true
46
54
47− Scopes {
55+ filterRole: Scopes.RoleVisible
48− id: scopes
56+ filterRegExp: RegExp("^true$")
49 }
57 }
50
58
51 Rectangle {
59 Rectangle {
52− anchors.fill: scopeView
60+ anchors.fill: dashContent
53 color: "#FCFCFC"
61 color: "#FCFCFC"
54 }
62 }
55
63
56− GenericScopeView {
64+ DashContent {
57− id: scopeView
65+ id: dashContent
66+
67+ model: filteredScopes
68+ searchHistory: SearchHistoryModel { }
69+ property Scope scope: scopes.get(currentIndex)
58
70
59 anchors {
71 anchors {
60 top: parent.top
72 top: parent.top
@@ -62,8 +74,6 @@ Rectangle {
62 left: parent.left
74 left: parent.left
63 right: controls.left
75 right: controls.left
64 }
76 }
65−
66− scope: scopes.loaded ? scopes.get(scopeSelector.selectedIndex) : undefined
67 }
77 }
68
78
69 Rectangle {
79 Rectangle {
@@ -76,6 +86,10 @@ Rectangle {
76 right: parent.right
86 right: parent.right
77 }
87 }
78
88
89+ MouseArea {
90+ anchors.fill: parent
91+ }
92+
79 Column {
93 Column {
80 anchors { fill: parent; margins: units.gu(1) }
94 anchors { fill: parent; margins: units.gu(1) }
81 spacing: units.gu(1)
95 spacing: units.gu(1)
@@ -88,27 +102,11 @@ Rectangle {
88 id: searchField
102 id: searchField
89 anchors { left: parent.left; right: parent.right }
103 anchors { left: parent.left; right: parent.right }
90
104
91− onTextChanged: scopeView.scope.searchQuery = text
105+ onTextChanged: dashContent.scope.searchQuery = text
92
106
93 Connections {
107 Connections {
94− target: scopeView.scope
108+ target: dashContent.scope
95− onSearchQueryChanged: searchField.text = scopeView.scope.searchQuery
109+ onSearchQueryChanged: searchField.text = dashContent.scope.searchQuery
96− }
97− }
98−
99− Label {
100− text: "Scope selection"
101− height: units.gu(4)
102− verticalAlignment: Text.AlignBottom
103− }
104−
105− OptionSelector {
106− id: scopeSelector
107− anchors { left: parent.left; right: parent.right }
108−
109− model: scopes
110− delegate: OptionSelectorDelegate {
111− text: model.title
112 }
110 }
113 }
111 }
114
112
@@ -121,7 +119,7 @@ Rectangle {
121 OptionSelector {
119 OptionSelector {
122 id: categorySelector
120 id: categorySelector
123 anchors { left: parent.left; right: parent.right }
121 anchors { left: parent.left; right: parent.right }
124− model: scopeView.scope ? scopeView.scope.categories : null
122+ model: dashContent.scope ? dashContent.scope.categories : null
125
123
126 property Item selectedItem
124 property Item selectedItem
127
125
@@ -170,7 +168,7 @@ Rectangle {
170 onCancelClicked: PopupUtils.close(sheet)
168 onCancelClicked: PopupUtils.close(sheet)
171 onConfirmClicked: {
169 onConfirmClicked: {
172 PopupUtils.close(sheet);
170 PopupUtils.close(sheet);
173− scopeView.scope.categories.overrideCategoryJson(categorySelector.selectedItem.categoryId, categoryEditorArea.text);
171+ dashContent.scope.categories.overrideCategoryJson(categorySelector.selectedItem.categoryId, categoryEditorArea.text);
174 }
172 }
175 }
173 }
176 }
174 }
tests/autopilot/unity8/indicators/__init__.pynew file

No changes to the content.

tests/autopilot/unity8/indicators/tests/__init__.pynew file

No changes to the content.

tests/autopilot/unity8/indicators/tests/test_indicators.pynew file+50
@@ -0,0 +1,50 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity - Indicators Autopilot Test Suite
4+# Copyright (C) 2013 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+from __future__ import absolute_import
21+
22+from autopilot import platform
23+from testtools import skipIf
24+
25+from unity8.process_helpers import unlock_unity
26+from unity8.shell.tests import UnityTestCase
27+
28+
29+class IndicatorTestCase(UnityTestCase):
30+
31+ scenarios = [
32+ ('Bluetooth', dict(indicator_name='indicator-bluetooth')),
33+ ('Datetime', dict(indicator_name='indicator-datetime')),
34+ ('Location', dict(indicator_name='indicator-location')),
35+ ('Messaging', dict(indicator_name='indicator-messages')),
36+ ('Network', dict(indicator_name='indicator-network')),
37+ ('Power', dict(indicator_name='indicator-power')),
38+ ('Sound', dict(indicator_name='indicator-sound')),
39+ ]
40+
41+ @skipIf(platform.image_codename() == 'Desktop',
42+ "Unity8/phablet-only test!")
43+ def setUp(self):
44+ super(IndicatorTestCase, self).setUp()
45+
46+ def test_indicator_exists(self):
47+ unity_proxy = self.launch_unity()
48+ unlock_unity(unity_proxy)
49+ indicator = self.main_window.get_indicator(self.indicator_name)
50+ self.assertIsNotNone(indicator)
tests/autopilot/unity8/shell/emulators/__init__.py+11−4
@@ -1,7 +1,7 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
2#
3# Unity Autopilot Test Suite
3# Unity Autopilot Test Suite
4−# Copyright (C) 2012-2013 Canonical
4+# Copyright (C) 2012, 2013, 2014 Canonical
5#
5#
6# This program is free software: you can redistribute it and/or modify
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
7# it under the terms of the GNU General Public License as published by
@@ -17,9 +17,16 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
18#
19
19
20+from autopilot import input, introspection
20
21
21−from autopilot.introspection import CustomEmulatorBase
22
22
23−
24−class UnityEmulatorBase(CustomEmulatorBase):
23+class UnityEmulatorBase(introspection.CustomEmulatorBase):
25 """A base class for all unity emulators."""
24 """A base class for all unity emulators."""
25+
26+ def __init__(self, *args):
27+ super(UnityEmulatorBase, self).__init__(*args)
28+ self.pointing_device = input.Touch.create()
29+
30+
31+class UnityEmulatorException(Exception):
32+ """Exception raised when there is an error with the Unity emulators."""
tests/autopilot/unity8/shell/emulators/dash.py+102−8
@@ -1,7 +1,7 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
2#
3# Unity Autopilot Test Suite
3# Unity Autopilot Test Suite
4−# Copyright (C) 2012-2013 Canonical
4+# Copyright (C) 2012, 2013, 2014 Canonical
5#
5#
6# This program is free software: you can redistribute it and/or modify
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
7# it under the terms of the GNU General Public License as published by
@@ -17,13 +17,25 @@
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18#
18#
19
19
20−from unity8.shell.emulators import UnityEmulatorBase
20+import logging
21
21
22+from unity8.shell import emulators
22
23
23−class Dash(UnityEmulatorBase):
24+from autopilot import logging as autopilot_logging
25+from autopilot.introspection import dbus
24
26
27+
28+logger = logging.getLogger(__name__)
29+
30+
31+class Dash(emulators.UnityEmulatorBase):
25 """An emulator that understands the Dash."""
32 """An emulator that understands the Dash."""
26
33
34+ def __init__(self, *args):
35+ super(Dash, self).__init__(*args)
36+ self.dash_content_list = self.wait_select_single(
37+ 'QQuickListView', objectName='dashContentList')
38+
27 def get_home_applications_grid(self):
39 def get_home_applications_grid(self):
28 get_grid = self.get_scope('home').wait_select_single(
40 get_grid = self.get_scope('home').wait_select_single(
29 "GenericFilterGrid",
41 "GenericFilterGrid",
@@ -43,9 +55,91 @@ class Dash(UnityEmulatorBase):
43 return resp_grid.select_single('Tile', text=text)
55 return resp_grid.select_single('Tile', text=text)
44
56
45 def get_scope(self, scope_name='home'):
57 def get_scope(self, scope_name='home'):
46− dash_content = self.wait_select_single(
47− 'QQuickListView',
48− objectName='dashContentList'
49− )
50 scope_id = "%s.scope" % scope_name
58 scope_id = "%s.scope" % scope_name
59+ return self.dash_content_list.select_single(
51− return dash_content.select_single('QQuickLoader', scopeId=scope_id)
60+ 'QQuickLoader', scopeId=scope_id)
61+
62+ @autopilot_logging.log_action(logger.info)
63+ def open_scope(self, scope_name):
64+ """Open a dash scope.
65+
66+ :parameter scope_name: The name of the scope.
67+ :return: The scope.
68+
69+ """
70+ scope_id = "%s.scope" % scope_name
71+ scope_loader = self._get_scope_loader(scope_id)
72+ if scope_loader.isCurrent:
73+ logger.info('The scope is already open.')
74+ return self._get_scope_from_loader(scope_loader)
75+ else:
76+ return self._open_scope_scrolling(scope_loader)
77+
78+ def _get_scope_loader(self, scope_id):
79+ try:
80+ return self.dash_content_list.select_single(
81+ 'QQuickLoader', scopeId=scope_id)
82+ except dbus.StateNotFoundError:
83+ raise emulators.UnityEmulatorException(
84+ 'No scope found with id {0}'.format(scope_id))
85+
86+ def _get_scope_from_loader(self, loader):
87+ if loader.scopeId == 'applications.scope':
88+ return loader.select_single(DashApps)
89+ else:
90+ return loader.select_single(GenericScopeView)
91+
92+ def _open_scope_scrolling(self, scope_loader):
93+ scroll = self._get_scroll_direction(scope_loader)
94+
95+ while not scope_loader.isCurrent:
96+ scroll()
97+
98+ scope = self._get_scope_from_loader(scope_loader)
99+ scope.moving.wait_for(False)
100+ scope.isCurrent.wait_for(True)
101+ return scope
102+
103+ def _get_scroll_direction(self, scope_loader):
104+ current_scope_loader = self.dash_content_list.select_single(
105+ 'QQuickLoader', isCurrent=True)
106+ if scope_loader.globalRect.x < current_scope_loader.globalRect.x:
107+ return self._scroll_to_left_scope
108+ elif scope_loader.globalRect.x > current_scope_loader.globalRect.x:
109+ return self._scroll_to_right_scope
110+ else:
111+ raise emulators.UnityEmulatorException('The scope is already open')
112+
113+ @autopilot_logging.log_action(logger.info)
114+ def _scroll_to_left_scope(self):
115+ original_index = self.dash_content_list.currentIndex
116+ # Scroll on the border of the page header, because some scopes have
117+ # contents that can be scrolled horizontally.
118+ page_header = self.select_single('PageHeader')
119+ border = page_header.select_single('QQuickBorderImage')
120+ start_x = border.width / 3
121+ stop_x = border.width / 3 * 2
122+ start_y = stop_y = border.globalRect.y + border.height / 2
123+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
124+ self.dash_content_list.currentIndex.wait_for(original_index - 1)
125+
126+ @autopilot_logging.log_action(logger.info)
127+ def _scroll_to_right_scope(self):
128+ original_index = self.dash_content_list.currentIndex
129+ # Scroll on the border of the page header, because some scopes have
130+ # contents that can be scrolled horizontally.
131+ page_header = self.select_single('PageHeader')
132+ border = page_header.select_single('QQuickBorderImage')
133+ start_x = border.width / 3 * 2
134+ stop_x = border.width / 3
135+ start_y = stop_y = border.globalRect.y + border.height / 2
136+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
137+ self.dash_content_list.currentIndex.wait_for(original_index + 1)
138+
139+
140+class GenericScopeView(emulators.UnityEmulatorBase):
141+ """Autopilot emulators for generic scopes."""
142+
143+
144+class DashApps(emulators.UnityEmulatorBase):
145+ """Autopilot emulators for the applications scope."""
tests/autopilot/unity8/shell/emulators/greeter.py+2−5
@@ -1,7 +1,7 @@
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
1# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2#
2#
3# Unity Autopilot Test Suite
3# Unity Autopilot Test Suite
4−# Copyright (C) 2012-2013 Canonical
4+# Copyright (C) 2012, 2013, 2014 Canonical
5#
5#
6# This program is free software: you can redistribute it and/or modify
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
7# it under the terms of the GNU General Public License as published by
@@ -18,24 +18,21 @@
18#
18#
19
19
20from unity8.shell.emulators import UnityEmulatorBase
20from unity8.shell.emulators import UnityEmulatorBase
21−from autopilot.input import Touch
22
21
23
22
24class Greeter(UnityEmulatorBase):
23class Greeter(UnityEmulatorBase):
25−
26 """An emulator that understands the greeter screen."""
24 """An emulator that understands the greeter screen."""
27
25
28 def swipe(self):
26 def swipe(self):
29 """Swipe the greeter screen away."""
27 """Swipe the greeter screen away."""
30 self.created.wait_for(True)
28 self.created.wait_for(True)
31− touch = Touch.create()
32
29
33 rect = self.globalRect
30 rect = self.globalRect
34 start_x = rect[0] + rect[2] - 3
31 start_x = rect[0] + rect[2] - 3
35 start_y = int(rect[1] + rect[3] / 2)
32 start_y = int(rect[1] + rect[3] / 2)
36 stop_x = int(rect[0] + rect[2] * 0.2)
33 stop_x = int(rect[0] + rect[2] * 0.2)
37 stop_y = start_y
34 stop_y = start_y
38− touch.drag(start_x, start_y, stop_x, stop_y)
35+ self.pointing_device.drag(start_x, start_y, stop_x, stop_y)
39
36
40 self.created.wait_for(False)
37 self.created.wait_for(False)
41
38
tests/autopilot/unity8/shell/emulators/main_window.py+6
@@ -91,5 +91,11 @@ class MainWindow(object):
91 def get_pinentryField(self):
91 def get_pinentryField(self):
92 return self.app.select_single(objectName="pinentryField")
92 return self.app.select_single(objectName="pinentryField")
93
93
94+ def get_indicator(self, indicator_name):
95+ return self.app.select_single(
96+ 'DefaultIndicatorWidget',
97+ objectName=indicator_name+'-widget'
98+ )
99+
94 def get_shell_background(self):
100 def get_shell_background(self):
95 return self.app.select_single("CrossFadeImage", objectName="backgroundImage")
101 return self.app.select_single("CrossFadeImage", objectName="backgroundImage")
tests/autopilot/unity8/shell/tests/__init__.py+1
@@ -147,6 +147,7 @@ class UnityTestCase(AutopilotTestCase):
147 if model() != "Desktop":
147 if model() != "Desktop":
148 from autopilot.input import _uinput
148 from autopilot.input import _uinput
149 _uinput._touch_device = _uinput.create_touch_device()
149 _uinput._touch_device = _uinput.create_touch_device()
150+ self.addCleanup(_uinput._touch_device.close)
150 ####
151 ####
151
152
152 self.touch = Touch.create()
153 self.touch = Touch.create()
tests/autopilot/unity8/shell/tests/test_emulators.pynew file+120
@@ -0,0 +1,120 @@
1+# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
2+#
3+# Unity Autopilot Test Suite
4+# Copyright (C) 2014 Canonical
5+#
6+# This program is free software: you can redistribute it and/or modify
7+# it under the terms of the GNU General Public License as published by
8+# the Free Software Foundation, either version 3 of the License, or
9+# (at your option) any later version.
10+#
11+# This program is distributed in the hope that it will be useful,
12+# but WITHOUT ANY WARRANTY; without even the implied warranty of
13+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+# GNU General Public License for more details.
15+#
16+# You should have received a copy of the GNU General Public License
17+# along with this program. If not, see <http://www.gnu.org/licenses/>.
18+#
19+
20+import mock
21+
22+from unity8 import process_helpers
23+from unity8.shell import emulators, tests
24+from unity8.shell.emulators import dash as dash_emulators
25+
26+
27+class DashEmulatorTestCase(tests.UnityTestCase):
28+
29+ scenarios = tests._get_device_emulation_scenarios()
30+
31+ def setUp(self):
32+ super(DashEmulatorTestCase, self).setUp()
33+ unity_proxy = self.launch_unity()
34+ process_helpers.unlock_unity(unity_proxy)
35+ self.dash = self.main_window.get_dash()
36+
37+ def test_open_unexisting_scope(self):
38+ scope_name = 'unexisting'
39+ with mock.patch.object(self.dash, 'pointing_device') as mock_pointer:
40+ exception = self.assertRaises(
41+ emulators.UnityEmulatorException,
42+ self.dash.open_scope, scope_name)
43+
44+ self.assertEqual(
45+ 'No scope found with id unexisting.scope', str(exception))
46+ self.assertFalse(mock_pointer.called)
47+
48+ def test_open_already_opened_scope(self):
49+ scope_id = self._get_current_scope_id()
50+ with mock.patch.object(self.dash, 'pointing_device') as mock_pointer:
51+ scope = self.dash.open_scope(self._get_scope_name_from_id(
52+ scope_id))
53+
54+ self.assertFalse(mock_pointer.called)
55+ self._assert_scope_is_opened(scope, scope_id)
56+
57+ def _assert_scope_is_opened(self, scope, scope_id):
58+ self.assertTrue(scope.isCurrent)
59+ scope_loader = scope.get_parent()
60+ self.assertEqual(scope_loader.scopeId, scope_id)
61+
62+ def _get_current_scope_id(self):
63+ scope = self.dash.dash_content_list.select_single(
64+ 'QQuickLoader', isCurrent=True)
65+ return scope.scopeId
66+
67+ def test_open_scope_to_the_right(self):
68+ leftmost_scope = self._get_scope_name_from_id(
69+ self._get_leftmost_scope_id())
70+ self.dash.open_scope(leftmost_scope)
71+
72+ scope_id = self._get_rightmost_scope_id()
73+ scope = self.dash.open_scope(self._get_scope_name_from_id(scope_id))
74+ self._assert_scope_is_opened(scope, scope_id)
75+
76+ def _get_leftmost_scope_id(self):
77+ scope_loaders = self._get_scope_loaders()
78+ leftmost_scope_loader = scope_loaders[0]
79+ for loader in scope_loaders[1:]:
80+ if loader.globalRect.x < leftmost_scope_loader.globalRect.x:
81+ leftmost_scope_loader = loader
82+ return leftmost_scope_loader.scopeId
83+
84+ def _get_scope_loaders(self):
85+ item = self.dash.dash_content_list.get_children_by_type(
86+ 'QQuickItem')[0]
87+ return item.get_children_by_type('QQuickLoader')
88+
89+ def _get_scope_name_from_id(self, scope_id):
90+ if scope_id.endswith('.scope'):
91+ return scope_id[:-6]
92+
93+ def _get_rightmost_scope_id(self):
94+ scope_loaders = self._get_scope_loaders()
95+ rightmost_scope_loader = scope_loaders[0]
96+ for loader in scope_loaders[1:]:
97+ if loader.globalRect.x > rightmost_scope_loader.globalRect.x:
98+ rightmost_scope_loader = loader
99+ return rightmost_scope_loader.scopeId
100+
101+ def test_open_scope_to_the_left(self):
102+ rightmost_scope = self._get_scope_name_from_id(
103+ self._get_rightmost_scope_id())
104+ self.dash.open_scope(rightmost_scope)
105+
106+ scope_id = self._get_leftmost_scope_id()
107+ scope = self.dash.open_scope(self._get_scope_name_from_id(scope_id))
108+ self._assert_scope_is_opened(scope, scope_id)
109+
110+ def test_open_generic_scope(self):
111+ scope_id = 'home.scope'
112+ scope = self.dash.open_scope(self._get_scope_name_from_id(scope_id))
113+ self._assert_scope_is_opened(scope, scope_id)
114+ self.assertIsInstance(scope, dash_emulators.GenericScopeView)
115+
116+ def test_open_applications_scope(self):
117+ scope_id = 'applications.scope'
118+ scope = self.dash.open_scope(self._get_scope_name_from_id(scope_id))
119+ self._assert_scope_is_opened(scope, scope_id)
120+ self.assertIsInstance(scope, dash_emulators.DashApps)
tests/mocks/Unity/Indicators/IndicatorsModel.qml+2−5
@@ -21,12 +21,9 @@ ListModel {
21
21
22 property var indicatorData : undefined
22 property var indicatorData : undefined
23
23
24− function load() {}
24+ function load() {
25+ clear();
25
26
26− // Need to do this dynamically.
27− // Apparently ListModels dont order roles visually when using ListElement
28− // which throws off the filter for non-visible indicators.
29− Component.onCompleted: {
30 append({ "identifier": "indicator-fake1",
27 append({ "identifier": "indicator-fake1",
31 "position": 0,
28 "position": 0,
32 "widgetSource": "qrc:/tests/indciators/qml/fake_menu_widget1.qml",
29 "widgetSource": "qrc:/tests/indciators/qml/fake_menu_widget1.qml",
tests/mocks/Unity/Indicators/qml/fake_menu_widget2.qml+1−3
@@ -19,7 +19,5 @@ import QtQuick 2.0
19Rectangle {
19Rectangle {
20 width: 40
20 width: 40
21 height: 40
21 height: 40
22− color : "yellow"
22+ color : "blue"
23−
24− enabled: false
25}
23}
tests/mocks/Unity/Indicators/qml/fake_menu_widget3.qml+1−1
@@ -19,5 +19,5 @@ import QtQuick 2.0
19Rectangle {
19Rectangle {
20 width: 40
20 width: 40
21 height: 40
21 height: 40
22− color : "blue"
22+ color : "yellow"
23}
23}
tests/plugins/DashViews/CMakeLists.txt+3−5
@@ -55,7 +55,7 @@ macro(add_journal_try_test FILENAME)
55 target_link_libraries(${FILENAME}testExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
55 target_link_libraries(${FILENAME}testExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
56 add_custom_target(test${CUSTOM_TARGET_NAME}
56 add_custom_target(test${CUSTOM_TARGET_NAME}
57 ${CMAKE_CURRENT_BINARY_DIR}/${FILENAME}testExec
57 ${CMAKE_CURRENT_BINARY_DIR}/${FILENAME}testExec
58− -o ${CMAKE_BINARY_DIR}/testVerticalJournal.xml,xunitxml
58+ -o ${CMAKE_BINARY_DIR}/test${CUSTOM_TARGET_NAME}.xml,xunitxml
59 -o -,txt
59 -o -,txt
60 DEPENDS ${FILENAME}testExec DashViews-qml
60 DEPENDS ${FILENAME}testExec DashViews-qml
61 )
61 )
@@ -69,8 +69,6 @@ macro(add_journal_try_test FILENAME)
69 target_link_libraries(${FILENAME}tryExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
69 target_link_libraries(${FILENAME}tryExec ${Qt5Gui_LIBRARIES} ${Qt5Quick_LIBRARIES})
70 add_custom_target(try${CUSTOM_TARGET_NAME}
70 add_custom_target(try${CUSTOM_TARGET_NAME}
71 ${CMAKE_CURRENT_BINARY_DIR}/${FILENAME}tryExec
71 ${CMAKE_CURRENT_BINARY_DIR}/${FILENAME}tryExec
72− -o ${CMAKE_BINARY_DIR}/testVerticalJournal.xml,xunitxml
73− -o -,txt
74 DEPENDS ${FILENAME}tryExec DashViews-qml
72 DEPENDS ${FILENAME}tryExec DashViews-qml
75 )
73 )
76endmacro()
74endmacro()
@@ -85,5 +83,5 @@ add_dependencies(qmluitests testlistviewwithpageheader)
85add_dependencies(qmluitests testlistviewwithpageheadersection)
83add_dependencies(qmluitests testlistviewwithpageheadersection)
86add_dependencies(qmluitests testlistviewwithpageheadersectionexternalmodel)
84add_dependencies(qmluitests testlistviewwithpageheadersectionexternalmodel)
87add_dependencies(qmluitests testlistviewwithpageheaderqml)
85add_dependencies(qmluitests testlistviewwithpageheaderqml)
88−add_dependencies(qmluitests testVerticalJournal)
86+add_dependencies(qmluitests testVerticaljournal)
89−add_dependencies(qmluitests testHorizontalJournal)
87+add_dependencies(qmluitests testHorizontaljournal)
tests/qmltests/CMakeLists.txt+2
@@ -42,6 +42,8 @@ add_qml_test(Dash Dash IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAUL
42add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
42add_qml_test(Dash DashContent IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
43add_qml_test(Dash DashPreview)
43add_qml_test(Dash DashPreview)
44add_qml_test(Dash GenericPreview)
44add_qml_test(Dash GenericPreview)
45+add_qml_test(Dash Card)
46+add_qml_test(Dash CardHeader)
45add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
47add_qml_test(Dash GenericScopeView IMPORT_PATHS ${CMAKE_BINARY_DIR}/plugins ${qmltest_DEFAULT_IMPORT_PATHS})
46add_qml_test(Dash FilterGrids IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_SOURCE_DIR}/plugins
48add_qml_test(Dash FilterGrids IMPORT_PATHS ${qmltest_DEFAULT_IMPORT_PATHS} ${CMAKE_BINARY_DIR}/plugins ${CMAKE_CURRENT_SOURCE_DIR}/plugins
47 ${CMAKE_BINARY_DIR}/tests/mocks)
49 ${CMAKE_BINARY_DIR}/tests/mocks)
tests/qmltests/Dash/CardHelpers.jsnew file+75
@@ -0,0 +1,75 @@
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+.pragma library
18+
19+var components = ["title", "art", "subtitle", "mascot", "emblem", "old-price", "price", "alt-price", "rating", "alt-rating", "summary"]
20+
21+function tryParse(json, errorLabel) {
22+ var o = undefined;
23+ if (errorLabel !== undefined) {
24+ errorLabel.text = "";
25+ }
26+ try {
27+ o = JSON.parse(json)
28+ } catch(err) {
29+ if (errorLabel !== undefined) {
30+ errorLabel.text = err + "";
31+ } else {
32+ console.debug(err);
33+ }
34+ }
35+ return o;
36+}
37+
38+function mapData(json, layout, errorLabel) {
39+ var o = tryParse(json, errorLabel);
40+ var d = undefined;
41+
42+ if (o !== undefined) {
43+ d = Object();
44+ for (var k in components) {
45+ try {
46+ if (typeof layout[components[k]] == "object") {
47+ d[components[k]] = o[layout[components[k]]['field']];
48+ } else {
49+ d[components[k]] = o[layout[components[k]]];
50+ }
51+ } catch(err) {
52+ d[components[k]] = undefined;
53+ }
54+ }
55+ }
56+ return d;
57+}
58+
59+function update(object, overrides) {
60+ for (var k in overrides) {
61+ if (typeof object[k] == "string" && typeof overrides[k] == "object") {
62+ if (!overrides[k].hasOwnProperty('field')) overrides[k]['field'] = object[k];
63+ }
64+ if (object[k] === null) {
65+ object[k] = overrides[k];
66+ } else if (typeof object[k] == "object" && typeof overrides[k] == "string") {
67+ object[k]['field'] = overrides[k];
68+ } else if (typeof object[k] == "object") {
69+ update(object[k], overrides[k]);
70+ } else {
71+ object[k] = overrides[k];
72+ }
73+ }
74+ return object;
75+}
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+337
@@ -0,0 +1,337 @@
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 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 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+
65+ property string cardData: '
66+ {
67+ "art": "../../tests/qmltests/Dash/artwork/music-player-design.png",
68+ "mascot": "../../tests/qmltests/Dash/artwork/avatar.png",
69+ "title": "foo",
70+ "subtitle": "bar",
71+ "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."
72+ }'
73+
74+ property string fullMapping: '
75+ {
76+ "title": "title",
77+ "art": "art",
78+ "subtitle": "subtitle",
79+ "mascot": "mascot",
80+ "summary": "summary"
81+ }'
82+
83+ property var cardsModel: [
84+ {
85+ "name": "Art, header, summary - vertical",
86+ "layout": { "components": JSON.parse(fullMapping) }
87+ },
88+ {
89+ "name": "Art, header, summary - vertical, small",
90+ "layout": { "template": { "card-size": "small" }, "components": JSON.parse(fullMapping) }
91+ },
92+ {
93+ "name": "Art, header, summary - vertical, large",
94+ "layout": { "template": { "card-size": "large" }, "components": JSON.parse(fullMapping) }
95+ },
96+ {
97+ "name": "Art, header, summary - vertical, wide",
98+ "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "art": { "aspect-ratio": 2 } }) }
99+ },
100+ {
101+ "name": "Art, title - vertical, fitted",
102+ "layout": { "components": Helpers.update(JSON.parse(root.fullMapping), { "art": { "fill-mode": "fit" } }) }
103+ },
104+ {
105+ "name": "Art, header, summary - horizontal",
106+ "layout": { "template": { "card-layout": "horizontal" },
107+ "components": JSON.parse(fullMapping) }
108+ },
109+ ]
110+
111+ Card {
112+ id: card
113+ anchors { top: parent.top; left: parent.left; margins: units.gu(1) }
114+
115+ template: Helpers.update(JSON.parse(root.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['template'];
116+ components: Helpers.update(JSON.parse(root.defaultLayout), Helpers.tryParse(layoutArea.text, layoutError))['components'];
117+ cardData: Helpers.mapData(dataArea.text, components, dataError)
118+ }
119+
120+ Rectangle {
121+ anchors { top: parent.top; bottom: parent.bottom; right: parent.right}
122+ width: units.gu(40)
123+ color: "lightgrey"
124+
125+ Column {
126+ anchors { fill: parent; margins: units.gu(1) }
127+ spacing: units.gu(1)
128+
129+ OptionSelector {
130+ id: selector
131+ model: cardsModel
132+ delegate: OptionSelectorDelegate { text: modelData.name }
133+ onSelectedIndexChanged: updateAreas()
134+ Component.onCompleted: updateAreas()
135+
136+ function updateAreas() {
137+ var element = cardsModel[selectedIndex];
138+ if (element) {
139+ layoutArea.text = JSON.stringify(element.layout, undefined, 2) || "{}";
140+ // FIXME: don't overwrite data
141+ var data = JSON.parse(root.cardData);
142+ Helpers.update(data, element.data);
143+ dataArea.text = JSON.stringify(data, undefined, 2) || "{}";
144+ } else {
145+ layoutArea.text = "";
146+ dataArea.text = "";
147+ }
148+
149+ }
150+ }
151+
152+ TextArea {
153+ id: layoutArea
154+ anchors { left: parent.left; right: parent.right }
155+ height: units.gu(25)
156+ }
157+
158+ Label {
159+ id: layoutError
160+ anchors { left: parent.left; right: parent.right }
161+ height: units.gu(4)
162+ color: "orange"
163+ }
164+
165+ TextArea {
166+ id: dataArea
167+ anchors { left: parent.left; right: parent.right }
168+ height: units.gu(25)
169+ }
170+
171+ Label {
172+ id: dataError
173+ anchors { left: parent.left; right: parent.right }
174+ height: units.gu(4)
175+ color: "orange"
176+ }
177+ }
178+ }
179+
180+ UT.UnityTestCase {
181+ id: testCase
182+ name: "Card"
183+
184+ when: windowShown
185+
186+ property Item header: findChild(card, "cardHeader")
187+ property Item art: findChild(card, "artShape")
188+ property Item artImage: findChild(card, "artImage")
189+ property Item summary: findChild(card, "summaryLabel")
190+
191+ function initTestCase() {
192+ verify(testCase.header !== undefined, "Couldn't find header object.");
193+ }
194+
195+ function cleanup() {
196+ selector.selectedIndex = -1;
197+ }
198+
199+
200+ function test_header_binding_data() {
201+ return [
202+ { tag: "Mascot", property: "mascot", value: Qt.resolvedUrl("artwork/avatar.png"), index: 0 },
203+ { tag: "Title", property: "title", value: "foo", index: 0 },
204+ { tag: "Subtitle", property: "subtitle", value: "bar", index: 0 },
205+ ];
206+ }
207+
208+ function test_header_binding(data) {
209+ selector.selectedIndex = data.index;
210+ tryCompare(testCase.header, data.property, data.value);
211+ }
212+
213+ function test_card_binding_data() {
214+ return [
215+ { tag: "Art", object: artImage, property: "source", value: Qt.resolvedUrl("artwork/music-player-design.png"), index: 0 },
216+ { tag: "Summary", object: summary, property: "text", field: "summary", index: 0 },
217+ { tag: "Fit", object: art, fill: Image.PreserveAspectFit, index: 4 },
218+ ];
219+ }
220+
221+ function test_card_binding(data) {
222+ selector.selectedIndex = data.index;
223+
224+ if (data.hasOwnProperty('value')) {
225+ tryCompare(data.object, data.property, data.value);
226+ }
227+
228+ if (data.hasOwnProperty('field')) {
229+ tryCompare(data.object, data.property, card.cardData[data.field]);
230+ }
231+ }
232+
233+ function test_card_size_data() {
234+ return [
235+ { tag: "Medium", width: units.gu(18.5), index: 0 },
236+ { tag: "Small", width: units.gu(12), size: "small", index: 0 },
237+ { tag: "Large", width: units.gu(38), size: "large", index: 0 },
238+ { tag: "Wide", width: units.gu(18.5), aspect: 0.5, index: 0 },
239+ { tag: "Horizontal", width: units.gu(38), index: 5 },
240+ ]
241+ }
242+
243+ function test_card_size(data) {
244+ selector.selectedIndex = data.index;
245+
246+ if (data.hasOwnProperty("size")) {
247+ card.template['card-size'] = data.size;
248+ card.templateChanged();
249+ }
250+
251+ if (data.hasOwnProperty("aspect")) {
252+ card.components['art']['aspect-ratio'] = data.aspect;
253+ card.componentsChanged();
254+ }
255+
256+ if (data.hasOwnProperty("width")) {
257+ tryCompare(card, "width", data.width);
258+ }
259+
260+ if (data.hasOwnProperty("height")) {
261+ tryCompare(card, "height", data.height);
262+ }
263+ }
264+
265+ function test_art_size_data() {
266+ return [
267+ { tag: "Medium", width: units.gu(18.5), fill: Image.PreserveAspectCrop, index: 0 },
268+ { tag: "Small", width: units.gu(12), index: 1 },
269+ { tag: "Large", width: units.gu(38), index: 2 },
270+ { tag: "Wide", height: units.gu(19), size: "large", index: 3 },
271+ { tag: "Fit", height: units.gu(38), size: "large", width: units.gu(19), index: 4 },
272+ { tag: "VerticalWidth", width: function() { return header.width }, index: 0 },
273+ { tag: "HorizontalHeight", height: function() { return header.height }, index: 5 },
274+ { tag: "HorizontalWidth", width: function() { return header.x }, index: 5 },
275+ ]
276+ }
277+
278+ function test_art_size(data) {
279+ selector.selectedIndex = data.index;
280+
281+ if (data.hasOwnProperty("size")) {
282+ card.template['card-size'] = data.size;
283+ card.templateChanged();
284+ }
285+
286+ if (data.hasOwnProperty("aspect")) {
287+ card.components['art']['aspect-ratio'] = data.aspect;
288+ card.componentsChanged();
289+ }
290+
291+ if (data.hasOwnProperty("width")) {
292+ if (typeof data.width === "function") {
293+ tryCompareFunction(function() { return art.width === data.width() }, true);
294+ } else tryCompare(art, "width", data.width);
295+ }
296+
297+ if (data.hasOwnProperty("height")) {
298+ if (typeof data.height === "function") {
299+ tryCompareFunction(function() { return art.height === data.height() }, true);
300+ } else tryCompare(art, "height", data.height);
301+ }
302+
303+ if (data.hasOwnProperty("fill")) {
304+ tryCompare(artImage, "fillMode", data.fill);
305+ }
306+ }
307+
308+ function test_art_layout_data() {
309+ return [
310+ { tag: "Vertical", left: function() { return 0 }, index: 0},
311+ { tag: "Horizontal", left: function() { return art.width }, index: 5 }
312+ ];
313+ }
314+
315+ function test_art_layout(data) {
316+ selector.selectedIndex = data.index;
317+
318+ tryCompare(testCase.header, "x", data.left());
319+ }
320+
321+ function test_header_layout_data() {
322+ return [
323+ { tag: "Vertical", top: function() { return art.y + art.height },
324+ left: function() { return art.x }, index: 0 },
325+ { tag: "Horizontal", top: function() { return art.y },
326+ left: function() { return art.x + art.width }, index: 5 }
327+ ]
328+ }
329+
330+ function test_header_layout(data) {
331+ selector.selectedIndex = data.index;
332+
333+ tryCompareFunction(function() { return testCase.header.y === data.top() }, true);
334+ tryCompareFunction(function() { return testCase.header.x === data.left() }, true);
335+ }
336+ }
337+}
tests/qmltests/Dash/tst_CardHeader.qmlnew file+138
@@ -0,0 +1,138 @@
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 QtTest 1.0
19+import Ubuntu.Components 0.1
20+import Unity.Test 0.1 as UT
21+import "../../../qml/Dash"
22+
23+
24+Rectangle {
25+ width: units.gu(40)
26+ height: units.gu(72)
27+ color: "lightgrey"
28+
29+ CardHeader {
30+ id: cardHeader
31+ anchors { left: parent.left; right: parent.right }
32+ }
33+
34+ Rectangle {
35+ anchors.fill: cardHeader
36+ color: "lightblue"
37+ opacity: 0.5
38+ }
39+
40+ UT.UnityTestCase {
41+ id: testCase
42+ name: "CardHeader"
43+
44+ when: windowShown
45+
46+ property Item mascot: findChild(cardHeader, "mascotShape")
47+ property Item titleLabel: findChild(cardHeader, "titleLabel")
48+ property Item subtitleLabel: findChild(cardHeader, "subtitleLabel")
49+ property Item prices: findChild(cardHeader, "prices")
50+ property Item oldPriceLabel: findChild(cardHeader, "oldPriceLabel")
51+ property Item outerRow: findChild(cardHeader, "outerRow")
52+ property Item column: findChild(cardHeader, "column")
53+
54+ function initTestCase() {
55+ verify(testCase.mascot !== undefined, "Couldn't find mascot object.");
56+ verify(testCase.titleLabel !== undefined, "Couldn't find titleLabel object.");
57+ verify(testCase.subtitleLabel !== undefined, "Couldn't find subtitleLabel object.");
58+ verify(testCase.prices !== undefined, "Couldn't find prices object.");
59+ verify(testCase.oldPriceLabel !== undefined, "Couldn't find oldPriceLabel object.");
60+ }
61+
62+ function cleanup() {
63+ cardHeader.mascot = "";
64+ cardHeader.title = "";
65+ cardHeader.subtitle = "";
66+ cardHeader.price = "";
67+ cardHeader.oldPrice = "";
68+ cardHeader.altPrice = "";
69+ }
70+
71+ function test_mascot_data() {
72+ return [
73+ { tag: "Empty", source: "", visible: false },
74+ { tag: "Invalid", source: "bad_path", visible: false },
75+ { tag: "Valid", source: Qt.resolvedUrl("artwork/avatar.png"), visible: true },
76+ ]
77+ }
78+
79+ function test_mascot(data) {
80+ cardHeader.mascot = data.source;
81+ tryCompare(testCase.mascot, "visible", data.visible);
82+ }
83+
84+ function test_labels_data() {
85+ return [
86+ { tag: "Empty", visible: false },
87+ { tag: "Title only", title: "Foo", visible: true },
88+ { tag: "Subtitle only", subtitle: "Bar", visible: false },
89+ { tag: "Both", title: "Foo", subtitle: "Bar", visible: true }
90+ ]
91+ }
92+
93+ function test_labels(data) {
94+ cardHeader.title = data.title !== undefined ? data.title : "";
95+ cardHeader.subtitle = data.subtitle !== undefined ? data.subtitle : "";
96+ tryCompare(cardHeader, "visible", data.visible);
97+ }
98+
99+ function test_prices_data() {
100+ return [
101+ { tag: "Main", main: "$1.25", visible: true },
102+ { tag: "Alt", alt: "€1.00", visible: false },
103+ { tag: "Old", old: "€2.00", visible: false },
104+ { tag: "Main and Alt", main: "$1.25", alt: "€1.00", visible: true },
105+ { tag: "Main and Old", main: "$1.25", old: "$2.00", visible: true, oldAlign: Text.AlignRight },
106+ { tag: "Alt and Old", alt: "€1.00", old: "$2.00", visible: false },
107+ { tag: "All", main: "$1.25", alt: "€1.00", old: "$2.00", visible: true, oldAlign: Text.AlignHCenter }
108+ ]
109+ }
110+
111+ function test_prices(data) {
112+ cardHeader.price = data.main !== undefined ? data.main : "";
113+ cardHeader.oldPrice = data.old !== undefined ? data.old : "";
114+ cardHeader.altPrice = data.alt !== undefined ? data.alt : "";
115+ tryCompare(cardHeader, "visible", data.visible);
116+ if (data.hasOwnProperty("oldAlign")) {
117+ compare(testCase.oldPriceLabel.horizontalAlignment, data.oldAlign, "Old price label is aligned wrong.")
118+ }
119+ }
120+
121+ function test_dimensions_data() {
122+ 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+ ]
126+ }
127+
128+ function test_dimensions(data) {
129+ if (data.hasOwnProperty("mascot")) {
130+ cardHeader.mascot = data.mascot;
131+ }
132+
133+ if (data.hasOwnProperty("width")) {
134+ tryCompare(data.object, "width", data.width);
135+ }
136+ }
137+ }
138+}
tests/qmltests/Panel/tst_IndicatorRow.qml+2−1
@@ -32,6 +32,8 @@ Item {
32
32
33 function init_test()
33 function init_test()
34 {
34 {
35+ indicatorModel.load();
36+
35 indicatorRow.state = "initial";
37 indicatorRow.state = "initial";
36 indicatorRow.setCurrentItemIndex(-1);
38 indicatorRow.setCurrentItemIndex(-1);
37 indicatorRow.unitProgress = 0.0;
39 indicatorRow.unitProgress = 0.0;
@@ -53,7 +55,6 @@ Item {
53
55
54 Indicators.IndicatorsModel {
56 Indicators.IndicatorsModel {
55 id: indicatorModel
57 id: indicatorModel
56− Component.onCompleted: load()
57 }
58 }
58
59
59 UT.UnityTestCase {
60 UT.UnityTestCase {
tests/qmltests/Panel/tst_Indicators.qml+61−10
@@ -83,6 +83,8 @@ Item {
83 when: windowShown
83 when: windowShown
84
84
85 function init() {
85 function init() {
86+ indicators.initialise();
87+
86 indicators.hide();
88 indicators.hide();
87 tryCompare(indicators.hideAnimation, "running", false);
89 tryCompare(indicators.hideAnimation, "running", false);
88 tryCompare(indicators, "state", "initial");
90 tryCompare(indicators, "state", "initial");
@@ -161,25 +163,74 @@ Item {
161 compare(indicators.fullyOpened, true);
163 compare(indicators.fullyOpened, true);
162 }
164 }
163
165
166+ function init_invisible_indicator(identifier) {
167+ tryCompareFunction(function() { return findChild(indicators, identifier+"-widget") !== undefined }, true);
168+ var item = findChild(indicators, identifier+"-widget");
169+
170+ item.enabled = false;
171+ }
172+
164 function test_row_visible_menuContent_visible_data() { return [
173 function test_row_visible_menuContent_visible_data() { return [
165− {tag: "visible", index: 0, name: "indicator-fake1", visible: true },
174+ {tag: "first", visible: [false, true, true, true, true] },
166− {tag: "invisible", index: 1, name: "indicator-fake2", visible: false }]
175+ {tag: "adjacent", visible: [true, false, false, true, true] },
176+ {tag: "bounds", visible: [false, true, true, true, false] },
177+ {tag: "disjoint", visible: [true, false, true, false, true] },
178+ {tag: "last", visible: [true, true, true, true, false] }];
167 }
179 }
168
180
169 function test_row_visible_menuContent_visible(data) {
181 function test_row_visible_menuContent_visible(data) {
182+
183+ indicators.show();
184+
170 var indicatorTabs = findChild(indicators, "tabs");
185 var indicatorTabs = findChild(indicators, "tabs");
186+ var indicatorRow = findChild(indicators, "indicatorRow");
187+
188+
189+ for (var i = 0; i< data.visible.length; i++) {
190+ if (data.visible[i] === false)
191+ init_invisible_indicator("indicator-fake" + (i + 1));
192+ }
193+
194+ for (i = 0; i < data.visible.length; i++) {
195+ var shouldBeVisible = data.visible[i];
196+
197+ // check item visibility
198+ var indicatorItem = findChild(indicatorRow.row, "item" + i);
171− verify(indicatorTabs !== undefined)
199+ verify(indicatorItem !== undefined);
200+ tryCompare(indicatorItem, "visible", shouldBeVisible);
172
201
202+ // check tab visibility
203+ tryCompareFunction(function() { return findChild(indicatorTabs, "indicator-fake" + (i + 1)) !== undefined }, shouldBeVisible);
204+ }
205+ }
206+
207+ function test_indicator_visible_correct_tabs_data() { return [
208+ {tag: "current-first", currentIndex: 0, visible: [false, true, true, true, true], expected: 1 },
209+ {tag: "current-last", currentIndex: 4, visible: [true, true, true, true, false], expected: 3 },
210+ {tag: "after", currentIndex: 0, visible: [true, false, true, true, true], expected: 0 },
211+ {tag: "before", currentIndex: 1, visible: [false, true, true, true, true], expected: 1 }];
212+ }
213+
214+ function test_indicator_visible_correct_tabs(data) {
215+
216+ var indicatorTabs = findChild(indicators, "tabs");
173 var indicatorRow = findChild(indicators, "indicatorRow");
217 var indicatorRow = findChild(indicators, "indicatorRow");
174− verify(indicatorRow !== undefined);
175− var indicatorItem = findChild(indicatorRow.row, "item" + data.index);
176− verify(indicatorItem !== undefined);
177
218
219+ indicators.show();
220+ indicatorRow.setCurrentItemIndex(data.currentIndex);
221+ tryCompare(indicators, "fullyOpened", true);
222+
223+ for (var i = 0; i< data.visible.length; i++) {
224+ if (data.visible[i] === false)
225+ init_invisible_indicator("indicator-fake" + (i + 1));
226+ }
227+
228+ // check for current selected item
229+ tryCompare(indicatorRow, "currentItemIndex", data.expected);
230+
231+ // check for current selected tab
178− tryCompareFunction(function() { return indicatorItem.width > 0}, data.visible);
232+ tryCompareFunction(function() { return findChild(indicatorTabs, "indicator-fake" + (data.expected + 1)) === indicatorTabs.selectedTab }, true);
179− tryCompare(indicatorItem, "visible", data.visible);
180
233
181− var indicatorTab = findChild(indicatorTabs, data.name)
182− tryCompareFunction(function() { return indicatorTab !== undefined }, data.visible);
183 }
234 }
184 }
235 }
185}
236}
tests/qmltests/Panel/tst_Panel.qml+2
@@ -56,6 +56,8 @@ Item {
56 }
56 }
57
57
58 function init() {
58 function init() {
59+ panel.indicators.initialise();
60+
59 searchClicked = false;
61 searchClicked = false;
60 panel.indicators.hide();
62 panel.indicators.hide();
61 tryCompare(panel.indicators.hideAnimation, "running", false);
63 tryCompare(panel.indicators.hideAnimation, "running", false);