Merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/noCopyPass into lp:ubuntu-ui-toolkit/staging

Proposed by Christian Dywan on 2015-05-19
Status: Merged
Approved by: Zsombor Egri on 2015-05-20
Approved revision: 1515
Merged at revision: 1514
Proposed branch: lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/noCopyPass
Merge into: lp:ubuntu-ui-toolkit/staging
Diff against target: 504 lines (+406/-13)
4 files modified
modules/Ubuntu/Components/1.2/TextInputPopover.qml (+7/-6)
modules/Ubuntu/Components/1.3/TextInputPopover.qml (+7/-6)
tests/unit_x11/tst_components/tst_textinput_common.qml (+43/-1)
tests/unit_x11/tst_components/tst_textinput_common13.qml (+349/-0)
To merge this branch: bzr merge lp:~ubuntu-sdk-team/ubuntu-ui-toolkit/noCopyPass
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Approve on 2015-05-20
Zsombor Egri (community) 2015-05-19 Approve on 2015-05-20
Review via email: mp+259497@code.launchpad.net

Commit Message

No cutting and copying of passwords

To post a comment you must log in.
Christian Dywan (kalikiana) wrote :

As per discussion I'll get back the fix for 1.2 and duplicate the test cases.

Zsombor Egri (zsombi) wrote :

Looks good, thanks

review: Approve
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'modules/Ubuntu/Components/1.2/TextInputPopover.qml'
2--- modules/Ubuntu/Components/1.2/TextInputPopover.qml 2015-04-30 08:32:44 +0000
3+++ modules/Ubuntu/Components/1.2/TextInputPopover.qml 2015-05-20 17:25:08 +0000
4@@ -22,12 +22,14 @@
5 id: popover
6 objectName: "text_input_contextmenu"
7 property Item target
8+ property bool canCopy: target && target.selectedText !== "" && !popover.password
9+ property bool password: target && target.hasOwnProperty('echoMode') && target.echoMode == TextInput.Password
10 property list<Action> actions: [
11 Action {
12 text: i18n.dtr('ubuntu-ui-toolkit', "Select All")
13 iconName: "edit-select-all"
14- enabled: target.text !== ""
15- visible: target && target.selectedText === ""
16+ enabled: target && target.text !== "" && target.selectedText === ""
17+ visible: target && (target.selectedText === "" || popover.password)
18 onTriggered: target.selectAll()
19 },
20 Action {
21@@ -35,8 +37,8 @@
22 iconName: "edit-cut"
23 // If paste/editing is not possible, then disable also "Cut" operation
24 // It is applicable for ReadOnly's TextFields and TextAreas
25- enabled: target && target.selectedText !== "" && !target.readOnly
26- visible: target.selectedText !== ""
27+ enabled: !target.readOnly
28+ visible: popover.canCopy
29 onTriggered: {
30 PopupUtils.close(popover);
31 target.cut();
32@@ -45,8 +47,7 @@
33 Action {
34 text: i18n.dtr('ubuntu-ui-toolkit', "Copy")
35 iconName: "edit-copy"
36- enabled: target && target.selectedText !== ""
37- visible: target.selectedText !== ""
38+ visible: popover.canCopy
39 onTriggered: {
40 PopupUtils.close(popover);
41 target.copy();
42
43=== modified file 'modules/Ubuntu/Components/1.3/TextInputPopover.qml'
44--- modules/Ubuntu/Components/1.3/TextInputPopover.qml 2015-04-29 08:55:31 +0000
45+++ modules/Ubuntu/Components/1.3/TextInputPopover.qml 2015-05-20 17:25:08 +0000
46@@ -22,12 +22,14 @@
47 id: popover
48 objectName: "text_input_contextmenu"
49 property Item target
50+ property bool canCopy: target && target.selectedText !== "" && !popover.password
51+ property bool password: target && target.hasOwnProperty('echoMode') && target.echoMode == TextInput.Password
52 property list<Action> actions: [
53 Action {
54 text: i18n.dtr('ubuntu-ui-toolkit', "Select All")
55 iconName: "edit-select-all"
56- enabled: target.text !== ""
57- visible: target && target.selectedText === ""
58+ enabled: target && target.text !== "" && target.selectedText === ""
59+ visible: target && (target.selectedText === "" || popover.password)
60 onTriggered: target.selectAll()
61 },
62 Action {
63@@ -35,8 +37,8 @@
64 iconName: "edit-cut"
65 // If paste/editing is not possible, then disable also "Cut" operation
66 // It is applicable for ReadOnly's TextFields and TextAreas
67- enabled: target && target.selectedText !== "" && !target.readOnly
68- visible: target.selectedText !== ""
69+ enabled: !target.readOnly
70+ visible: popover.canCopy
71 onTriggered: {
72 PopupUtils.close(popover);
73 target.cut();
74@@ -45,8 +47,7 @@
75 Action {
76 text: i18n.dtr('ubuntu-ui-toolkit', "Copy")
77 iconName: "edit-copy"
78- enabled: target && target.selectedText !== ""
79- visible: target.selectedText !== ""
80+ visible: popover.canCopy
81 onTriggered: {
82 PopupUtils.close(popover);
83 target.copy();
84
85=== modified file 'tests/unit_x11/tst_components/tst_textinput_common.qml'
86--- tests/unit_x11/tst_components/tst_textinput_common.qml 2015-03-03 13:20:06 +0000
87+++ tests/unit_x11/tst_components/tst_textinput_common.qml 2015-05-20 17:25:08 +0000
88@@ -1,5 +1,5 @@
89 /*
90- * Copyright 2014 Canonical Ltd.
91+ * Copyright 2014-2015 Canonical Ltd.
92 *
93 * This program is free software; you can redistribute it and/or modify
94 * it under the terms of the GNU Lesser General Public License as published by
95@@ -32,6 +32,11 @@
96 TextArea {
97 id: textArea
98 }
99+ TextField {
100+ id: password
101+ echoMode: TextInput.Password
102+ text: 'deadbeef'
103+ }
104 }
105
106 SignalSpy {
107@@ -94,6 +99,43 @@
108 scrollerSpy.clear();
109 }
110
111+ function test_context_menu_items_data() {
112+ return [
113+ { tag: 'textField with text', input: textField, text: "lalelu", all: true, copy: false },
114+ { tag: 'textField selected', input: textField, text: "lalelu", select: true, all: false, copy: true },
115+ { tag: 'textArea with text', input: textArea, text: "lalelu", all: true, copy: false },
116+ { tag: 'textArea selected', input: textArea, text: "lalelu", select: true, all: false, copy: true },
117+ { tag: 'textField with password', input: password, text: "deadbeef", all: true, copy: false },
118+ ]
119+ }
120+
121+ function test_context_menu_items(data) {
122+ var handler = findChild(data.input, "input_handler");
123+ popupSpy.target = handler;
124+ data.input.focus = true;
125+
126+ var x = data.input.width / 2;
127+ var y = data.input.height / 2;
128+ mouseClick(data.input, x, y, Qt.RightButton);
129+ popupSpy.wait();
130+ var popover = findChild(testMain, "text_input_contextmenu");
131+ verify(popover, "Cannot retrieve default TextInputPopover");
132+ waitForRendering(popover);
133+
134+ if (data.select) {
135+ var selectAll = findChildWithProperty(popover, "text", "Select All");
136+ verify(selectAll, "Select All item not found");
137+ mouseClick(selectAll, selectAll.width / 2, selectAll.height / 2);
138+ waitForRendering(data.input, 1000);
139+ compare(data.input.text, data.input.selectedText, "Not all the text is selected");
140+ }
141+
142+ var all = findChildWithProperty(popover, "text", "Select All");
143+ compare(all.visible, data.all, "Select All%1expected".arg(data.all ? " " : " not "))
144+ var copy = findChildWithProperty(popover, "text", "Copy");
145+ compare(copy.visible, data.copy, "Copy%1expected".arg(data.copy ? " " : " not "))
146+ }
147+
148 function test_clear_text_using_popover_data() {
149 return [
150 {input: textField},
151
152=== added file 'tests/unit_x11/tst_components/tst_textinput_common13.qml'
153--- tests/unit_x11/tst_components/tst_textinput_common13.qml 1970-01-01 00:00:00 +0000
154+++ tests/unit_x11/tst_components/tst_textinput_common13.qml 2015-05-20 17:25:08 +0000
155@@ -0,0 +1,349 @@
156+/*
157+ * Copyright 2014-2015 Canonical Ltd.
158+ *
159+ * This program is free software; you can redistribute it and/or modify
160+ * it under the terms of the GNU Lesser General Public License as published by
161+ * the Free Software Foundation; version 3.
162+ *
163+ * This program is distributed in the hope that it will be useful,
164+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
165+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
166+ * GNU Lesser General Public License for more details.
167+ *
168+ * You should have received a copy of the GNU Lesser General Public License
169+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
170+ */
171+
172+import QtQuick 2.0
173+import QtTest 1.0
174+import Ubuntu.Test 1.0
175+import Ubuntu.Components 1.3
176+
177+Item {
178+ id: testMain
179+ width: units.gu(40)
180+ height: units.gu(71)
181+
182+ Column {
183+ spacing: units.gu(1)
184+ TextField {
185+ id: textField
186+ }
187+ TextArea {
188+ id: textArea
189+ }
190+ TextField {
191+ id: password
192+ echoMode: TextInput.Password
193+ text: 'deadbeef'
194+ }
195+ }
196+
197+ SignalSpy {
198+ id: cursorPositionSpy
199+ signalName: "onCursorPositionChanged"
200+ }
201+ SignalSpy {
202+ id: selectionStartSpy
203+ signalName: "onSelectionStartChanged"
204+ }
205+ SignalSpy {
206+ id: selectionEndSpy
207+ signalName: "onSelectionEndChanged"
208+ }
209+ SignalSpy {
210+ id: selectedTextSpy
211+ signalName: "onSelectedTextChanged"
212+ }
213+ SignalSpy {
214+ id: popupSpy
215+ signalName: "pressAndHold"
216+ }
217+ SignalSpy {
218+ id: movementXSpy
219+ signalName: "onContentXChanged"
220+ }
221+ SignalSpy {
222+ id: movementYSpy
223+ signalName: "contentYChanged"
224+ }
225+ SignalSpy {
226+ id: scrollerSpy
227+ signalName: "movementEnded"
228+ }
229+
230+ UbuntuTestCase {
231+ name: "TextInputCommonTest"
232+ when: windowShown
233+
234+ function init() {
235+ textField.text = "This is a single line text input called TextField.";
236+ textArea.text = "This is a multiline text input component called TextArea. It supports fix size as well as auto-expanding behavior. The content is scrollable only if it exceeds the visible area.";
237+ textField.cursorPosition = 0;
238+ textArea.cursorPosition = 0;
239+ waitForRendering(textField, 500);
240+ waitForRendering(textArea, 500);
241+ }
242+
243+ function cleanup() {
244+ textField.focus = false;
245+ textArea.focus = false;
246+ cursorPositionSpy.clear();
247+ selectionStartSpy.clear();
248+ selectionEndSpy.clear();
249+ selectedTextSpy.clear();
250+ popupSpy.clear();
251+ movementXSpy.clear();
252+ movementYSpy.clear();
253+ cursorRectSpy.clear();
254+ scrollerSpy.clear();
255+ }
256+
257+ function test_context_menu_items_data() {
258+ return [
259+ { tag: 'textField with text', input: textField, text: "lalelu", all: true, copy: false },
260+ { tag: 'textField selected', input: textField, text: "lalelu", select: true, all: false, copy: true },
261+ { tag: 'textArea with text', input: textArea, text: "lalelu", all: true, copy: false },
262+ { tag: 'textArea selected', input: textArea, text: "lalelu", select: true, all: false, copy: true },
263+ { tag: 'textField with password', input: password, text: "deadbeef", all: true, copy: false },
264+ ]
265+ }
266+
267+ function test_context_menu_items(data) {
268+ var handler = findChild(data.input, "input_handler");
269+ popupSpy.target = handler;
270+ data.input.focus = true;
271+
272+ var x = data.input.width / 2;
273+ var y = data.input.height / 2;
274+ mouseClick(data.input, x, y, Qt.RightButton);
275+ popupSpy.wait();
276+ var popover = findChild(testMain, "text_input_contextmenu");
277+ verify(popover, "Cannot retrieve default TextInputPopover");
278+ waitForRendering(popover);
279+
280+ if (data.select) {
281+ var selectAll = findChildWithProperty(popover, "text", "Select All");
282+ verify(selectAll, "Select All item not found");
283+ mouseClick(selectAll, selectAll.width / 2, selectAll.height / 2);
284+ waitForRendering(data.input, 1000);
285+ compare(data.input.text, data.input.selectedText, "Not all the text is selected");
286+ }
287+
288+ var all = findChildWithProperty(popover, "text", "Select All");
289+ compare(all.visible, data.all, "Select All%1expected".arg(data.all ? " " : " not "))
290+ var copy = findChildWithProperty(popover, "text", "Copy");
291+ compare(copy.visible, data.copy, "Copy%1expected".arg(data.copy ? " " : " not "))
292+ }
293+
294+ function test_clear_text_using_popover_data() {
295+ return [
296+ {input: textField},
297+ {input: textArea},
298+ ];
299+ }
300+
301+ function test_clear_text_using_popover(data) {
302+ var handler = findChild(data.input, "input_handler");
303+ popupSpy.target = handler;
304+ data.input.focus = true;
305+
306+ // invoke popover
307+ var x = data.input.width / 2;
308+ var y = data.input.height / 2;
309+ mouseClick(data.input, x, y, Qt.RightButton);
310+ popupSpy.wait();
311+ var popover = findChild(testMain, "text_input_contextmenu");
312+ verify(popover, "Cannot retrieve default TextInputPopover");
313+ waitForRendering(popover);
314+ // select all
315+ var selectAll = findChildWithProperty(popover, "text", "Select All");
316+ verify(selectAll, "Select All item not found");
317+ mouseClick(selectAll, selectAll.width / 2, selectAll.height / 2);
318+ waitForRendering(data.input, 1000);
319+ compare(data.input.text, data.input.selectedText, "Not all the text is selected");
320+ // delete with key press
321+ keyClick(Qt.Key_Backspace);
322+ waitForRendering(data.input, 1000);
323+ compare(data.input.text, "", "The text has not been deleted");
324+
325+ // dismiss popover
326+ mouseClick(testMain, testMain.width / 2, testMain.height / 2);
327+ wait(200);
328+ }
329+
330+ SignalSpy {
331+ id: cursorRectSpy
332+ signalName: "cursorRectangleChanged"
333+ }
334+
335+ function test_input_pageup_pagedown_data() {
336+ return [
337+ {tag: "PageUp in TextField", input: textField, moveToEnd: true, key: Qt.Key_PageUp, modifier: Qt.NoModifier, xfail: false},
338+ {tag: "PageDown in TextField", input: textField, moveToEnd: false, key: Qt.Key_PageDown, modifier: Qt.NoModifier, xfail: false},
339+ {tag: "PageUp in TextArea", input: textArea, moveToEnd: true, key: Qt.Key_PageUp, modifier: Qt.NoModifier, xfail: false},
340+ {tag: "PageDown in TextArea", input: textArea, moveToEnd: false, key: Qt.Key_PageDown, modifier: Qt.NoModifier, xfail: false},
341+ {tag: "Ctrl+PageUp in TextField", input: textField, moveToEnd: true, key: Qt.Key_PageUp, modifier: Qt.ControlModifier, xfail: true},
342+ {tag: "Ctrl+PageDown in TextField", input: textField, moveToEnd: false, key: Qt.Key_PageDown, modifier: Qt.ControlModifier, xfail: true},
343+ {tag: "Ctrl+PageUp in TextArea", input: textArea, moveToEnd: true, key: Qt.Key_PageUp, modifier: Qt.ControlModifier, xfail: true},
344+ {tag: "Ctrl+PageDown in TextArea", input: textArea, moveToEnd: false, key: Qt.Key_PageDown, modifier: Qt.ControlModifier, xfail: true},
345+ {tag: "Shift+PageUp in TextField", input: textField, moveToEnd: true, key: Qt.Key_PageUp, modifier: Qt.ShiftModifier, xfail: true},
346+ {tag: "Shift+PageDown in TextField", input: textField, moveToEnd: false, key: Qt.Key_PageDown, modifier: Qt.ShiftModifier, xfail: true},
347+ {tag: "Shift+PageUp in TextArea", input: textArea, moveToEnd: true, key: Qt.Key_PageUp, modifier: Qt.ShiftModifier, xfail: true},
348+ {tag: "Shift+PageDown in TextArea", input: textArea, moveToEnd: false, key: Qt.Key_PageDown, modifier: Qt.ShiftModifier, xfail: true},
349+ {tag: "Alt+PageUp in TextField", input: textField, moveToEnd: true, key: Qt.Key_PageUp, modifier: Qt.AltModifier, xfail: true},
350+ {tag: "Alt+PageDown in TextField", input: textField, moveToEnd: false, key: Qt.Key_PageDown, modifier: Qt.AltModifier, xfail: true},
351+ {tag: "Alt+PageUp in TextArea", input: textArea, moveToEnd: true, key: Qt.Key_PageUp, modifier: Qt.AltModifier, xfail: true},
352+ {tag: "Alt+PageDown in TextArea", input: textArea, moveToEnd: false, key: Qt.Key_PageDown, modifier: Qt.AltModifier, xfail: true},
353+ ];
354+ }
355+ function test_input_pageup_pagedown(data) {
356+ var handler = findChild(data.input, "input_handler");
357+ data.input.focus = true;
358+
359+ // move the cursor to the end
360+ if (data.moveToEnd) {
361+ keyClick(Qt.Key_End);
362+ waitForRendering(data.input, 500);
363+ verify(data.input.cursorPosition > 0, "The cursor wasn't moved");
364+ }
365+ cursorRectSpy.target = data.input;
366+ keyClick(data.key, data.modifier);
367+ waitForRendering(data.input, 500);
368+ if (data.xfail) {
369+ expectFailContinue(data.tag, "With modifier");
370+ }
371+ cursorRectSpy.wait(500);
372+ cursorRectSpy.target = null;
373+ }
374+
375+ function test_scroll_when_not_focused_data() {
376+ return [
377+ // dx and dy are in eights of a degree; see QWheelEvent::angleDelta() for more details.
378+ {tag: "TextField", input: textField, x: textField.width / 2, y: textField.height / 2, dx: -240, dy: 0},
379+ {tag: "TextArea", input: textArea, x: textField.width / 2, y: textField.height / 2, dx: 0, dy: -240},
380+ ];
381+ }
382+ function test_scroll_when_not_focused(data) {
383+ var scroller = findChild(data.input, "input_scroller");
384+ scrollerSpy.target = scroller;
385+
386+ mouseWheel(data.input, data.x, data.y, data.dx, data.dy);
387+ expectFailContinue(data.tag, "Content must not scroll while inactive");
388+ scrollerSpy.wait(500);
389+ }
390+
391+ function test_scroll_when_focused_data() {
392+ return [
393+ // dx and dy are in eights of a degree; see QWheelEvent::angleDelta() for more details.
394+ {tag: "TextField", input: textField, x: textField.width / 2, y: textField.height / 2, dx: -480, dy: 0},
395+ {tag: "TextArea", input: textArea, x: textArea.width / 2, y: textArea.height / 2, dx: 0, dy: -480},
396+ ];
397+ }
398+ function test_scroll_when_focused(data) {
399+ var scroller = findChild(data.input, "input_scroller");
400+ scrollerSpy.target = scroller;
401+
402+ // focus component
403+ data.input.focus = true;
404+
405+ mouseWheel(data.input, data.x, data.y, data.dx, data.dy);
406+ waitForRendering(data.input);
407+ scrollerSpy.wait();
408+ }
409+
410+ function test_rightclick_opens_popover_data() {
411+ return [
412+ {tag: "TextField active", input: textField, whenFocused: true},
413+ {tag: "TextArea active" , input: textArea, whenFocused: true},
414+ {tag: "TextField inactive", input: textField, whenFocused: false},
415+ {tag: "TextArea inactive" , input: textArea, whenFocused: false},
416+ ];
417+ }
418+ function test_rightclick_opens_popover(data) {
419+ var handler = findChild(data.input, "input_handler");
420+ popupSpy.target = handler;
421+
422+ if (data.whenFocused) {
423+ data.input.focus = true;
424+ waitForRendering(data.input);
425+ }
426+ mouseClick(data.input, data.input.width / 2, data.input.height / 2, Qt.RightButton);
427+ waitForRendering(data.input);
428+ popupSpy.wait();
429+ verify(data.input.cursorPosition !== 0, "Cursor should be moved to the mouse click position.")
430+
431+ // dismiss popover
432+ mouseClick(testMain, 0, 0);
433+ // add some timeout to get the event buffer cleaned
434+ wait(500);
435+ }
436+
437+ function test_clear_selection_on_click_data() {
438+ return [
439+ {tag: "TextField click on selection", input: textField, selectChars: 10, clickPos: Qt.point(10, textField.height / 2)},
440+ {tag: "TextArea click on selection", input: textArea, selectChars: 40, clickPos: Qt.point(20, 20)},
441+ {tag: "TextField click beside selection", input: textField, selectChars: 5, clickPos: Qt.point(textField.width / 2, textField.height / 2)},
442+ {tag: "TextArea click beside selection", input: textArea, selectChars: 1, clickPos: Qt.point(textArea.width / 2, textArea.height / 2)},
443+ ];
444+ }
445+ function test_clear_selection_on_click(data) {
446+ data.input.focus = true;
447+ data.input.select(0, data.selectChars);
448+ verify(data.input.selectedText !== "", "No text selected!");
449+
450+ mouseClick(data.input, data.clickPos.x, data.clickPos.y);
451+ verify(data.input.selectedText === "", "There is still text selected!");
452+ }
453+
454+ function test_select_text_by_mouse_drag_data() {
455+ return [
456+ {tag: "TextField", input: textField},
457+ {tag: "TextArea", input: textArea},
458+ ];
459+ }
460+ function test_select_text_by_mouse_drag(data) {
461+ data.input.focus = true;
462+
463+ flick(data.input, 0, 0, data.input.width / 2, data.input.height / 2);
464+ waitForRendering(data.input);
465+ verify(data.input.selectedText !== "", "There's no text selected!");
466+ }
467+
468+ function test_no_caret_when_no_touchscreen_data() {
469+ return [
470+ {tag: "TextField", input: textField},
471+ {tag: "TextArea", input: textArea},
472+ ];
473+ }
474+ function test_no_caret_when_no_touchscreen(data) {
475+ if (TestExtras.touchDevicePresent()) {
476+ skip("This test cannot be executed in touch environment");
477+ }
478+
479+ data.input.focus = true;
480+ waitForRendering(data.input);
481+
482+ var cursor = findChild(data.input, "textCursor");
483+ verify(cursor, "Cursor not accessible, FAIL");
484+ verify(cursor.caret, "No caret is set");
485+ compare(cursor.caret.visible, false, "Caret must not be visible!");
486+ }
487+
488+ function test_select_text_with_double_click_data() {
489+ return [
490+ {tag: "TextField", input: textField},
491+ {tag: "TextArea", input: textArea},
492+ ];
493+ }
494+ function test_select_text_with_double_click(data) {
495+ data.input.focus = true;
496+ waitForRendering(data.input, 500);
497+
498+ mouseDoubleClick(data.input, units.gu(1), units.gu(1));
499+ waitForRendering(data.input, 500);
500+ expectFail(data.tag, "mouseDoubleClick() fails to trigger")
501+ verify(data.input.selectedText != "", "No text selected.");
502+ }
503+ }
504+}

Subscribers

People subscribed via source and target branches