Merge lp:~zsombi/ubuntu-ui-toolkit/focus-handling-on-touch into lp:ubuntu-ui-toolkit/staging
- focus-handling-on-touch
- Merge into staging
Status: | Merged |
---|---|
Approved by: | Zsombor Egri |
Approved revision: | 1225 |
Merged at revision: | 1211 |
Proposed branch: | lp:~zsombi/ubuntu-ui-toolkit/focus-handling-on-touch |
Merge into: | lp:ubuntu-ui-toolkit/staging |
Prerequisite: | lp:~zsombi/ubuntu-ui-toolkit/ucfocusscope |
Diff against target: |
1373 lines (+712/-316) 23 files modified
modules/Ubuntu/Components/AbstractButton.qml (+4/-0) modules/Ubuntu/Components/OptionSelector.qml (+5/-0) modules/Ubuntu/Components/PageTreeNode.qml (+3/-0) modules/Ubuntu/Components/Pickers/DatePicker.qml (+1/-0) modules/Ubuntu/Components/Pickers/Dialer.qml (+1/-0) modules/Ubuntu/Components/Pickers/DialerHand.qml (+2/-0) modules/Ubuntu/Components/Pickers/Picker.qml (+8/-0) modules/Ubuntu/Components/Pickers/PickerPanel.qml (+5/-0) modules/Ubuntu/Components/Popups/Dialog.qml (+1/-0) modules/Ubuntu/Components/Popups/Popover.qml (+1/-0) modules/Ubuntu/Components/Popups/PopupBase.qml (+31/-1) modules/Ubuntu/Components/Popups/SheetBase.qml (+1/-0) modules/Ubuntu/Components/Slider.qml (+4/-0) modules/Ubuntu/Components/TabBar.qml (+1/-0) modules/Ubuntu/Components/TextCursor.qml (+5/-2) modules/Ubuntu/Components/TextField.qml (+3/-0) modules/Ubuntu/Components/UbuntuListView.qml (+18/-0) modules/Ubuntu/Components/plugin/ucstyleditembase.cpp (+1/-2) tests/unit_x11/tst_components/tst_datepicker.qml (+6/-2) tests/unit_x11/tst_components/tst_focus.qml (+240/-0) tests/unit_x11/tst_components/tst_tabs.qml (+96/-309) tests/unit_x11/tst_components/tst_tabs_empty.qml (+41/-0) tests/unit_x11/tst_components/tst_tabs_with_repeater.qml (+234/-0) |
To merge this branch: | bzr merge lp:~zsombi/ubuntu-ui-toolkit/focus-handling-on-touch |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
PS Jenkins bot | continuous-integration | Approve | |
Cris Dywan | Approve | ||
Review via email: mp+231356@code.launchpad.net |
Commit message
Focus handling on components.
Description of the change
PS Jenkins bot (ps-jenkins) wrote : | # |
Cris Dywan (kalikiana) wrote : | # |
393 + // note: find Label, Picker's label uses medium font size. The lookup must be changed
Rogue fix sneaked in. Please make a separate MR for this.
Zsombor Egri (zsombi) wrote : | # |
> 393 + // note: find Label, Picker's label uses medium font size. The
> lookup must be changed
>
> Rogue fix sneaked in. Please make a separate MR for this.
As discussed, the order of children differs when focused. That's why it has been "sniffed" in here.
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1215
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
FAILURE: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1216
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1217
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Tim Peeters (tpeeters) wrote : | # |
tim@trusty:
unity::
Could not determine application identifier. HUD will not work properly.
Provide your application identifier in $APP_ID environment variable.
file://
Test '"/home/
file://
********* Start testing of qmltestrunner *********
Config: Using QtTest library 5.3.0, Qt 5.3.0
PASS : qmltestrunner:
PASS : qmltestrunner:
PASS : qmltestrunner:
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of qmltestrunner *********
tim@trusty:
Tim Peeters (tpeeters) wrote : | # |
^is just a typo in the code:
height: uniys.gu(100)
Tim Peeters (tpeeters) wrote : | # |
this program http://
file://
it did not do that before. Please add a guard to check that window is valid.
Tim Peeters (tpeeters) wrote : | # |
function saveActiveFocus() {
if (typeof window != 'undefined') prevFocus = window.
}
fixes the issue in the previous comment
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1219
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Continuous integration, rev:1221
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild:
http://
Cris Dywan (kalikiana) wrote : | # |
214 + if (typeof window != "undefined") {
This is still racy, it won't set if the window is available later. And it's the third instance where this came up and wasn't handled correctly on the first attempt.
We need something like Context.
Zsombor Egri (zsombi) wrote : | # |
> 214 + if (typeof window != "undefined") {
>
> This is still racy, it won't set if the window is available later. And it's
> the third instance where this came up and wasn't handled correctly on the
> first attempt.
>
> We need something like Context.
I'll do a local fix with a property in between, and we can return to it in a separate MR with a proper fix.
Cris Dywan (kalikiana) wrote : | # |
> > 214 + if (typeof window != "undefined") {
> >
> > This is still racy, it won't set if the window is available later. And it's
> > the third instance where this came up and wasn't handled correctly on the
> > first attempt.
> >
> > We need something like Context.
>
> I'll do a local fix with a property in between, and we can return to it in a
> separate MR with a proper fix.
Ack.
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1223
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
PS Jenkins bot (ps-jenkins) wrote : | # |
FAILED: Autolanding.
Unapproved changes made after approval.
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
PS Jenkins bot (ps-jenkins) wrote : | # |
PASSED: Continuous integration, rev:1225
http://
Executed test runs:
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
Click here to trigger a rebuild:
http://
Preview Diff
1 | === modified file 'modules/Ubuntu/Components/AbstractButton.qml' |
2 | --- modules/Ubuntu/Components/AbstractButton.qml 2014-06-23 09:53:09 +0000 |
3 | +++ modules/Ubuntu/Components/AbstractButton.qml 2014-08-22 17:18:58 +0000 |
4 | @@ -16,6 +16,7 @@ |
5 | |
6 | import QtQuick 2.0 |
7 | import QtFeedback 5.0 |
8 | +import Ubuntu.Components 1.1 |
9 | |
10 | /*! |
11 | \qmlabstract AbstractButton |
12 | @@ -82,6 +83,8 @@ |
13 | */ |
14 | property alias __mouseArea: mouseArea |
15 | |
16 | + activeFocusOnPress: true |
17 | + |
18 | HapticsEffect { |
19 | id: pressEffect |
20 | attackIntensity: 0.0 |
21 | @@ -100,6 +103,7 @@ |
22 | hoverEnabled: true |
23 | |
24 | onClicked: { |
25 | + button.requestFocus(); |
26 | if (button.__acceptEvents) { |
27 | pressEffect.start() |
28 | button.clicked() |
29 | |
30 | === modified file 'modules/Ubuntu/Components/OptionSelector.qml' |
31 | --- modules/Ubuntu/Components/OptionSelector.qml 2014-04-23 08:50:20 +0000 |
32 | +++ modules/Ubuntu/Components/OptionSelector.qml 2014-08-22 17:18:58 +0000 |
33 | @@ -220,6 +220,7 @@ |
34 | StyledItem { |
35 | id: listContainer |
36 | objectName: "listContainer" |
37 | + activeFocusOnPress: true |
38 | |
39 | readonly property url chevron: __styleInstance.chevron |
40 | readonly property url tick: __styleInstance.tick |
41 | @@ -279,6 +280,10 @@ |
42 | property real itemHeight |
43 | signal delegateClicked(int index) |
44 | |
45 | + onMovementStarted: optionSelector.requestFocus(Qt.MouseFocusReason) |
46 | + onFlickStarted: optionSelector.requestFocus(Qt.MouseFocusReason) |
47 | + Toolkit.Mouse.onClicked: optionSelector.requestFocus(Qt.MouseFocusReason) |
48 | + |
49 | onDelegateClicked: optionSelector.delegateClicked(index); |
50 | interactive: listContainer.height !== list.contentHeight && listContainer.currentlyExpanded ? true : false |
51 | clip: true |
52 | |
53 | === modified file 'modules/Ubuntu/Components/PageTreeNode.qml' |
54 | --- modules/Ubuntu/Components/PageTreeNode.qml 2014-06-19 12:45:37 +0000 |
55 | +++ modules/Ubuntu/Components/PageTreeNode.qml 2014-08-22 17:18:58 +0000 |
56 | @@ -100,6 +100,9 @@ |
57 | */ |
58 | property bool isLeaf: false |
59 | |
60 | + // turn on focusing |
61 | + activeFocusOnPress: true |
62 | + |
63 | Binding { |
64 | target: node.parentNode |
65 | property: "activeLeafNode" |
66 | |
67 | === modified file 'modules/Ubuntu/Components/Pickers/DatePicker.qml' |
68 | --- modules/Ubuntu/Components/Pickers/DatePicker.qml 2014-07-18 20:12:42 +0000 |
69 | +++ modules/Ubuntu/Components/Pickers/DatePicker.qml 2014-08-22 17:18:58 +0000 |
70 | @@ -302,6 +302,7 @@ |
71 | |
72 | implicitWidth: units.gu(36) |
73 | implicitHeight: units.gu(20) |
74 | + activeFocusOnPress: true |
75 | |
76 | /*! \internal */ |
77 | onMinimumChanged: { |
78 | |
79 | === modified file 'modules/Ubuntu/Components/Pickers/Dialer.qml' |
80 | --- modules/Ubuntu/Components/Pickers/Dialer.qml 2014-04-25 12:53:58 +0000 |
81 | +++ modules/Ubuntu/Components/Pickers/Dialer.qml 2014-08-22 17:18:58 +0000 |
82 | @@ -142,6 +142,7 @@ |
83 | id: dialer |
84 | implicitWidth: size |
85 | implicitHeight: size |
86 | + activeFocusOnPress: true |
87 | |
88 | style: Theme.createStyleComponent("DialerStyle.qml", dialer) |
89 | |
90 | |
91 | === modified file 'modules/Ubuntu/Components/Pickers/DialerHand.qml' |
92 | --- modules/Ubuntu/Components/Pickers/DialerHand.qml 2014-04-25 12:53:58 +0000 |
93 | +++ modules/Ubuntu/Components/Pickers/DialerHand.qml 2014-08-22 17:18:58 +0000 |
94 | @@ -141,6 +141,7 @@ |
95 | anchors.centerIn: parent |
96 | width: parent.width |
97 | height: parent.height |
98 | + activeFocusOnPress: true |
99 | style: Theme.createStyleComponent("DialerHandStyle.qml", dialerHand) |
100 | |
101 | /*! \internal */ |
102 | @@ -193,6 +194,7 @@ |
103 | onPositionChanged: { |
104 | if (internalChange) return; |
105 | internalChange = true; |
106 | + dialerHand.requestFocus(Qt.MouseFocusReason); |
107 | var point = mapToItem (dialerHand, mouse.x, mouse.y); |
108 | var diffX = (point.x - centerX); |
109 | var diffY = -1 * (point.y - centerY); |
110 | |
111 | === modified file 'modules/Ubuntu/Components/Pickers/Picker.qml' |
112 | --- modules/Ubuntu/Components/Pickers/Picker.qml 2014-04-23 08:50:20 +0000 |
113 | +++ modules/Ubuntu/Components/Pickers/Picker.qml 2014-08-22 17:18:58 +0000 |
114 | @@ -157,6 +157,7 @@ |
115 | |
116 | implicitWidth: units.gu(8) |
117 | implicitHeight: units.gu(20) |
118 | + activeFocusOnPress: true |
119 | |
120 | style: Theme.createStyleComponent("PickerStyle.qml", picker) |
121 | |
122 | @@ -251,6 +252,9 @@ |
123 | picker.selectedIndex = 0; |
124 | } |
125 | } |
126 | + // focus handling |
127 | + onMoveStarted: picker.requestFocus(Qt.MouseFocusReason) |
128 | + onFlickStarted: picker.requestFocus(Qt.MouseFocusReason) |
129 | } |
130 | |
131 | function moveToIndex(toIndex) { |
132 | @@ -289,6 +293,8 @@ |
133 | } |
134 | width: parent ? parent.width : 0 |
135 | clip: true |
136 | + focus: true |
137 | + Mouse.onClicked: picker.requestFocus() |
138 | |
139 | model: picker.model |
140 | delegate: picker.delegate |
141 | @@ -348,6 +354,8 @@ |
142 | } |
143 | width: parent ? parent.width : 0 |
144 | clip: true |
145 | + focus: true |
146 | + Mouse.onClicked: picker.requestFocus(Qt.MouseFocusReason) |
147 | |
148 | model: picker.model |
149 | delegate: picker.delegate |
150 | |
151 | === modified file 'modules/Ubuntu/Components/Pickers/PickerPanel.qml' |
152 | --- modules/Ubuntu/Components/Pickers/PickerPanel.qml 2014-04-23 08:50:20 +0000 |
153 | +++ modules/Ubuntu/Components/Pickers/PickerPanel.qml 2014-08-22 17:18:58 +0000 |
154 | @@ -118,6 +118,11 @@ |
155 | "callerProperty": property |
156 | } |
157 | |
158 | + // hide OSK if eventually open |
159 | + if (Qt.inputMethod.visible) { |
160 | + Qt.inputMethod.hide(); |
161 | + } |
162 | + |
163 | if (!internal.isPhone) { |
164 | // we have no input panel defined, or the therefore we show the picker in a Popover |
165 | return internal.openPopover(caller, params); |
166 | |
167 | === modified file 'modules/Ubuntu/Components/Popups/Dialog.qml' |
168 | --- modules/Ubuntu/Components/Popups/Dialog.qml 2014-07-25 12:55:27 +0000 |
169 | +++ modules/Ubuntu/Components/Popups/Dialog.qml 2014-08-22 17:18:58 +0000 |
170 | @@ -157,6 +157,7 @@ |
171 | |
172 | StyledItem { |
173 | id: foreground |
174 | + activeFocusOnPress: true |
175 | width: Math.min(minimumWidth, dialog.width) |
176 | anchors.centerIn: parent |
177 | |
178 | |
179 | === modified file 'modules/Ubuntu/Components/Popups/Popover.qml' |
180 | --- modules/Ubuntu/Components/Popups/Popover.qml 2014-07-16 06:31:50 +0000 |
181 | +++ modules/Ubuntu/Components/Popups/Popover.qml 2014-08-22 17:18:58 +0000 |
182 | @@ -204,6 +204,7 @@ |
183 | |
184 | StyledItem { |
185 | id: foreground |
186 | + activeFocusOnPress: true |
187 | objectName: "popover_foreground" |
188 | |
189 | //styling properties |
190 | |
191 | === modified file 'modules/Ubuntu/Components/Popups/PopupBase.qml' |
192 | --- modules/Ubuntu/Components/Popups/PopupBase.qml 2014-07-10 11:50:13 +0000 |
193 | +++ modules/Ubuntu/Components/Popups/PopupBase.qml 2014-08-22 17:18:58 +0000 |
194 | @@ -163,12 +163,41 @@ |
195 | onVisibleChanged: stateWrapper.state = (visible) ? 'opened' : 'closed' |
196 | /*! \internal */ |
197 | onParentChanged: stateWrapper.rootItem = QuickUtils.rootItem(popupBase) |
198 | - Component.onCompleted: stateWrapper.rootItem = QuickUtils.rootItem(popupBase); |
199 | + Component.onCompleted: { |
200 | + stateWrapper.saveActiveFocus(); |
201 | + stateWrapper.rootItem = QuickUtils.rootItem(popupBase); |
202 | + } |
203 | |
204 | Item { |
205 | id: stateWrapper |
206 | property Item rootItem: QuickUtils.rootItem(popupBase) |
207 | |
208 | + property bool windowIsValid: typeof window != "undefined" |
209 | + property Item prevFocus |
210 | + |
211 | + function saveActiveFocus() { |
212 | + // 'window' context property is exposed to QML after component completion |
213 | + // before rendering is complete, therefore a simple 'if (window)' check is |
214 | + // not enough. |
215 | + if (windowIsValid) { |
216 | + prevFocus = window.activeFocusItem; |
217 | + windowIsValidChanged.disconnect(saveActiveFocus); |
218 | + } else { |
219 | + // connect the function so we can save the original focus item |
220 | + windowIsValidChanged.connect(saveActiveFocus); |
221 | + } |
222 | + } |
223 | + |
224 | + function restoreActiveFocus() { |
225 | + if (prevFocus) { |
226 | + if (prevFocus.hasOwnProperty("requestFocus")) { |
227 | + prevFocus.requestFocus(Qt.PopupFocusReason); |
228 | + } else { |
229 | + prevFocus.forceActiveFocus(Qt.PopupFocusReason); |
230 | + } |
231 | + } |
232 | + } |
233 | + |
234 | states: [ |
235 | State { |
236 | name: 'closed' |
237 | @@ -211,6 +240,7 @@ |
238 | ScriptAction { |
239 | script: { |
240 | popupBase.visible = false; |
241 | + stateWrapper.restoreActiveFocus(); |
242 | } |
243 | } |
244 | } |
245 | |
246 | === modified file 'modules/Ubuntu/Components/Popups/SheetBase.qml' |
247 | --- modules/Ubuntu/Components/Popups/SheetBase.qml 2014-06-03 13:37:22 +0000 |
248 | +++ modules/Ubuntu/Components/Popups/SheetBase.qml 2014-08-22 17:18:58 +0000 |
249 | @@ -86,6 +86,7 @@ |
250 | |
251 | StyledItem { |
252 | id: foreground |
253 | + activeFocusOnPress: true |
254 | |
255 | property string title |
256 | property real contentsWidth: units.gu(64) |
257 | |
258 | === modified file 'modules/Ubuntu/Components/Slider.qml' |
259 | --- modules/Ubuntu/Components/Slider.qml 2014-08-19 05:21:56 +0000 |
260 | +++ modules/Ubuntu/Components/Slider.qml 2014-08-22 17:18:58 +0000 |
261 | @@ -119,6 +119,8 @@ |
262 | /*! \internal */ |
263 | onValueChanged: mouseArea.liveValue = slider.value |
264 | |
265 | + activeFocusOnPress: true |
266 | + |
267 | Binding { |
268 | target: slider |
269 | property: "value" |
270 | @@ -208,6 +210,8 @@ |
271 | var normalizedOffsetX = (mouseArea.mouseX - dragInitMouseX) / barMinusThumb; |
272 | liveValue = valueFromNormalizedValue(dragInitNormalizedValue + normalizedOffsetX); |
273 | } |
274 | + onClicked: slider.requestFocus(Qt.MouseFocusReason) |
275 | + onLiveValueChanged: if (isPressed) slider.requestFocus(Qt.MouseFocusReason) |
276 | } |
277 | |
278 | style: Theme.createStyleComponent("SliderStyle.qml", slider) |
279 | |
280 | === modified file 'modules/Ubuntu/Components/TabBar.qml' |
281 | --- modules/Ubuntu/Components/TabBar.qml 2014-05-09 11:22:07 +0000 |
282 | +++ modules/Ubuntu/Components/TabBar.qml 2014-08-22 17:18:58 +0000 |
283 | @@ -95,6 +95,7 @@ |
284 | property bool animate: true |
285 | |
286 | implicitHeight: units.gu(7.5) |
287 | + activeFocusOnPress: true |
288 | |
289 | style: Theme.createStyleComponent("TabBarStyle.qml", tabBar) |
290 | |
291 | |
292 | === modified file 'modules/Ubuntu/Components/TextCursor.qml' |
293 | --- modules/Ubuntu/Components/TextCursor.qml 2014-06-13 08:57:48 +0000 |
294 | +++ modules/Ubuntu/Components/TextCursor.qml 2014-08-22 17:18:58 +0000 |
295 | @@ -72,18 +72,21 @@ |
296 | } |
297 | // open context menu only for cursorPosition or selectionEnd |
298 | if (positionProperty !== "selectionStart") { |
299 | + var popup; |
300 | if (handler.main.popover === undefined) { |
301 | // open the default one |
302 | - PopupUtils.open(Qt.resolvedUrl("TextInputPopover.qml"), cursorItem, |
303 | + popup = PopupUtils.open(Qt.resolvedUrl("TextInputPopover.qml"), cursorItem, |
304 | { |
305 | "target": handler.main |
306 | }) |
307 | } else { |
308 | - PopupUtils.open(handler.main.popover, cursorItem, |
309 | + popup = PopupUtils.open(handler.main.popover, cursorItem, |
310 | { |
311 | "target": handler.main |
312 | }) |
313 | } |
314 | + // do not grab focus! |
315 | + popup.__foreground.activeFocusOnPress = false; |
316 | } |
317 | } |
318 | |
319 | |
320 | === modified file 'modules/Ubuntu/Components/TextField.qml' |
321 | --- modules/Ubuntu/Components/TextField.qml 2014-08-08 07:28:49 +0000 |
322 | +++ modules/Ubuntu/Components/TextField.qml 2014-08-22 17:18:58 +0000 |
323 | @@ -818,6 +818,7 @@ |
324 | // internals |
325 | |
326 | opacity: enabled ? 1.0 : 0.3 |
327 | + activeFocusOnPress: true |
328 | |
329 | /*! \internal */ |
330 | onVisibleChanged: { |
331 | @@ -904,6 +905,8 @@ |
332 | AbstractButton { |
333 | id: clearButton |
334 | objectName: "clear_button" |
335 | + activeFocusOnPress: false |
336 | + |
337 | anchors { |
338 | top: parent.top |
339 | right: rightPane.left |
340 | |
341 | === modified file 'modules/Ubuntu/Components/UbuntuListView.qml' |
342 | --- modules/Ubuntu/Components/UbuntuListView.qml 2014-04-23 08:50:20 +0000 |
343 | +++ modules/Ubuntu/Components/UbuntuListView.qml 2014-08-22 17:18:58 +0000 |
344 | @@ -92,8 +92,26 @@ |
345 | } |
346 | animation.start(); |
347 | } |
348 | + |
349 | + function requestFocus(reason) { |
350 | + // lookup for the currentItem, and if it is a FocusScope, focus the view |
351 | + // this will also focus the currentItem |
352 | + if (root.currentItem && root.currentItem.hasOwnProperty("activeFocusOnPress")) { |
353 | + root.forceActiveFocus(reason); |
354 | + } |
355 | + } |
356 | } |
357 | |
358 | + focus: true |
359 | + |
360 | + /*! |
361 | + \internal |
362 | + Grab focus when moved, flicked or clicked |
363 | + */ |
364 | + onMovementStarted: priv.requestFocus(Qt.MouseFocusReason) |
365 | + onFlickStarted: priv.requestFocus(Qt.MouseFocusReason) |
366 | + Toolkit.Mouse.onClicked: priv.requestFocus(Qt.MouseFocusReason) |
367 | + |
368 | /*! |
369 | \preliminary |
370 | Expand the item at the given index. |
371 | |
372 | === modified file 'modules/Ubuntu/Components/plugin/ucstyleditembase.cpp' |
373 | --- modules/Ubuntu/Components/plugin/ucstyleditembase.cpp 2014-08-21 11:58:33 +0000 |
374 | +++ modules/Ubuntu/Components/plugin/ucstyleditembase.cpp 2014-08-22 17:18:58 +0000 |
375 | @@ -224,8 +224,7 @@ |
376 | { |
377 | QQuickItem::mousePressEvent(event); |
378 | Q_D(UCStyledItemBase); |
379 | - // accept the event if the focus was possible. |
380 | - event->setAccepted(requestFocus(Qt::MouseFocusReason)); |
381 | + requestFocus(Qt::MouseFocusReason); |
382 | } |
383 | |
384 | #include "moc_ucstyleditembase.cpp" |
385 | |
386 | === modified file 'tests/unit_x11/tst_components/tst_datepicker.qml' |
387 | --- tests/unit_x11/tst_components/tst_datepicker.qml 2014-05-09 18:06:06 +0000 |
388 | +++ tests/unit_x11/tst_components/tst_datepicker.qml 2014-08-22 17:18:58 +0000 |
389 | @@ -59,7 +59,11 @@ |
390 | function getPickerLabel(picker, name) { |
391 | var pickerItem = findChild(picker, name); |
392 | var pickerCurrent = findChild(pickerItem, "Picker_ViewLoader"); |
393 | - return pickerCurrent.item.currentItem.children[2]; |
394 | + // note: find Label, Picker's label uses medium font size. The lookup must be changed |
395 | + // if the fontSize is changed! |
396 | + var pickerLabel = findChildWithProperty(pickerCurrent.item.currentItem, "fontSize", "medium"); |
397 | + verify(pickerLabel, ("Label of %1 not accessible").arg(name)); |
398 | + return pickerLabel; |
399 | } |
400 | function getPickerModel(picker, name) { |
401 | var pickerItem = findInvisibleChild(picker, name); |
402 | @@ -318,7 +322,7 @@ |
403 | picker.mode = "Years|Months|Days"; |
404 | waitPickerMoving(); |
405 | |
406 | - var yearLabel = getPickerLabel(picker, "PickerRow_YearPicker"); |
407 | + var yearLabel = getPickerLabel(picker, "PickerRow_YearPicker", date.getFullYear()); |
408 | var monthLabel = getPickerLabel(picker, "PickerRow_MonthPicker"); |
409 | var monthModel = getPickerModel(picker, "PickerRow_MonthPicker"); |
410 | var dayLabel = getPickerLabel(picker, "PickerRow_DayPicker"); |
411 | |
412 | === added file 'tests/unit_x11/tst_components/tst_focus.qml' |
413 | --- tests/unit_x11/tst_components/tst_focus.qml 1970-01-01 00:00:00 +0000 |
414 | +++ tests/unit_x11/tst_components/tst_focus.qml 2014-08-22 17:18:58 +0000 |
415 | @@ -0,0 +1,240 @@ |
416 | +/* |
417 | + * Copyright 2014 Canonical Ltd. |
418 | + * |
419 | + * This program is free software; you can redistribute it and/or modify |
420 | + * it under the terms of the GNU Lesser General Public License as published by |
421 | + * the Free Software Foundation; version 3. |
422 | + * |
423 | + * This program is distributed in the hope that it will be useful, |
424 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
425 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
426 | + * GNU Lesser General Public License for more details. |
427 | + * |
428 | + * You should have received a copy of the GNU Lesser General Public License |
429 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
430 | + */ |
431 | + |
432 | +import QtQuick 2.2 |
433 | +import QtTest 1.0 |
434 | +import Ubuntu.Test 1.0 |
435 | +import Ubuntu.Components 1.1 |
436 | +import Ubuntu.Components.Pickers 1.0 |
437 | +import Ubuntu.Components.ListItems 1.0 as ListItem |
438 | +import Ubuntu.Components.Popups 1.0 |
439 | + |
440 | +Item { |
441 | + id: main |
442 | + width: units.gu(50); height: units.gu(100) |
443 | + |
444 | + property bool hasOSK: QuickUtils.inputMethodProvider !== "" |
445 | + |
446 | + Flow { |
447 | + anchors { |
448 | + fill: parent |
449 | + margins: units.gu(0.5) |
450 | + } |
451 | + spacing: units.gu(1) |
452 | + |
453 | + UbuntuListView { |
454 | + id: listView |
455 | + width: units.gu(50) |
456 | + height: units.gu(20) |
457 | + clip: true |
458 | + model: 10 |
459 | + delegate: ListItem.Standard { |
460 | + text: "Whatever" |
461 | + } |
462 | + } |
463 | + TextField { |
464 | + id: textField |
465 | + text: "This is a text field with some text handling focus" |
466 | + } |
467 | + TextArea { |
468 | + id: textArea |
469 | + text: "This is a text area with some text handling focus" |
470 | + } |
471 | + Button { |
472 | + id: button |
473 | + text: "Press me" |
474 | + } |
475 | + CheckBox { |
476 | + id: checkbox |
477 | + text: "Check me" |
478 | + } |
479 | + Switch { |
480 | + id: switchBox |
481 | + } |
482 | + Picker { |
483 | + id: picker |
484 | + model: 10 |
485 | + } |
486 | + Picker { |
487 | + id: roundPicker |
488 | + model: 10 |
489 | + circular: true |
490 | + } |
491 | + Button { |
492 | + id: pickerPanel |
493 | + property date date: new Date() |
494 | + text: Qt.formatDateTime(date, "yyyy/MMMM") |
495 | + onClicked: PickerPanel.openDatePicker(pickerPanel, "date", "Years|Months") |
496 | + } |
497 | + Slider { |
498 | + id: slider |
499 | + } |
500 | + ComboButton { |
501 | + id: comboButton |
502 | + Rectangle { |
503 | + height: comboButton.comboListHeight |
504 | + color: "blue" |
505 | + } |
506 | + } |
507 | + Button { |
508 | + id: popoverTest |
509 | + text: "Popovers" |
510 | + property Item popover |
511 | + property Component popoverComponent |
512 | + onClicked: { |
513 | + popover = PopupUtils.open(popoverComponent) |
514 | + } |
515 | + } |
516 | + } |
517 | + |
518 | + Component { |
519 | + id: dialogComponent |
520 | + Dialog { |
521 | + id: dialog |
522 | + title: "TestDialog" |
523 | + Button { |
524 | + text: "close" |
525 | + onClicked: PopupUtils.close(dialog) |
526 | + } |
527 | + } |
528 | + } |
529 | + |
530 | + Component { |
531 | + id: popoverComponent |
532 | + Popover { |
533 | + id: popover |
534 | + contentWidth: units.gu(20) |
535 | + contentHeight: item.height |
536 | + ListItem.Standard { |
537 | + id: item |
538 | + text: "close" |
539 | + onClicked: PopupUtils.close(popover) |
540 | + } |
541 | + } |
542 | + } |
543 | + |
544 | + UbuntuTestCase { |
545 | + name: "FocusingTests" |
546 | + when: windowShown |
547 | + |
548 | + SignalSpy { |
549 | + id: popupCloseSpy |
550 | + signalName: "onDestruction" |
551 | + } |
552 | + |
553 | + function initTestCase() { |
554 | + textField.forceActiveFocus(); |
555 | + } |
556 | + function cleanup() { |
557 | + // empty event buffer |
558 | + wait(200); |
559 | + popupCloseSpy.clear(); |
560 | + popupCloseSpy.target = null; |
561 | + } |
562 | + |
563 | + // make this as the very first test executed |
564 | + function test_0_transfer_focus_data() { |
565 | + return [ |
566 | + {tag: "TextArea", previousFocused: textField, focusOn: textArea, clickToDismiss: false}, |
567 | + {tag: "Button", previousFocused: textArea, focusOn: button, clickToDismiss: false}, |
568 | + {tag: "Checkbox", previousFocused: button, focusOn: checkbox, clickToDismiss: false}, |
569 | + {tag: "Switch", previousFocused: checkbox, focusOn: switchBox, clickToDismiss: false}, |
570 | + {tag: "Picker - linear", previousFocused: switchBox, focusOn: picker, clickToDismiss: false}, |
571 | + {tag: "Picker - circular", previousFocused: picker, focusOn: roundPicker, clickToDismiss: false}, |
572 | + {tag: "PickerPanel", previousFocused: roundPicker, focusOn: pickerPanel, clickToDismiss: true}, |
573 | + {tag: "UbuntuListView", previousFocused: pickerPanel, focusOn: listView, clickToDismiss: false}, |
574 | + {tag: "Slider", previousFocused: listView, focusOn: slider, clickToDismiss: false}, |
575 | + {tag: "ComboButton", previousFocused: slider, focusOn: comboButton, clickToDismiss: false}, |
576 | + ]; |
577 | + } |
578 | + function test_0_transfer_focus(data) { |
579 | + // perform mouse press on |
580 | + mouseClick(data.focusOn, centerOf(data.focusOn).x, centerOf(data.focusOn).y); |
581 | + compare(data.previousFocused.focus, false, "Previous focus is still set!"); |
582 | + compare(data.focusOn.focus, true, data.tag + " is not focused!"); |
583 | + if (data.clickToDismiss) { |
584 | + mouseClick(main, 0, 0); |
585 | + } |
586 | + waitForRendering(data.focusOn, 200); |
587 | + } |
588 | + |
589 | + function test_hide_osk_when_pickerpanel_opens() { |
590 | + if (!main.hasOSK) { |
591 | + skip("This functionality can be tested with OSK only."); |
592 | + } |
593 | + |
594 | + textField.forceActiveFocus(); |
595 | + verify(textField.focus, "TextField is not focused"); |
596 | + verify(Qt.inputMethod.visible, "OSK is hidden"); |
597 | + mouseClick(pickerPanel, centerOf(pickerPanel).x, centerOf(pickerPanel).y); |
598 | + verify(!Qt.inputMethod.visible, "OSK is visible still!"); |
599 | + // remove panel/popover |
600 | + mouseClick(main, 0, 0); |
601 | + } |
602 | + |
603 | + function test_textfield_clear_button_keeps_focus() { |
604 | + textField.forceActiveFocus(); |
605 | + var text = textField.text; |
606 | + |
607 | + var clearButton = findChild(textField, "clear_button"); |
608 | + verify(clearButton, "clearButton is not accessible!"); |
609 | + var center = centerOf(clearButton); |
610 | + mouseClick(clearButton, center.x, center.y); |
611 | + compare(textField.text, "", "Text had not been cleared?"); |
612 | + compare(textField.focus, true, "Focus had been stolen from text input"); |
613 | + |
614 | + //restore |
615 | + textField.text = text; |
616 | + } |
617 | + |
618 | + function test_combo_button_dropdown_focuses_component() { |
619 | + textField.forceActiveFocus(); |
620 | + var dropdownButton = findChild(comboButton, "combobutton_dropdown"); |
621 | + verify(dropdownButton, "dropdown button is not accessible?"); |
622 | + |
623 | + var center = centerOf(dropdownButton); |
624 | + mouseClick(dropdownButton, center.x, center.y); |
625 | + waitForRendering(comboButton); |
626 | + compare(dropdownButton.focus, true, "Dropdown button hasn't got focused!"); |
627 | + compare(comboButton.focus, true, "ComboButton hasn't been focused!"); |
628 | + comboButton.expanded = false; |
629 | + } |
630 | + |
631 | + function test_popover_refocus_data() { |
632 | + return [ |
633 | + {tag: "Dialog", component: dialogComponent}, |
634 | + {tag: "Popover", component: popoverComponent}, |
635 | + ]; |
636 | + } |
637 | + |
638 | + function test_popover_refocus(data) { |
639 | + popoverTest.popoverComponent = data.component; |
640 | + var center = centerOf(popoverTest); |
641 | + mouseClick(popoverTest, center.x, center.y); |
642 | + verify(popoverTest.focus, "Button focus not gained."); |
643 | + waitForRendering(popoverTest.popover); |
644 | + popupCloseSpy.target = popoverTest.popover.Component; |
645 | + |
646 | + var closeButton = findChildWithProperty(popoverTest.popover, "text", "close"); |
647 | + verify(closeButton, "Close button not accessible"); |
648 | + center = centerOf(closeButton); |
649 | + mouseClick(closeButton, center.x, center.y); |
650 | + verify(!popoverTest.focus, "Button focus not lost."); |
651 | + popupCloseSpy.wait(); |
652 | + verify(popoverTest.focus, "Button focus not restored."); |
653 | + } |
654 | + } |
655 | +} |
656 | |
657 | === modified file 'tests/unit_x11/tst_components/tst_tabs.qml' |
658 | --- tests/unit_x11/tst_components/tst_tabs.qml 2014-04-25 05:28:37 +0000 |
659 | +++ tests/unit_x11/tst_components/tst_tabs.qml 2014-08-22 17:18:58 +0000 |
660 | @@ -16,323 +16,108 @@ |
661 | |
662 | import QtQuick 2.0 |
663 | import QtTest 1.0 |
664 | +import Ubuntu.Test 1.0 |
665 | import Ubuntu.Components 1.1 |
666 | |
667 | -Item { |
668 | - id: testCase |
669 | +MainView { |
670 | + id: mainView |
671 | width: units.gu(50) |
672 | height: units.gu(80) |
673 | |
674 | Tabs { |
675 | - id: emptyTabs |
676 | - } |
677 | - |
678 | - MainView { |
679 | - id: mainView |
680 | - anchors.fill: parent |
681 | - Tabs { |
682 | - id: tabs |
683 | - Tab { |
684 | - id: tab1 |
685 | - title: "tab 1" |
686 | - page: Page { |
687 | - id: page1 |
688 | - Button { |
689 | - id: button |
690 | - anchors.centerIn: parent |
691 | - text: "click" |
692 | - } |
693 | - } |
694 | - } |
695 | - Tab { |
696 | - id: tab2 |
697 | - title: "tab 2" |
698 | - page: Page { |
699 | - id: page2 |
700 | - } |
701 | - } |
702 | - Tab { |
703 | - id: tab3 |
704 | - title: "tab 3" |
705 | - page: Page { |
706 | - id: page3 |
707 | - } |
708 | - } |
709 | - Tab { |
710 | - id: tabFlick1 |
711 | - title: "flick" |
712 | - page: Page { |
713 | - Flickable { |
714 | - id: flickable1 |
715 | - anchors.fill: parent |
716 | - } |
717 | - } |
718 | - } |
719 | - Tab { |
720 | - id: tabFlick2 |
721 | - title: "flick 2" |
722 | - page: Page { |
723 | - Flickable { |
724 | - id: flickable2 |
725 | - anchors.fill: parent |
726 | - } |
727 | - } |
728 | - } |
729 | - Tab { |
730 | - id: tabFlickLoader |
731 | - title: "load" |
732 | - page: Loader { |
733 | - id: loader |
734 | - sourceComponent: tabs.selectedTabIndex != 5 ? null : pageComponent |
735 | - } |
736 | - } |
737 | - Tab { |
738 | - id: tabNoFlickLoader |
739 | - title: "loadNoFlick" |
740 | - page: Loader { |
741 | - id: loaderNoFlick |
742 | - anchors { |
743 | - left: parent.left |
744 | - right: parent.right |
745 | - bottom: parent.bottom |
746 | - } |
747 | - // height compes from the loaded Page |
748 | - sourceComponent: tabs.selectedTabIndex === 6 ? pageComponentNoFlick : null |
749 | - } |
750 | - } |
751 | - } |
752 | - Component { |
753 | - id: pageComponent |
754 | - Page { |
755 | - title: "Loaded page" |
756 | - property Flickable flick: loadedFlickable |
757 | - Flickable { |
758 | - id: loadedFlickable |
759 | - anchors.fill: parent |
760 | - contentHeight: 1000 |
761 | - } |
762 | - } |
763 | - } |
764 | - Component { |
765 | - id: pageComponentNoFlick |
766 | - Page { |
767 | - title: "Loaded page without flickable" |
768 | - } |
769 | - } |
770 | - } |
771 | - |
772 | - ListModel { |
773 | - id: inputModel |
774 | - Component.onCompleted: { |
775 | - append({ "name": "tab 1" }); |
776 | - insert(0, { "name": "tab 0" }); |
777 | - append({ "name": "tab 3" }); |
778 | - insert(2, { "name": "tab 2" }); |
779 | - } |
780 | - } |
781 | - |
782 | - Tabs { |
783 | - id: tabsWithRepeater |
784 | - Repeater { |
785 | - objectName: "tabsRepeater" |
786 | - id: tabsRepeater |
787 | - model: inputModel |
788 | - Tab { |
789 | - title: name |
790 | - } |
791 | - } |
792 | - } |
793 | - |
794 | - Tabs { |
795 | - id: repeaterTabs |
796 | - |
797 | - Repeater { |
798 | - objectName: "repeater" |
799 | - id: repeater |
800 | - Tab { |
801 | - title: modelData |
802 | - } |
803 | - } |
804 | - } |
805 | - |
806 | - Tabs { |
807 | - id: twoRepeaters |
808 | - |
809 | - Repeater { |
810 | - objectName: "firstRepeater" |
811 | - id: firstRepeater |
812 | - model: inputModel |
813 | - Tab { |
814 | - title: name |
815 | - } |
816 | - } |
817 | - |
818 | - Repeater { |
819 | - objectName: "secondRepeater" |
820 | - id: secondRepeater |
821 | - model: testCase.listModel |
822 | - Tab { |
823 | - title: modelData |
824 | - } |
825 | - } |
826 | - } |
827 | - |
828 | - property var listModel: ["tab #0", "tab #1", "tab #2", "tab #3"]; |
829 | - |
830 | - Tabs { |
831 | - id: twinRepeaters |
832 | - ListModel { |
833 | - id: twinModel |
834 | - Component.onCompleted: { |
835 | - append({ "name": "twintab 1" }); |
836 | - insert(0, { "name": "twintab 0" }); |
837 | - append({ "name": "twintab 3" }); |
838 | - insert(2, { "name": "twintab 2" }); |
839 | - } |
840 | - } |
841 | - Repeater { |
842 | - objectName: "tabsRepeater" |
843 | - id: twinRepeater1 |
844 | - model: twinModel |
845 | - Tab { |
846 | - title: name |
847 | - } |
848 | - } |
849 | - Repeater { |
850 | - objectName: "tabsRepeater" |
851 | - id: twinRepeater2 |
852 | - model: twinModel |
853 | - Tab { |
854 | - title: name |
855 | - } |
856 | - } |
857 | - } |
858 | - |
859 | - TestCase { |
860 | + id: tabs |
861 | + Tab { |
862 | + id: tab1 |
863 | + title: "tab 1" |
864 | + page: Page { |
865 | + id: page1 |
866 | + Button { |
867 | + id: button |
868 | + anchors.centerIn: parent |
869 | + text: "click" |
870 | + } |
871 | + } |
872 | + } |
873 | + Tab { |
874 | + id: tab2 |
875 | + title: "tab 2" |
876 | + page: Page { |
877 | + id: page2 |
878 | + } |
879 | + } |
880 | + Tab { |
881 | + id: tab3 |
882 | + title: "tab 3" |
883 | + page: Page { |
884 | + id: page3 |
885 | + } |
886 | + } |
887 | + Tab { |
888 | + id: tabFlick1 |
889 | + title: "flick" |
890 | + page: Page { |
891 | + Flickable { |
892 | + id: flickable1 |
893 | + anchors.fill: parent |
894 | + } |
895 | + } |
896 | + } |
897 | + Tab { |
898 | + id: tabFlick2 |
899 | + title: "flick 2" |
900 | + page: Page { |
901 | + Flickable { |
902 | + id: flickable2 |
903 | + anchors.fill: parent |
904 | + } |
905 | + } |
906 | + } |
907 | + Tab { |
908 | + id: tabFlickLoader |
909 | + title: "load" |
910 | + page: Loader { |
911 | + id: loader |
912 | + sourceComponent: tabs.selectedTabIndex != 5 ? null : pageComponent |
913 | + } |
914 | + } |
915 | + Tab { |
916 | + id: tabNoFlickLoader |
917 | + title: "loadNoFlick" |
918 | + page: Loader { |
919 | + id: loaderNoFlick |
920 | + anchors { |
921 | + left: parent.left |
922 | + right: parent.right |
923 | + bottom: parent.bottom |
924 | + } |
925 | + // height compes from the loaded Page |
926 | + sourceComponent: tabs.selectedTabIndex === 6 ? pageComponentNoFlick : null |
927 | + } |
928 | + } |
929 | + } |
930 | + Component { |
931 | + id: pageComponent |
932 | + Page { |
933 | + title: "Loaded page" |
934 | + property Flickable flick: loadedFlickable |
935 | + Flickable { |
936 | + id: loadedFlickable |
937 | + anchors.fill: parent |
938 | + contentHeight: 1000 |
939 | + } |
940 | + } |
941 | + } |
942 | + Component { |
943 | + id: pageComponentNoFlick |
944 | + Page { |
945 | + title: "Loaded page without flickable" |
946 | + } |
947 | + } |
948 | + |
949 | + UbuntuTestCase { |
950 | name: "TabsAPI" |
951 | when: windowShown |
952 | |
953 | - /* |
954 | - The following testcases are all related to bug #1253804 |
955 | - */ |
956 | - function test_tabOrder_bug1253804() { |
957 | - var tabsModel = tabsWithRepeater.__model; |
958 | - |
959 | - compare(tabsRepeater.count, inputModel.count, "Incorrect number of tabs in Tabs"); |
960 | - compare(tabsModel.count, tabsRepeater.count, "Incorrect number of tabs in TabBar"); |
961 | - for (var i=0; i < tabsModel.count; i++) { |
962 | - compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i); |
963 | - } |
964 | - |
965 | - //shufle |
966 | - inputModel.move(1, 2, 1); |
967 | - inputModel.move(3, 0, 1); |
968 | - inputModel.move(1, 3, 1); |
969 | - // wait few miliseconds |
970 | - wait(50); |
971 | - for (i=0; i < tabsModel.count; i++) { |
972 | - compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles after shuffling don't match for index "+i); |
973 | - } |
974 | - |
975 | - // set it to null |
976 | - tabsRepeater.model = null; |
977 | - compare(tabsWithRepeater.__model.count, 0, "There are still tabs left after repeater model is reset"); |
978 | - } |
979 | - |
980 | - function test_repeaterTabs() { |
981 | - repeater.model = inputModel; |
982 | - var tabsModel = repeaterTabs.__model; |
983 | - |
984 | - compare(repeater.count, inputModel.count, "Incorrect number of tabs in Tabs"); |
985 | - compare(tabsModel.count, repeater.count, "Incorrect number of tabs in TabBar"); |
986 | - for (var i=0; i < tabsModel.count; i++) { |
987 | - compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i); |
988 | - } |
989 | - |
990 | - // clear repeaterTabs |
991 | - repeater.model = null; |
992 | - compare(repeaterTabs.__model.count, 0, "There are still tabs left after repeater model is reset"); |
993 | - } |
994 | - |
995 | - function test_repeaterTabs_arrayAsModel() { |
996 | - repeater.model = testCase.listModel; |
997 | - var tabsModel = repeaterTabs.__model; |
998 | - |
999 | - compare(repeater.count, testCase.listModel.length, "Incorrect number of tabs in Tabs"); |
1000 | - compare(tabsModel.count, repeater.count, "Incorrect number of tabs in TabBar"); |
1001 | - for (var i=0; i < tabsModel.count; i++) { |
1002 | - compare(tabsModel.get(i).title, testCase.listModel[i], "Tab titles don't match for index "+i); |
1003 | - } |
1004 | - |
1005 | - // shuffling elements in an array is not detectable in Repeater |
1006 | - var x = testCase.listModel[1]; |
1007 | - testCase.listModel[1] = testCase.listModel[0]; |
1008 | - testCase.listModel[0] = x; |
1009 | - expectFailContinue("", "Array changes are not detected by repeaters"); |
1010 | - compare(tabsModel.get(0).title, testCase.listModel[0], "Tab titles don't match for index 0"); |
1011 | - expectFailContinue("", "Array changes are not detected by repeaters"); |
1012 | - compare(tabsModel.get(1).title, testCase.listModel[1], "Tab titles don't match for index 0"); |
1013 | - |
1014 | - // clear repeaterTabs |
1015 | - repeater.model = null; |
1016 | - compare(repeaterTabs.__model.count, 0, "There are still tabs left after repeater model is reset"); |
1017 | - |
1018 | - } |
1019 | - |
1020 | - function test_twoRepeaters() { |
1021 | - var tabsModel = twoRepeaters.__model; |
1022 | - var secondRepeaterModel = secondRepeater.model; |
1023 | - |
1024 | - compare(tabsModel.count, firstRepeater.count + secondRepeater.count, "Incorrect number of tabs in TabBar"); |
1025 | - for (var i = 0; i < firstRepeater.count; i++) { |
1026 | - compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i); |
1027 | - } |
1028 | - for (i = firstRepeater.count; i < firstRepeater.count + secondRepeater.count; i++) { |
1029 | - compare(tabsModel.get(i).title, secondRepeaterModel[i - firstRepeater.count], "Tab titles don't match for index "+i); |
1030 | - } |
1031 | - } |
1032 | - |
1033 | - function test_twinRepeaters() { |
1034 | - var tabsModel = twinRepeaters.__model; |
1035 | - |
1036 | - compare(twinRepeater1.count, twinModel.count, "Incorrect number of tabs in the first repeater"); |
1037 | - compare(twinRepeater2.count, twinModel.count, "Incorrect number of tabs in the second repeater"); |
1038 | - compare(tabsModel.count, twinRepeater1.count + twinRepeater2.count, "Incorrect number of tabs in TabBar"); |
1039 | - for (var j = 0; j < 2; j++) { |
1040 | - for (var i = 0; i < twinModel.count; i++) { |
1041 | - var index = j * twinModel.count + i; |
1042 | - compare(tabsModel.get(index).title, twinModel.get(i).name, "Tab titles don't match for Tabs index " + index); |
1043 | - } |
1044 | - } |
1045 | - |
1046 | - //shuffle |
1047 | - twinModel.move(1, 2, 1); |
1048 | - twinModel.move(3, 0, 1); |
1049 | - twinModel.move(1, 3, 1); |
1050 | - // wait few miliseconds till Tabs update is realized |
1051 | - wait(50); |
1052 | - |
1053 | - /* FIXME |
1054 | - for (var j = 0; j < 2; j++) { |
1055 | - for (var i = 0; i < twinModel.count; i++) { |
1056 | - var index = j * twinModel.count + i; |
1057 | - compare(tabsModel.get(index).title, twinModel.get(i).name, "Tab titles don't match for Tabs index " + index); |
1058 | - } |
1059 | - } |
1060 | - */ |
1061 | - |
1062 | - // set it to null |
1063 | - twinRepeater1.model = null; |
1064 | - twinRepeater2.model = null; |
1065 | - compare(twinRepeaters.__model.count, 0, "There are still tabs left after repeater model is reset"); |
1066 | - } |
1067 | - |
1068 | - function test_emptyTabs() { |
1069 | - compare(emptyTabs.selectedTabIndex, -1, "The default value for selectedTabIndex is -1 when there are no tabs"); |
1070 | - compare(emptyTabs.selectedTab, null, "The default selected tab is null when there are no tabs"); |
1071 | - compare(emptyTabs.currentPage, null, "The default currentPage is null when there are no tabs"); |
1072 | - } |
1073 | - |
1074 | /* FIXME |
1075 | function test_tabsDefaults() { |
1076 | compare(tabs.selectedTabIndex, 0, "The default selectedTabIndex is 0 when Tabs has contents"); |
1077 | @@ -412,8 +197,10 @@ |
1078 | |
1079 | function test_deactivateByAppInteraction() { |
1080 | tabs.tabBar.selectionMode = true; |
1081 | + tabs.selectedTabIndex = 0; |
1082 | + waitForRendering(tabs.tabBar, 400); |
1083 | compare(tabs.tabBar.selectionMode, true, "Tab bar can be put into selection mode"); |
1084 | - mouseClick(button, units.gu(1), units.gu(1), Qt.LeftButton); |
1085 | + mouseClick(button, centerOf(button).x, centerOf(button).y); |
1086 | compare(tabs.tabBar.selectionMode, false, "Tab bar deactivated by interacting with the page contents"); |
1087 | } |
1088 | |
1089 | |
1090 | === added file 'tests/unit_x11/tst_components/tst_tabs_empty.qml' |
1091 | --- tests/unit_x11/tst_components/tst_tabs_empty.qml 1970-01-01 00:00:00 +0000 |
1092 | +++ tests/unit_x11/tst_components/tst_tabs_empty.qml 2014-08-22 17:18:58 +0000 |
1093 | @@ -0,0 +1,41 @@ |
1094 | +/* |
1095 | + * Copyright 2014 Canonical Ltd. |
1096 | + * |
1097 | + * This program is free software; you can redistribute it and/or modify |
1098 | + * it under the terms of the GNU Lesser General Public License as published by |
1099 | + * the Free Software Foundation; version 3. |
1100 | + * |
1101 | + * This program is distributed in the hope that it will be useful, |
1102 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1103 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1104 | + * GNU Lesser General Public License for more details. |
1105 | + * |
1106 | + * You should have received a copy of the GNU Lesser General Public License |
1107 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1108 | + */ |
1109 | + |
1110 | +import QtQuick 2.2 |
1111 | +import Ubuntu.Components 1.1 |
1112 | +import Ubuntu.Test 1.0 |
1113 | + |
1114 | +MainView { |
1115 | + width: units.gu(50) |
1116 | + height: units.gu(100) |
1117 | + |
1118 | + useDeprecatedToolbar: true |
1119 | + |
1120 | + Tabs { |
1121 | + id: empty |
1122 | + } |
1123 | + |
1124 | + UbuntuTestCase { |
1125 | + name: "EmptyTabs" |
1126 | + when: windowShown |
1127 | + |
1128 | + function test_emptyTabs() { |
1129 | + compare(empty.selectedTabIndex, -1, "The default value for selectedTabIndex is -1 when there are no tabs"); |
1130 | + compare(empty.selectedTab, null, "The default selected tab is null when there are no tabs"); |
1131 | + compare(empty.currentPage, null, "The default currentPage is null when there are no tabs"); |
1132 | + } |
1133 | + } |
1134 | +} |
1135 | |
1136 | === added file 'tests/unit_x11/tst_components/tst_tabs_with_repeater.qml' |
1137 | --- tests/unit_x11/tst_components/tst_tabs_with_repeater.qml 1970-01-01 00:00:00 +0000 |
1138 | +++ tests/unit_x11/tst_components/tst_tabs_with_repeater.qml 2014-08-22 17:18:58 +0000 |
1139 | @@ -0,0 +1,234 @@ |
1140 | +/* |
1141 | + * Copyright 2014 Canonical Ltd. |
1142 | + * |
1143 | + * This program is free software; you can redistribute it and/or modify |
1144 | + * it under the terms of the GNU Lesser General Public License as published by |
1145 | + * the Free Software Foundation; version 3. |
1146 | + * |
1147 | + * This program is distributed in the hope that it will be useful, |
1148 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
1149 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1150 | + * GNU Lesser General Public License for more details. |
1151 | + * |
1152 | + * You should have received a copy of the GNU Lesser General Public License |
1153 | + * along with this program. If not, see <http://www.gnu.org/licenses/>. |
1154 | + */ |
1155 | + |
1156 | +import QtQuick 2.2 |
1157 | +import Ubuntu.Components 1.1 |
1158 | +import Ubuntu.Test 1.0 |
1159 | + |
1160 | +MainView { |
1161 | + id: testCase |
1162 | + width: units.gu(50) |
1163 | + height: units.gu(100) |
1164 | + |
1165 | + useDeprecatedToolbar: true |
1166 | + |
1167 | + ListModel { |
1168 | + id: inputModel |
1169 | + Component.onCompleted: { |
1170 | + append({ "name": "tab 1" }); |
1171 | + insert(0, { "name": "tab 0" }); |
1172 | + append({ "name": "tab 3" }); |
1173 | + insert(2, { "name": "tab 2" }); |
1174 | + } |
1175 | + } |
1176 | + |
1177 | + Tabs { |
1178 | + id: tabsWithRepeater |
1179 | + Repeater { |
1180 | + objectName: "tabsRepeater" |
1181 | + id: tabsRepeater |
1182 | + model: inputModel |
1183 | + Tab { |
1184 | + title: name |
1185 | + } |
1186 | + } |
1187 | + } |
1188 | + |
1189 | + Tabs { |
1190 | + id: repeaterTabs |
1191 | + |
1192 | + Repeater { |
1193 | + objectName: "repeater" |
1194 | + id: repeater |
1195 | + Tab { |
1196 | + title: modelData |
1197 | + } |
1198 | + } |
1199 | + } |
1200 | + |
1201 | + Tabs { |
1202 | + id: twoRepeaters |
1203 | + |
1204 | + Repeater { |
1205 | + objectName: "firstRepeater" |
1206 | + id: firstRepeater |
1207 | + model: inputModel |
1208 | + Tab { |
1209 | + title: name |
1210 | + } |
1211 | + } |
1212 | + |
1213 | + Repeater { |
1214 | + objectName: "secondRepeater" |
1215 | + id: secondRepeater |
1216 | + model: testCase.listModel |
1217 | + Tab { |
1218 | + title: modelData |
1219 | + } |
1220 | + } |
1221 | + } |
1222 | + |
1223 | + property var listModel: ["tab #0", "tab #1", "tab #2", "tab #3"]; |
1224 | + |
1225 | + Tabs { |
1226 | + id: twinRepeaters |
1227 | + ListModel { |
1228 | + id: twinModel |
1229 | + Component.onCompleted: { |
1230 | + append({ "name": "twintab 1" }); |
1231 | + insert(0, { "name": "twintab 0" }); |
1232 | + append({ "name": "twintab 3" }); |
1233 | + insert(2, { "name": "twintab 2" }); |
1234 | + } |
1235 | + } |
1236 | + Repeater { |
1237 | + objectName: "tabsRepeater" |
1238 | + id: twinRepeater1 |
1239 | + model: twinModel |
1240 | + Tab { |
1241 | + title: name |
1242 | + } |
1243 | + } |
1244 | + Repeater { |
1245 | + objectName: "tabsRepeater" |
1246 | + id: twinRepeater2 |
1247 | + model: twinModel |
1248 | + Tab { |
1249 | + title: name |
1250 | + } |
1251 | + } |
1252 | + } |
1253 | + |
1254 | + UbuntuTestCase { |
1255 | + name: "TabsWithRepeater" |
1256 | + when: windowShown |
1257 | + |
1258 | + /* |
1259 | + The following testcases are all related to bug #1253804 |
1260 | + */ |
1261 | + function test_tabOrder_bug1253804() { |
1262 | + var tabsModel = tabsWithRepeater.__model; |
1263 | + |
1264 | + compare(tabsRepeater.count, inputModel.count, "Incorrect number of tabs in Tabs"); |
1265 | + compare(tabsModel.count, tabsRepeater.count, "Incorrect number of tabs in TabBar"); |
1266 | + for (var i=0; i < tabsModel.count; i++) { |
1267 | + compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i); |
1268 | + } |
1269 | + |
1270 | + //shufle |
1271 | + inputModel.move(1, 2, 1); |
1272 | + inputModel.move(3, 0, 1); |
1273 | + inputModel.move(1, 3, 1); |
1274 | + // wait few miliseconds |
1275 | + wait(50); |
1276 | + for (i=0; i < tabsModel.count; i++) { |
1277 | + compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles after shuffling don't match for index "+i); |
1278 | + } |
1279 | + |
1280 | + // set it to null |
1281 | + tabsRepeater.model = null; |
1282 | + compare(tabsWithRepeater.__model.count, 0, "There are still tabs left after repeater model is reset"); |
1283 | + } |
1284 | + |
1285 | + function test_repeaterTabs() { |
1286 | + repeater.model = inputModel; |
1287 | + var tabsModel = repeaterTabs.__model; |
1288 | + |
1289 | + compare(repeater.count, inputModel.count, "Incorrect number of tabs in Tabs"); |
1290 | + compare(tabsModel.count, repeater.count, "Incorrect number of tabs in TabBar"); |
1291 | + for (var i=0; i < tabsModel.count; i++) { |
1292 | + compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i); |
1293 | + } |
1294 | + |
1295 | + // clear repeaterTabs |
1296 | + repeater.model = null; |
1297 | + compare(repeaterTabs.__model.count, 0, "There are still tabs left after repeater model is reset"); |
1298 | + } |
1299 | + |
1300 | + function test_repeaterTabs_arrayAsModel() { |
1301 | + repeater.model = testCase.listModel; |
1302 | + var tabsModel = repeaterTabs.__model; |
1303 | + |
1304 | + compare(repeater.count, testCase.listModel.length, "Incorrect number of tabs in Tabs"); |
1305 | + compare(tabsModel.count, repeater.count, "Incorrect number of tabs in TabBar"); |
1306 | + for (var i=0; i < tabsModel.count; i++) { |
1307 | + compare(tabsModel.get(i).title, testCase.listModel[i], "Tab titles don't match for index "+i); |
1308 | + } |
1309 | + |
1310 | + // shuffling elements in an array is not detectable in Repeater |
1311 | + var x = testCase.listModel[1]; |
1312 | + testCase.listModel[1] = testCase.listModel[0]; |
1313 | + testCase.listModel[0] = x; |
1314 | + expectFailContinue("", "Array changes are not detected by repeaters"); |
1315 | + compare(tabsModel.get(0).title, testCase.listModel[0], "Tab titles don't match for index 0"); |
1316 | + expectFailContinue("", "Array changes are not detected by repeaters"); |
1317 | + compare(tabsModel.get(1).title, testCase.listModel[1], "Tab titles don't match for index 0"); |
1318 | + |
1319 | + // clear repeaterTabs |
1320 | + repeater.model = null; |
1321 | + compare(repeaterTabs.__model.count, 0, "There are still tabs left after repeater model is reset"); |
1322 | + |
1323 | + } |
1324 | + |
1325 | + function test_twoRepeaters() { |
1326 | + var tabsModel = twoRepeaters.__model; |
1327 | + var secondRepeaterModel = secondRepeater.model; |
1328 | + |
1329 | + compare(tabsModel.count, firstRepeater.count + secondRepeater.count, "Incorrect number of tabs in TabBar"); |
1330 | + for (var i = 0; i < firstRepeater.count; i++) { |
1331 | + compare(tabsModel.get(i).title, inputModel.get(i).name, "Tab titles don't match for index "+i); |
1332 | + } |
1333 | + for (i = firstRepeater.count; i < firstRepeater.count + secondRepeater.count; i++) { |
1334 | + compare(tabsModel.get(i).title, secondRepeaterModel[i - firstRepeater.count], "Tab titles don't match for index "+i); |
1335 | + } |
1336 | + } |
1337 | + |
1338 | + function test_twinRepeaters() { |
1339 | + var tabsModel = twinRepeaters.__model; |
1340 | + |
1341 | + compare(twinRepeater1.count, twinModel.count, "Incorrect number of tabs in the first repeater"); |
1342 | + compare(twinRepeater2.count, twinModel.count, "Incorrect number of tabs in the second repeater"); |
1343 | + compare(tabsModel.count, twinRepeater1.count + twinRepeater2.count, "Incorrect number of tabs in TabBar"); |
1344 | + for (var j = 0; j < 2; j++) { |
1345 | + for (var i = 0; i < twinModel.count; i++) { |
1346 | + var index = j * twinModel.count + i; |
1347 | + compare(tabsModel.get(index).title, twinModel.get(i).name, "Tab titles don't match for Tabs index " + index); |
1348 | + } |
1349 | + } |
1350 | + |
1351 | + //shuffle |
1352 | + twinModel.move(1, 2, 1); |
1353 | + twinModel.move(3, 0, 1); |
1354 | + twinModel.move(1, 3, 1); |
1355 | + // wait few miliseconds till Tabs update is realized |
1356 | + wait(50); |
1357 | + |
1358 | + /* FIXME |
1359 | + for (var j = 0; j < 2; j++) { |
1360 | + for (var i = 0; i < twinModel.count; i++) { |
1361 | + var index = j * twinModel.count + i; |
1362 | + compare(tabsModel.get(index).title, twinModel.get(i).name, "Tab titles don't match for Tabs index " + index); |
1363 | + } |
1364 | + } |
1365 | + */ |
1366 | + |
1367 | + // set it to null |
1368 | + twinRepeater1.model = null; |
1369 | + twinRepeater2.model = null; |
1370 | + compare(twinRepeaters.__model.count, 0, "There are still tabs left after repeater model is reset"); |
1371 | + } |
1372 | + } |
1373 | +} |
FAILED: Continuous integration, rev:1214 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/841/ jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- utopic- touch/3772/ console jenkins. qa.ubuntu. com/job/ generic- mediumtests- utopic/ 2901/console jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- utopic- amd64-ci/ 673 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- utopic- armhf-ci/ 673 jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- utopic- armhf-ci/ 673/artifact/ work/output/ *zip*/output. zip jenkins. qa.ubuntu. com/job/ ubuntu- sdk-team- ubuntu- ui-toolkit- staging- utopic- i386-ci/ 673 jenkins. qa.ubuntu. com/job/ generic- deb-autopilot- runner- mako/3638/ console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- utopic- armhf/5019 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- utopic- armhf/5019/ artifact/ work/output/ *zip*/output. zip s-jenkins. ubuntu- ci:8080/ job/touch- flash-device/ 11738 jenkins. qa.ubuntu. com/job/ autopilot- testrunner- otto-utopic/ 2355/console jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- utopic- amd64/3185 jenkins. qa.ubuntu. com/job/ generic- mediumtests- builder- utopic- amd64/3185/ artifact/ work/output/ *zip*/output. zip
http://
Executed test runs:
FAILURE: http://
FAILURE: http://
SUCCESS: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
SUCCESS: http://
FAILURE: http://
SUCCESS: http://
deb: http://
Click here to trigger a rebuild: s-jenkins. ubuntu- ci:8080/ job/ubuntu- sdk-team- ubuntu- ui-toolkit- staging- ci/841/ rebuild
http://