@@ -0,0 +1,1366 @@
1+/*
2+ * Copyright 2014-2016 Canonical Ltd.
3+ * Copyright 2021 UBports Foundation
4+ *
5+ * This program is free software; you can redistribute it and/or modify
6+ * it under the terms of the GNU General Public License as published by
7+ * the Free Software Foundation; version 3.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+import QtQuick 2.4
19+import QtQuick.Window 2.2
20+import QtTest 1.0
21+import ".."
22+import "../../../qml/Greeter"
23+import LightDMController 0.1
24+import LightDM.FullLightDM 0.1 as LightDM
25+import Ubuntu.Components 1.3
26+import Ubuntu.Components.ListItems 1.3 as ListItem
27+import Ubuntu.Telephony 0.1 as Telephony
28+import Unity.Test 0.1 as UT
29+
30+StyledItem {
31+ id: root
32+
33+ width: units.gu(160)
34+ height: units.gu(80)
35+ focus: true
36+
37+ theme.name: "Ubuntu.Components.Themes.Ambiance"
38+
39+ Component.onCompleted: {
40+ // must set the mock mode before loading the Shell
41+ LightDMController.userMode = "single";
42+ }
43+
44+ Row {
45+ anchors.fill: parent
46+ Rectangle {
47+ width: root.width - controls.width
48+ height: parent.height
49+ color: "grey"
50+
51+ Rectangle {
52+ id: viewContainer
53+ anchors.horizontalCenter: parent.horizontalCenter
54+ anchors.verticalCenter: parent.verticalCenter
55+ focus: true
56+
57+ Loader {
58+ id: loader
59+ width: parent.width
60+ height: parent.height
61+ focus: true
62+
63+ property bool itemDestroyed: false
64+ sourceComponent: Component {
65+ GreeterView {
66+ id: view
67+
68+ background: "/usr/share/backgrounds/warty-final-ubuntu.png"
69+ backgroundSourceSize: width
70+ userModel: LightDM.Users
71+ infographicModel: LightDM.Infographic
72+
73+ launcherOffset: parseFloat(launcherOffsetField.text)
74+ currentIndex: parseInt(currentIndexField.text, 10)
75+ delayMinutes: parseInt(delayMinutesField.text, 10)
76+ panelHeight: parseFloat(panelHeightField.text)
77+ locked: lockedCheckBox.checked
78+ inputMethodRect: fakeKeyboard.childrenRect
79+ property var testKeyboard: fakeKeyboard
80+
81+ usageMode: usageScenarioSelector.model[usageScenarioSelector.selectedIndex]
82+ multiUser: multiUserCheckBox.checked
83+ orientation: orientationSelector.model[orientationSelector.selectedIndex] == "landscape" ? Qt.LandscapeOrientation : Qt.PortraitOrientation
84+
85+ Component.onDestruction: {
86+ loader.itemDestroyed = true
87+ }
88+
89+ onSelected: {
90+ if (index >= 0)
91+ currentIndexField.text = index;
92+ }
93+
94+ Rectangle {
95+ id: fakeKeyboard
96+ color: "green"
97+ opacity: 0.7
98+ anchors.bottom: view.bottom
99+ width: view.width
100+ height: visible ? view.height * 0.6 : 0
101+ visible: keyboardVisibleCheckBox.checked
102+ Text {
103+ text: "Keyboard Rectangle"
104+ color: "yellow"
105+ font.bold: true
106+ fontSizeMode: Text.Fit
107+ minimumPixelSize: 10; font.pixelSize: 200
108+ verticalAlignment: Text.AlignVCenter
109+ x: (parent.width - width) / 2
110+ y: (parent.height - height) / 2
111+ width: parent.width
112+ height: parent.height
113+ }
114+ }
115+ }
116+ }
117+ }
118+
119+ state: loader.item ? loader.item.usageMode + "-" + (loader.item.isLandscape ? "landscape" : "portrait") : "phone-portrait"
120+
121+ states: [
122+ State {
123+ name: "phone-portrait"
124+ PropertyChanges {
125+ target: viewContainer
126+ width: units.gu(40)
127+ height: units.gu(71)
128+ }
129+ },
130+ State {
131+ name: "phone-landscape"
132+ PropertyChanges {
133+ target: viewContainer
134+ width: units.gu(71)
135+ height: units.gu(40)
136+ }
137+ },
138+ State {
139+ name: "tablet-landscape"
140+ PropertyChanges {
141+ target: viewContainer
142+ width: units.gu(100)
143+ height: units.gu(71)
144+ }
145+ },
146+ State {
147+ name: "tablet-portrait"
148+ PropertyChanges {
149+ target: viewContainer
150+ width: units.gu(71)
151+ height: units.gu(100)
152+ }
153+ },
154+ State {
155+ name: "desktop-portrait"
156+ PropertyChanges {
157+ target: viewContainer
158+ width: parent.width
159+ height: parent.height
160+ }
161+ },
162+ State {
163+ name: "desktop-landscape"
164+ PropertyChanges {
165+ target: viewContainer
166+ width: parent.width
167+ height: parent.height
168+ }
169+ }
170+ ]
171+ }
172+ }
173+
174+ Rectangle {
175+ id: controls
176+ color: theme.palette.normal.background
177+ width: units.gu(40)
178+ height: parent.height
179+ Column {
180+ anchors { left: parent.left; right: parent.right; top: parent.top; margins: units.gu(1) }
181+ spacing: units.gu(1)
182+ Row {
183+ Button {
184+ text: "Hide"
185+ onClicked: loader.item.hide()
186+ }
187+ }
188+ Row {
189+ Button {
190+ text: "Show Message"
191+ onClicked: LightDMService.prompts.append(messageField.text, LightDMService.prompts.Message)
192+ }
193+ TextField {
194+ id: messageField
195+ width: units.gu(10)
196+ text: ""
197+ }
198+ }
199+ Row {
200+ Button {
201+ text: "Show Prompt"
202+ onClicked: LightDMService.prompts.append(promptField.text, isSecretCheckBox.checked ? LightDMService.prompts.Secret : LightDMService.prompts.Question)
203+ }
204+ TextField {
205+ id: promptField
206+ width: units.gu(10)
207+ text: ""
208+ }
209+ CheckBox {
210+ id: isSecretCheckBox
211+ }
212+ Label {
213+ text: "secret"
214+ }
215+ }
216+ Row {
217+ Button {
218+ text: "Notify Auth Failure"
219+ onClicked: {
220+ loader.item.notifyAuthenticationFailed();
221+ }
222+ }
223+ }
224+ Row {
225+ Button {
226+ text: "Try To Unlock"
227+ onClicked: loader.item.tryToUnlock(toTheRightCheckBox.checked)
228+ }
229+ CheckBox {
230+ id: toTheRightCheckBox
231+ }
232+ Label {
233+ text: "toTheRight"
234+ }
235+ }
236+ Label {
237+ text: "Usage scenario"
238+ }
239+
240+ ListItem.ItemSelector {
241+ id: usageScenarioSelector
242+ anchors { left: parent.left; right: parent.right }
243+ activeFocusOnPress: false
244+ model: ["phone", "tablet", "desktop"]
245+ }
246+ Label {
247+ text: "Orientation"
248+ }
249+
250+ ListItem.ItemSelector {
251+ id: orientationSelector
252+ anchors { left: parent.left; right: parent.right }
253+ activeFocusOnPress: false
254+ model: ["portrait", "landscape"]
255+ }
256+ Row {
257+ TextField {
258+ id: launcherOffsetField
259+ width: units.gu(10)
260+ text: "0"
261+ }
262+ Label {
263+ text: "launcherOffset"
264+ }
265+ }
266+ Row {
267+ TextField {
268+ id: currentIndexField
269+ width: units.gu(10)
270+ text: "0"
271+ }
272+ Label {
273+ text: "currentIndex"
274+ }
275+ }
276+ Row {
277+ TextField {
278+ id: delayMinutesField
279+ width: units.gu(10)
280+ text: "0"
281+ }
282+ Label {
283+ text: "delayMinutes"
284+ }
285+ }
286+ Row {
287+ TextField {
288+ id: panelHeightField
289+ width: units.gu(10)
290+ text: "0"
291+ }
292+ Label {
293+ text: "panelHeight"
294+ }
295+ }
296+ Row {
297+ CheckBox {
298+ id: lockedCheckBox
299+ }
300+ Label {
301+ text: "locked"
302+ }
303+ }
304+ Row {
305+ CheckBox {
306+ id: multiUserCheckBox
307+ onClicked: {
308+ if (checked) {
309+ LightDMController.userMode = "full";
310+ } else {
311+ LightDMController.userMode = "single";
312+ }
313+ }
314+ Connections {
315+ target: LightDMController
316+ onSessionModeChanged: {
317+ if (LightDMController.userMode === "full") {
318+ multipleSessionsCheckbox.checked = true;
319+ } else {
320+ multipleSessionsCheckbox.checked = false;
321+ }
322+ }
323+ }
324+ }
325+ Label {
326+ text: "multi user"
327+ }
328+ }
329+ Row {
330+ CheckBox {
331+ id: alphanumericCheckBox
332+ checked: true
333+ }
334+ Label {
335+ text: "alphanumeric"
336+ }
337+ }
338+ Row {
339+ Label {
340+ text: "selected: " + selectedSpy.count
341+ }
342+ }
343+ Row {
344+ Label {
345+ text: "responded: " + respondedSpy.count
346+ }
347+ }
348+ Row {
349+ Label {
350+ text: "teased: " + teaseSpy.count
351+ }
352+ }
353+ Row {
354+ Label {
355+ text: "emergency: " + emergencySpy.count
356+ }
357+ }
358+ Row {
359+ Label {
360+ text: "infographics: " + infographicDataChangedSpy.count
361+ }
362+ }
363+ Row {
364+ id: multipleSessions
365+ CheckBox {
366+ id: multipleSessionsCheckbox
367+ onClicked: {
368+ if (checked) {
369+ LightDMController.sessionMode = "full";
370+ } else {
371+ LightDMController.sessionMode = "single";
372+ }
373+ }
374+ Connections {
375+ target: LightDMController
376+ onSessionModeChanged: {
377+ if (LightDMController.sessionMode === "full") {
378+ multipleSessionsCheckbox.checked = true;
379+ } else {
380+ multipleSessionsCheckbox.checked = false;
381+ }
382+ }
383+ }
384+ }
385+ Label {
386+ text: "Multiple Sessions"
387+ }
388+ }
389+ Row {
390+ Slider {
391+ id: numSessionsSlider
392+
393+ width: units.gu(10)
394+ minimumValue: 0
395+ maximumValue: LightDMController.numAvailableSessions
396+ value: LightDMController.numSessions
397+ visible: LightDMController.sessionMode === "full"
398+ Binding {
399+ target: LightDMController
400+ property: "numSessions"
401+ value: numSessionsSlider.value
402+ }
403+ }
404+ Label {
405+ text: "Available Sessions"
406+ }
407+ }
408+ Row {
409+ Button {
410+ text: "Reload View"
411+ onClicked: {
412+ loader.active = false;
413+ loader.active = true;
414+ }
415+ }
416+ }
417+ Row {
418+ CheckBox {
419+ id: keyboardVisibleCheckBox
420+ }
421+ Label {
422+ text: "Keyboard Visible"
423+ }
424+ }
425+ }
426+ }
427+ }
428+
429+/* FIXME: infographic user is hardcoded to "has-password",
430+ * needs to be set to current user to test in multiUser mode
431+ */
432+ Binding {
433+ target: LightDM.Infographic
434+ property: "username"
435+ value: "has-password"
436+ }
437+
438+ SignalSpy {
439+ id: selectedSpy
440+ target: loader.item
441+ signalName: "selected"
442+ }
443+
444+ SignalSpy {
445+ id: respondedSpy
446+ target: loader.item
447+ signalName: "responded"
448+ }
449+
450+ SignalSpy {
451+ id: teaseSpy
452+ target: loader.item
453+ signalName: "tease"
454+ }
455+
456+ SignalSpy {
457+ id: emergencySpy
458+ target: loader.item
459+ signalName: "emergencyCall"
460+ }
461+
462+ SignalSpy {
463+ id: infographicDataChangedSpy
464+ target: LightDM.Infographic
465+ signalName: "dataChanged"
466+ }
467+
468+ UT.UnityTestCase {
469+ name: "GreeterView"
470+ when: windowShown
471+
472+ property Item view: loader.status === Loader.Ready ? loader.item : null
473+ property url testIconDirectory: Qt.resolvedUrl("../../../qml/Greeter/graphics/session_icons")
474+
475+ function init() {
476+ selectIndex(0); // break binding with text field
477+ tryCompare(LightDMService.prompts, "count", 1);
478+ selectedSpy.clear();
479+ respondedSpy.clear();
480+ teaseSpy.clear();
481+ emergencySpy.clear();
482+ infographicDataChangedSpy.clear();
483+ LightDMController.reset();
484+ LightDM.Sessions.iconSearchDirectories = [testIconDirectory];
485+
486+ waitForRendering(view);
487+ var userList = findChild(view, "userList");
488+ tryCompare(userList, "movingInternally", false);
489+ }
490+
491+ function cleanup() {
492+ keyboardVisibleCheckBox.checked = false;
493+
494+ loader.itemDestroyed = false;
495+ loader.active = false;
496+ tryCompare(loader, "status", Loader.Null);
497+ tryCompare(loader, "item", null);
498+ tryCompare(loader, "itemDestroyed", true);
499+ loader.active = true;
500+ tryCompare(loader, "status", Loader.Ready);
501+ removeTimeConstraintsFromSwipeAreas(loader.item);
502+ LightDMController.sessionMode = "single";
503+ }
504+
505+ function setLightDMMockMode(mode) {
506+ LightDMController.userMode = mode;
507+ waitForRendering(root);
508+ }
509+
510+ function setUsageMode(usageMode) {
511+ view.usageMode = usageMode;
512+ if (usageMode == "desktop") {
513+ view.multiUser = true;
514+ setLightDMMockMode("full");
515+ view.orientation = Qt.LandscapeOrientation;
516+ } else if (usageMode == "phone") {
517+ view.multiUser = false;
518+ setLightDMMockMode("single");
519+ view.orientation = Qt.PortraitOrientation;
520+ telepathyHelper.ready = true;
521+ telepathyHelper.emergencyCallsAvailable = true;
522+ LightDM.Greeter.authenticate("no-password");
523+ tryCompare(LightDMService.prompts, "count", 1);
524+ }
525+ }
526+
527+ function swipeAwayCover(toTheRight) {
528+ if (toTheRight === undefined) {
529+ toTheRight = false;
530+ }
531+
532+ tryCompare(view, "fullyShown", true);
533+ var touchY = view.height / 2;
534+ if (toTheRight) {
535+ touchFlick(view, 0, touchY, view.width, touchY);
536+ } else {
537+ touchFlick(view, view.width, touchY, 0, touchY);
538+ }
539+ var coverPage = findChild(view, "coverPage");
540+ tryCompare(coverPage, "showProgress", 0);
541+ waitForRendering(view);
542+ }
543+
544+ function getIndexOf(name) {
545+ for (var i = 0; i < LightDM.Users.count; i++) {
546+ if (name === LightDM.Users.data(i, LightDM.UserRoles.NameRole)) {
547+ return i;
548+ }
549+ }
550+ fail("Didn't find name")
551+ return -1;
552+ }
553+
554+ function selectIndex(i) {
555+ var user = LightDM.Users.data(i, LightDM.UserRoles.NameRole);
556+ LightDM.Greeter.authenticate(user);
557+
558+ var userList = findChild(view, "userList");
559+ var promptList = findChild(view, "promptList");
560+
561+ view.currentIndex = i;
562+ tryCompare(promptList, "opacity", 1);
563+ tryCompare(userList, "movingInternally", false);
564+ }
565+
566+ function selectUser(name) {
567+ var i = getIndexOf(name);
568+ selectIndex(i);
569+ return i;
570+ }
571+
572+/* Desktop view tests (multiuser) */
573+ function test_sessionless_user_is_still_valid() {
574+ setUsageMode("desktop");
575+ var loginList = findChild(view, "loginList")
576+
577+ /*
578+ * If a user has never logged in before, or, for some reason
579+ * has no sessionHint, ensure that the model returns the default
580+ * session and that the view respects this
581+ */
582+ selectUser("no-session");
583+ compare(LightDM.Users.data(loginList.currentIndex, LightDM.UserRoles.SessionRole), LightDM.Greeter.defaultSession);
584+
585+ tryCompare(loginList, "currentSession", LightDM.Greeter.defaultSession);
586+ }
587+
588+ function test_changingSessionSticksToUser() {
589+ setUsageMode("desktop");
590+ var loginList = findChild(view, "loginList");
591+
592+ selectUser("invalid-session");
593+ tryCompare(loginList, "currentSession", "invalid");
594+
595+ selectUser("has-password");
596+ tryCompare(loginList, "currentSession", "ubuntu");
597+
598+ selectUser("invalid-session")
599+ tryCompare(loginList, "currentSession", "invalid");
600+ }
601+
602+ function test_sessionIconsAreValid() {
603+ setUsageMode("desktop");
604+ LightDMController.sessionMode = "full";
605+ selectUser("has-password");
606+
607+ // Test the login list icon is valid
608+ var sessionChooserButton = findChild(view, "sessionChooserButton");
609+ compare(sessionChooserButton.visible, true);
610+
611+ var loginList = findChild(view, "loginList");
612+ var session = String(loginList.currentSession).toLowerCase();
613+ var icon = String(sessionChooserButton.icon);
614+ compare(icon.indexOf(session) > -1, true);
615+
616+ // Test the session list icons are valid
617+ var lockscreen = findChild(view, "lockscreen");
618+ lockscreen.state = "SessionsList"
619+ var sessionsList = findChild(view, "sessionsList");
620+ tryCompare(sessionsList, "visible", true);
621+ }
622+
623+ function test_choosingNewSessionChangesLoginListIcon() {
624+ // Ensure the default session is selected (Ubuntu)
625+ cleanup();
626+ setUsageMode("desktop");
627+ swipeAwayCover();
628+
629+ LightDMController.sessionMode = "full";
630+ selectUser("has-password");
631+
632+ var sessionChooserButton = findChild(view, "sessionChooserButton");
633+ var icon = String(sessionChooserButton.icon);
634+ compare(icon.indexOf("ubuntu") > -1, true);
635+
636+ tap(sessionChooserButton)
637+ for(var i = 0; i < LightDM.Sessions.count; i++) {
638+ var delegateName = "sessionDelegate" + String(i);
639+ var currentDelegate = findChild(view, delegateName);
640+ verify(currentDelegate);
641+ var sessionKey = LightDM.Sessions.data(i,LightDM.SessionRoles.KeyRole);
642+ if (sessionKey === "gnome-classic") {
643+ waitForRendering(currentDelegate);
644+ tap(currentDelegate);
645+ waitForRendering(sessionChooserButton);
646+ break;
647+ }
648+ }
649+
650+ icon = String(sessionChooserButton.icon);
651+ compare(icon.indexOf("gnome") > -1, true,
652+ "Expected icon to contain gnome but it was " + icon);
653+ }
654+
655+ function test_noSessionsDoesntBreakView() {
656+ setUsageMode("desktop");
657+ LightDMController.sessionMode = "none";
658+ compare(LightDM.Sessions.count, 0)
659+ }
660+
661+ function test_sessionIconNotShownWithOneSession() {
662+ setUsageMode("desktop");
663+ LightDMController.sessionMode = "single";
664+ compare(LightDM.Sessions.count, 1);
665+
666+ var sessionChooserButton = findChild(view, "sessionChooserButton");
667+ tryCompare(sessionChooserButton, "visible", false);
668+ }
669+
670+ function test_sessionIconNotShownWithActiveUser() {
671+ setUsageMode("desktop");
672+ selectUser("active");
673+
674+ var sessionChooserButton = findChild(view, "sessionChooserButton");
675+ tryCompare(sessionChooserButton, "visible", false);
676+ }
677+
678+ function test_sessionIconShownWithMultipleSessions() {
679+ setUsageMode("desktop");
680+ LightDMController.sessionMode = "full";
681+ selectUser("has-password");
682+
683+ var sessionChooserButton = findChild(view, "sessionChooserButton");
684+ tryCompare(sessionChooserButton, "visible", true);
685+ }
686+
687+ function test_selected() {
688+ setUsageMode("desktop");
689+ swipeAwayCover();
690+ var delegate = findChild(view, "username2");
691+ tap(delegate);
692+ compare(selectedSpy.count, 1);
693+ compare(selectedSpy.signalArguments[0][0], 2);
694+ compare(view.currentIndex, 0); // confirm we didn't change
695+ }
696+
697+ function test_customBackground() {
698+ setUsageMode("desktop");
699+ var lockscreen = findChild(view, "lockscreen");
700+ var backgroundShade = findChild(lockscreen, "backgroundShade");
701+
702+ verify(!view.hasCustomBackground);
703+ verify(!backgroundShade.visible);
704+
705+ view.hasCustomBackground = true;
706+ verify(backgroundShade.visible);
707+ }
708+
709+ function test_respondedWithPassword() {
710+ setUsageMode("desktop");
711+ swipeAwayCover();
712+ selectUser("has-password");
713+ var greeterPrompt = findChild(view, "greeterPrompt0");
714+ verify(greeterPrompt.isSecret);
715+ typeString("password");
716+ keyClick(Qt.Key_Enter);
717+ compare(respondedSpy.count, 1);
718+ compare(respondedSpy.signalArguments[0][0], "password");
719+ }
720+
721+ function test_respondedWithNonSecret() {
722+ setUsageMode("desktop");
723+ swipeAwayCover();
724+ selectUser("question-prompt");
725+ var greeterPrompt = findChild(view, "greeterPrompt0");
726+ verify(!greeterPrompt.isSecret);
727+ typeString("foo");
728+ keyClick(Qt.Key_Enter);
729+ compare(respondedSpy.count, 1);
730+ compare(respondedSpy.signalArguments[0][0], "foo");
731+ }
732+
733+ function test_fullyShown_data() {
734+ return [
735+ {tag: "multiuser", multiUser: true, usageMode: "desktop", results: [true, true, true, true]},
736+ {tag: "singleuser", multiUser: false, usageMode: "desktop", results: [true, false, true, false]},
737+ {tag: "multiuser", multiUser: true, usageMode: "phone", results: [true, true, true, true]},
738+ {tag: "singleuser", multiUser: false, usageMode: "phone", results: [true, false, true, false]}
739+ ]
740+ }
741+
742+ function test_fullyShown(data) {
743+ setUsageMode(data.usageMode);
744+ view.multiUser = data.multiUser;
745+ tryCompare(view, "fullyShown", data.results[0]);
746+ tryCompare(view, "required", data.results[0]);
747+ swipeAwayCover();
748+ tryCompare(view, "fullyShown", data.results[1]);
749+ tryCompare(view, "required", data.results[1]);
750+ view.locked = true;
751+ tryCompare(view, "fullyShown", data.results[2]);
752+ tryCompare(view, "required", data.results[2]);
753+ view.locked = false;
754+ tryCompare(view, "fullyShown", data.results[3]);
755+ tryCompare(view, "required", data.results[3]);
756+ }
757+
758+ function test_infoPrompt() {
759+ setUsageMode("desktop");
760+ selectUser("info-prompt");
761+
762+ var infoLabel = findChild(view, "infoLabel0");
763+ compare(infoLabel.text, "Welcome to Unity Greeter");
764+ compare(infoLabel.textFormat, Text.PlainText);
765+
766+ verify(findChild(view, "greeterPrompt1") != null);
767+ }
768+
769+ function test_longInfoPrompt() {
770+ setUsageMode("desktop");
771+ selectUser("long-info-prompt");
772+
773+ var infoLabel = findChild(view, "infoLabel0");
774+ compare(infoLabel.text, "Welcome to Unity Greeter\n\nWe like to annoy you with super ridiculously long messages.\nLike this one\n\nThis is the last line of a multiple line message.");
775+ verify(infoLabel.contentWidth > infoLabel.width);
776+
777+ verify(findChild(view, "greeterPrompt1") != null);
778+ }
779+
780+ function test_multiInfoPrompt() {
781+ setUsageMode("desktop");
782+ selectUser("multi-info-prompt");
783+
784+ var infoLabel0 = findChild(view, "infoLabel0");
785+ compare(infoLabel0.text, "Welcome to Unity Greeter");
786+
787+ var infoLabel1 = findChild(view, "infoLabel1");
788+ compare(infoLabel1.text, "This is an error");
789+ compare(infoLabel1.color, theme.palette.normal.negative);
790+
791+ var infoLabel2 = findChild(view, "infoLabel2");
792+ compare(infoLabel2.text, "You should have seen three messages");
793+
794+ verify(findChild(view, "greeterPrompt3") != null);
795+ }
796+
797+ // Escape is used to reset the authentication, especially if PAM is unresponsive
798+ function test_escape() {
799+ setUsageMode("desktop");
800+ swipeAwayCover();
801+ var index = selectUser("has-password");
802+ selectedSpy.clear();
803+ view.locked = true;
804+ var promptField = findChild(view, "promptField");
805+ tap(promptField);
806+ verify(promptField.activeFocus);
807+ compare(promptField.opacity, 1);
808+
809+ typeString("password");
810+ keyClick(Qt.Key_Enter);
811+ verify(promptField.activeFocus);
812+ compare(promptField.opacity, 0); // hidden by fakeLabel
813+
814+ compare(selectedSpy.count, 0);
815+ keyClick(Qt.Key_Escape);
816+ compare(selectedSpy.count, 1);
817+ compare(selectedSpy.signalArguments[0][0], index);
818+
819+ selectIndex(index);
820+ var promptField = findChild(view, "promptField");
821+ verify(promptField.activeFocus);
822+ compare(promptField.opacity, 1);
823+ }
824+
825+ function test_unicode() {
826+ setUsageMode("desktop");
827+ var index = selectUser("unicode");
828+ var label = findChild(view, "username" + index);
829+ tryCompare(label, "text", "가나다라마");
830+ }
831+
832+ function test_authError() {
833+ setUsageMode("desktop");
834+ swipeAwayCover();
835+ var index = selectUser("auth-error");
836+ var greeterPrompt = findChild(view, "greeterPrompt1"); // after error message
837+ view.locked = true;
838+ compare(greeterPrompt.text, "Retry");
839+ tap(greeterPrompt);
840+ compare(respondedSpy.count, 0);
841+ compare(selectedSpy.count, 1);
842+ compare(selectedSpy.signalArguments[0][0], index);
843+ }
844+
845+ function test_noPassword() {
846+ setUsageMode("desktop");
847+ swipeAwayCover();
848+ selectUser("no-password");
849+ var greeterPrompt = findChild(view, "greeterPrompt0");
850+ compare(greeterPrompt.text, "Log In");
851+ tap(greeterPrompt);
852+ compare(selectedSpy.count, 0);
853+ compare(respondedSpy.count, 1);
854+ compare(respondedSpy.signalArguments[0][0], "");
855+ }
856+
857+ function test_loginListNotCoveredByKeyboard() {
858+ setUsageMode("desktop");
859+ var loginList = findChild(view, "loginList");
860+ compare(loginList.height, view.height);
861+
862+ // when the vkb shows up, loginList is moved up to remain fully uncovered
863+
864+ keyboardVisibleCheckBox.checked = true;
865+
866+ var halfway = (view.height - loginList.highlightedHeight) / 2;
867+ var halfwayWithOsk = halfway - view.inputMethodRect.height / 2;
868+ tryCompare(loginList, "boxVerticalOffset", halfwayWithOsk);
869+
870+ var highlightItem = findChild(loginList, "highlightItem");
871+ tryCompareFunction( function() {
872+ var highlightRect = highlightItem.mapToItem(view, 0, 0, highlightItem.width, highlightItem.height);
873+ return highlightRect.y + highlightRect.height <= view.testKeyboard.y;
874+ }, true);
875+
876+ // once the vkb goes away, loginList goes back to its full height
877+
878+ keyboardVisibleCheckBox.checked = false;
879+
880+ tryCompare(loginList, "boxVerticalOffset", halfway);
881+ }
882+
883+ function test_passphrase() {
884+ setUsageMode("desktop");
885+ swipeAwayCover();
886+ var index = selectUser("has-password");
887+ var promptField = findChild(view, "promptField");
888+
889+ verify(view.alphanumeric);
890+ compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, 0);
891+
892+ keyClick(Qt.Key_D);
893+ compare(promptField.text, "d");
894+ }
895+
896+ function test_passcode() {
897+ setUsageMode("desktop");
898+ swipeAwayCover();
899+ var index = selectUser("has-pin");
900+ view.alphanumeric = false;
901+
902+ var promptField = findChild(view, "promptField");
903+ compare(promptField.inputMethodHints & Qt.ImhDigitsOnly, Qt.ImhDigitsOnly);
904+
905+ keyClick(Qt.Key_D);
906+ compare(promptField.text, "");
907+
908+ keyClick(Qt.Key_0);
909+ keyClick(Qt.Key_0);
910+ keyClick(Qt.Key_0);
911+ keyClick(Qt.Key_0);
912+ compare(promptField.text, "0000");
913+
914+ compare(respondedSpy.count, 1);
915+ compare(respondedSpy.signalArguments[0][0], "0000");
916+
917+ compare(promptField.opacity, 0);
918+ }
919+
920+ function test_loginListMovement_data() {
921+ return [
922+ {tag: "up", key: Qt.Key_Up, result: 1},
923+ {tag: "down", key: Qt.Key_Down, result: 3},
924+ ]
925+ }
926+
927+ function test_loginListMovement(data) {
928+ setUsageMode("desktop");
929+ swipeAwayCover();
930+ selectIndex(2);
931+ selectedSpy.clear();
932+
933+ keyClick(data.key);
934+ compare(selectedSpy.count, 1);
935+ compare(selectedSpy.signalArguments[0][0], data.result);
936+ }
937+
938+ function test_focusStaysActive() {
939+ setUsageMode("desktop");
940+ swipeAwayCover();
941+ selectUser("no-password");
942+ var greeterPrompt = findChild(view, "greeterPrompt0");
943+ verify(greeterPrompt.activeFocus);
944+ keyClick(Qt.Key_Enter);
945+ compare(selectedSpy.count, 0);
946+ compare(respondedSpy.count, 1);
947+ compare(respondedSpy.signalArguments[0][0], "");
948+ verify(greeterPrompt.activeFocus);
949+ keyClick(Qt.Key_Enter);
950+ compare(respondedSpy.count, 1);
951+
952+ selectUser("has-password");
953+ var greeterPrompt = findChild(view, "greeterPrompt0");
954+ verify(greeterPrompt.activeFocus);
955+ keyClick(Qt.Key_D);
956+ keyClick(Qt.Key_Enter);
957+ compare(selectedSpy.count, 0);
958+ compare(respondedSpy.count, 2);
959+ compare(respondedSpy.signalArguments[1][0], "d");
960+ verify(greeterPrompt.activeFocus);
961+ keyClick(Qt.Key_Enter);
962+ compare(respondedSpy.count, 2);
963+
964+ var index = selectUser("no-password");
965+ var greeterPrompt = findChild(view, "greeterPrompt0");
966+ view.locked = true;
967+ verify(greeterPrompt.activeFocus);
968+ keyClick(Qt.Key_Enter);
969+ compare(respondedSpy.count, 2);
970+ compare(selectedSpy.count, 1);
971+ compare(selectedSpy.signalArguments[0][0], index);
972+ verify(greeterPrompt.activeFocus);
973+ keyClick(Qt.Key_Enter);
974+ compare(selectedSpy.count, 1);
975+
976+ selectUser("no-password");
977+ var greeterPrompt = findChild(view, "greeterPrompt0");
978+ verify(greeterPrompt.activeFocus);
979+ }
980+
981+ function test_scrollChangesIndex() {
982+ /* Check if the index of the current user gets changed
983+ * if we scroll through the list.
984+ * Expected result: the list has moved at least by one
985+ * user and the index of the user is reported using the
986+ * selected signal in LoginList.qml
987+ * https://github.com/ubports/unity8/issues/397
988+ */
989+ setUsageMode("desktop");
990+ swipeAwayCover();
991+ var loginList = findChild(view, "loginList");
992+ // FIXME: Fix scrolling sensitivity (scrolling by -1 shouldn't move more than one user down)
993+ mouseWheel( loginList, loginList.width / 2, loginList.height / 2, 0, -1, null );
994+ selectedSpy.wait();
995+ tryVerify(function(){ return selectedSpy.signalArguments[0][0] > 0 });
996+ }
997+
998+// function test_dragChangesIndex() {
999+// /* Check if the index of the current user gets changed
1000+// * if we drag the list or swipe.on it.
1001+// * Expected result: the list has moved at least by one
1002+// * user and the index of the user is reported using the
1003+// * selected signal in LoginList.qml
1004+// * https://github.com/ubports/unity8/issues/397
1005+// */
1006+// setUsageMode("desktop");
1007+// swipeAwayCover();
1008+// var loginList = findChild(view, "loginList");
1009+// // FIXME: Fix scrolling sensitivity (the number of scrolled users is randomly changing)
1010+// touchFlick(loginList, loginList.width/2, loginList.height/3, loginList.width/2, loginList.height/3 -units.gu(2.1));
1011+// selectedSpy.wait();
1012+// tryVerify(function(){ return selectedSpy.signalArguments[0][0] > 0 });
1013+// }
1014+
1015+/* Mobile view tests */
1016+ function test_tease_data() {
1017+ return [
1018+ {tag: "left", x: 0, offset: 0, count: 1},
1019+ {tag: "leftWithOffsetPass", x: 10, offset: 10, count: 1},
1020+ {tag: "leftWithOffsetFail", x: 9, offset: 10, count: 0},
1021+ {tag: "right", x: view.width, offset: 0, count: 1},
1022+ ]
1023+ }
1024+ function test_tease(data) {
1025+ view.dragHandleLeftMargin = data.offset;
1026+ tap(view, data.x, 0);
1027+ tryCompare(teaseSpy, "count", data.count);
1028+ }
1029+
1030+ function test_respondedWithPin() {
1031+ LightDM.Greeter.authenticate("has-pin");
1032+ view.locked = true;
1033+ view.alphanumeric = false;
1034+ swipeAwayCover();
1035+ typeString("1234");
1036+ respondedSpy.wait();
1037+ compare(respondedSpy.signalArguments[0][0], "1234");
1038+ }
1039+
1040+ function test_respondedWithPassphrase() {
1041+ LightDM.Greeter.authenticate("has-password");
1042+ view.locked = true;
1043+ swipeAwayCover();
1044+ typeString("test");
1045+ keyClick(Qt.Key_Enter);
1046+ compare(respondedSpy.count, 1);
1047+ compare(respondedSpy.signalArguments[0][0], "test");
1048+ }
1049+
1050+ function test_respondedWithSwipe_data() {
1051+ return [
1052+ {tag: "left", toTheRight: false, hiddenX: -view.width},
1053+ {tag: "right", toTheRight: true, hiddenX: view.width},
1054+ ];
1055+ }
1056+ function test_respondedWithSwipe(data) {
1057+ swipeAwayCover(data.toTheRight);
1058+ var coverPage = findChild(view, "coverPage");
1059+ compare(coverPage.x, data.hiddenX);
1060+ compare(respondedSpy.count, 1);
1061+ compare(respondedSpy.signalArguments[0][0], "");
1062+ }
1063+
1064+ function test_emergencyCall_data() {
1065+ return [
1066+ {tag: "phone", available: true},
1067+ {tag: "desktop", available: false},
1068+ ];
1069+ }
1070+ function test_emergencyCall(data) {
1071+ telepathyHelper.emergencyCallsAvailable = data.available;
1072+ view.locked = true;
1073+ swipeAwayCover();
1074+ var emergencyCallLabel = findChild(view, "emergencyCallLabel");
1075+ tap(emergencyCallLabel);
1076+ if (!data.available) {
1077+ expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
1078+ }
1079+ compare(emergencyCallLabel.visible, data.available ? true : false);
1080+ compare(emergencySpy.count, data.available ? 1 : 0);
1081+ }
1082+
1083+ function test_emergencyCallAvailability() {
1084+ view.locked = true;
1085+ var emergencyCallLabel = findChild(view, "emergencyCallLabel");
1086+ verify(emergencyCallLabel.visible);
1087+
1088+ telepathyHelper.emergencyCallsAvailable = false;
1089+ telepathyHelper.ready = true;
1090+ expectFail("", "Bug 1616538 prevents us supporting conditional emergency button support");
1091+ verify(!emergencyCallLabel.visible);
1092+
1093+ telepathyHelper.emergencyCallsAvailable = true;
1094+ telepathyHelper.ready = false;
1095+ verify(!emergencyCallLabel.visible);
1096+ }
1097+
1098+ function test_tryToUnlock() {
1099+ var coverPage = findChild(view, "coverPage");
1100+ tryCompare(coverPage, "showProgress", 1);
1101+ compare(view.tryToUnlock(false), true);
1102+ tryCompare(coverPage, "showProgress", 0);
1103+ compare(view.tryToUnlock(false), false);
1104+ }
1105+
1106+ /*
1107+ Regression test for https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1388359
1108+ "User metrics can no longer be changed by double tap"
1109+ */
1110+ function test_doubleTapSwitchesToNextInfographic() {
1111+ setUsageMode("phone");
1112+ infographicDataChangedSpy.clear();
1113+ var coverPage = findChild(view, "coverPage");
1114+ verify(coverPage);
1115+
1116+ var infographicPrivate = findInvisibleChild(coverPage, "infographicPrivate");
1117+ verify(infographicPrivate);
1118+
1119+ // wait for the UI to settle down before double tapping it
1120+ tryCompare(infographicPrivate, "animating", false);
1121+
1122+ var dataCircle = findChild(coverPage, "dataCircle");
1123+ verify(dataCircle);
1124+
1125+ mouseDoubleClickSequence(dataCircle);
1126+
1127+ tryCompare(infographicDataChangedSpy, "count", 1);
1128+ }
1129+
1130+ function test_movesBackIntoPlaceWhenNotDraggedFarEnough() {
1131+ var coverPage = findChild(view, "coverPage");
1132+
1133+ var dragEvaluator = findInvisibleChild(coverPage, "edgeDragEvaluator");
1134+ verify(dragEvaluator);
1135+
1136+ // Make it easier to get a rejection/rollback. Otherwise would have to inject
1137+ // a fake timer into dragEvaluator.
1138+ // Afterall, we are testing if the CoverPage indeed moves back on a
1139+ // rollback decision, not the drag evaluation itself.
1140+ dragEvaluator.minDragDistance = dragEvaluator.maxDragDistance / 2;
1141+
1142+ // it starts as fully shown
1143+ compare(coverPage.x, 0);
1144+
1145+ // then we drag it a bit
1146+ var startX = coverPage.width - 1;
1147+ var touchY = coverPage.height / 2;
1148+ var dragXDelta = -(dragEvaluator.minDragDistance * 0.3);
1149+ touchFlick(coverPage,
1150+ startX , touchY, // start pos
1151+ startX + dragXDelta, touchY, // end pos
1152+ true /* beginTouch */, false /* endTouch */);
1153+
1154+ // which should make it move a bit
1155+ tryCompareFunction(function() {return coverPage.x < 0;}, true);
1156+
1157+ // then we release it
1158+ touchRelease(coverPage, startX + dragXDelta, touchY);
1159+
1160+ // which should make it move back into its original position as it didn't move
1161+ // far enough to have it hidden
1162+ tryCompare(coverPage, "x", 0);
1163+ }
1164+
1165+ function test_dragToHide_data() {
1166+ return [
1167+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1, hiddenX: -view.width},
1168+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95, hiddenX: view.width},
1169+ ];
1170+ }
1171+ function test_dragToHide(data) {
1172+ var coverPage = findChild(view, "coverPage");
1173+ compare(coverPage.x, 0);
1174+ compare(coverPage.visible, true);
1175+ compare(coverPage.shown, true);
1176+ compare(coverPage.showProgress, 1);
1177+ compare(view.fullyShown, true);
1178+
1179+ touchFlick(view,
1180+ data.startX, view.height / 2, // start pos
1181+ data.endX, view.height / 2); // end pos
1182+
1183+ tryCompare(coverPage, "x", data.hiddenX);
1184+ tryCompare(coverPage, "visible", false);
1185+ tryCompare(coverPage, "shown", false);
1186+ tryCompare(coverPage, "showProgress", 0);
1187+ compare(view.fullyShown, false);
1188+ }
1189+
1190+ function test_hiddenViewRemainsHiddenAfterResize_data() {
1191+ return [
1192+ {tag: "left", startX: view.width * 0.95, endX: view.width * 0.1},
1193+ {tag: "right", startX: view.width * 0.1, endX: view.width * 0.95},
1194+ ];
1195+ }
1196+ function test_hiddenViewRemainsHiddenAfterResize(data) {
1197+ touchFlick(view,
1198+ data.startX, view.height / 2, // start pos
1199+ data.endX, view.height / 2); // end pos
1200+
1201+ var coverPage = findChild(view, "coverPage");
1202+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
1203+ tryCompare(coverPage, "visible", false);
1204+ tryCompare(coverPage, "shown", false);
1205+ tryCompare(coverPage, "showProgress", 0);
1206+
1207+ // flip dimensions to simulate an orientation change
1208+ view.width = loader.height;
1209+ view.height = loader.width;
1210+
1211+ // All properties should remain consistent
1212+ tryCompare(coverPage, "x", data.tag == "left" ? -view.width : view.width);
1213+ tryCompare(coverPage, "visible", false);
1214+ tryCompare(coverPage, "shown", false);
1215+ tryCompare(coverPage, "showProgress", 0);
1216+ }
1217+
1218+ // Make sure that if user has a mouse, they can still get rid of cover page
1219+ function test_mouseClickHidesCoverPage() {
1220+ var coverPage = findChild(view, "coverPage");
1221+
1222+ verify(coverPage.shown);
1223+ mouseClick(coverPage, coverPage.width/2, coverPage.height - units.gu(2));
1224+ verify(!coverPage.shown);
1225+ }
1226+
1227+ function test_showErrorMessage() {
1228+ var coverPage = findChild(view, "coverPage");
1229+ var swipeHint = findChild(coverPage, "swipeHint");
1230+ var errorMessageAnimation = findInvisibleChild(coverPage, "errorMessageAnimation");
1231+
1232+ view.showErrorMessage("hello");
1233+ compare(swipeHint.text, "《 hello 》");
1234+ verify(errorMessageAnimation.running);
1235+ verify(swipeHint.opacityAnimation.running);
1236+
1237+ errorMessageAnimation.complete();
1238+ swipeHint.opacityAnimation.complete();
1239+ tryCompare(swipeHint, "text", "《 " + i18n.tr("Unlock") + " 》");
1240+ }
1241+
1242+ function test_keepPasswordOnRotation() {
1243+ /* Check if the password is kept when rotating
1244+ * the screen from portrait to landscape in mobile.
1245+ */
1246+ setUsageMode("phone");
1247+ LightDM.Greeter.authenticate("has-password");
1248+ view.locked = true;
1249+ swipeAwayCover();
1250+
1251+ let greeterPrompt = findChild(view, "greeterPrompt0");
1252+ tryCompare(greeterPrompt, "enteredText", "");
1253+ typeString("test");
1254+ tryCompare(greeterPrompt, "enteredText", "test");
1255+ view.orientation = Qt.LandscapeOrientation;
1256+ tryCompare(greeterPrompt, "enteredText", "test");
1257+ }
1258+
1259+ function test_infographicsShownOnRotation_data() {
1260+ return [
1261+ {
1262+ tag: "phone-portrait-singleuser",
1263+ multiUser: false,
1264+ usageMode: "phone",
1265+ orientation: Qt.PortraitOrientation,
1266+ coverPage: true,
1267+ lockscreen: false
1268+ },
1269+ {
1270+ tag: "phone-portrait-multiuser",
1271+ multiUser: true,
1272+ usageMode: "phone",
1273+ orientation: Qt.PortraitOrientation,
1274+ coverPage: false,
1275+ lockscreen: false
1276+ },
1277+ {
1278+ tag: "phone-landscape-singleuser",
1279+ multiUser: false,
1280+ usageMode: "phone",
1281+ orientation: Qt.LandscapeOrientation,
1282+ coverPage: true,
1283+ lockscreen: false
1284+ },
1285+ {
1286+ tag: "phone-landscape-multiuser",
1287+ multiUser: true,
1288+ usageMode: "phone",
1289+ orientation: Qt.LandscapeOrientation,
1290+ coverPage: false,
1291+ lockscreen: false
1292+ },
1293+ {
1294+ tag: "tablet-landscape-singleuser",
1295+ multiUser: false,
1296+ usageMode: "tablet",
1297+ orientation: Qt.LandscapeOrientation,
1298+ coverPage: true,
1299+ lockscreen: false
1300+ },
1301+ {
1302+ tag: "tablet-landscape-multiuser",
1303+ multiUser: true,
1304+ usageMode: "tablet",
1305+ orientation: Qt.LandscapeOrientation,
1306+ coverPage: false,
1307+ lockscreen: true
1308+ },
1309+ {
1310+ tag: "tablet-portrait-singleuser",
1311+ multiUser: false,
1312+ usageMode: "tablet",
1313+ orientation: Qt.PortraitOrientation,
1314+ coverPage: true,
1315+ lockscreen: false
1316+ },
1317+ {
1318+ tag: "tablet-portrait-multiuser",
1319+ multiUser: true,
1320+ usageMode: "tablet",
1321+ orientation: Qt.PortraitOrientation,
1322+ coverPage: false,
1323+ lockscreen: false
1324+ },
1325+ {
1326+ tag: "desktop-singleuser",
1327+ multiUser: false,
1328+ usageMode: "desktop",
1329+ orientation: Qt.LandscapeOrientation,
1330+ coverPage: false,
1331+ lockscreen: true
1332+ },
1333+ {
1334+ tag: "desktop-multiuser",
1335+ multiUser: true,
1336+ usageMode: "desktop",
1337+ orientation: Qt.LandscapeOrientation,
1338+ coverPage: false,
1339+ lockscreen: true
1340+ }
1341+ ]
1342+ }
1343+
1344+ function test_infographicsShownOnRotation(data) {
1345+ /* Check if infographics is shown in the correct
1346+ * places. This test runs in all the combinations
1347+ * of orientation and usage mode.
1348+ */
1349+ setUsageMode(data.usageMode);
1350+ view.orientation = data.orientation;
1351+ view.multiUser = data.multiUser;
1352+ view.locked = true;
1353+
1354+ let coverPage = findChild(view, "coverPage");
1355+ let lockscreen = findChild(view, "lockscreen");
1356+ let coverPageInfographics = findChild(coverPage, "infographicsLoader");
1357+ let lockscreenInfographics = findChild(lockscreen, "infographicsLoader");
1358+ verify(coverPageInfographics);
1359+ verify(lockscreenInfographics);
1360+
1361+ tryCompare(coverPageInfographics, "active", data.coverPage);
1362+ swipeAwayCover();
1363+ tryCompare(lockscreenInfographics, "active", data.lockscreen);
1364+ }
1365+ }
1366+}