]>
Commit | Line | Data |
---|---|---|
6527f429 DM |
1 | describe("Ext.util.FocusableContainer", function() {\r |
2 | var forward = true,\r | |
3 | backward = false,\r | |
4 | autoId = 0,\r | |
5 | focusAndWait = jasmine.focusAndWait,\r | |
6 | pressTab = jasmine.pressTabKey,\r | |
7 | pressArrow = jasmine.pressArrowKey,\r | |
8 | waitForFocus = jasmine.waitForFocus,\r | |
9 | expectFocused = jasmine.expectFocused,\r | |
10 | expectAria = jasmine.expectAriaAttr,\r | |
11 | expectNoAria = jasmine.expectNoAriaAttr,\r | |
12 | Container, fc, fcEl;\r | |
13 | \r | |
14 | function makeButton(config) {\r | |
15 | config = config || {};\r | |
16 | \r | |
17 | Ext.applyIf(config, {\r | |
18 | // It is easier to troubleshoot test failures when error message\r | |
19 | // says "expected fooBtn-xyz to be afterBtn-xyz", rather than\r | |
20 | // "expected button-xyz to be button-zyx"; since these\r | |
21 | // messages often display element id's, it's easier to set component\r | |
22 | // id here than guesstimate later.\r | |
23 | id: (config.text || 'button') + '-' + ++autoId,\r | |
24 | renderTo: Ext.getBody()\r | |
25 | });\r | |
26 | \r | |
27 | var btn = new Ext.button.Button(config);\r | |
28 | \r | |
29 | return btn;\r | |
30 | }\r | |
31 | \r | |
32 | function makeContainer(config) {\r | |
33 | var items, i, len, item;\r | |
34 | \r | |
35 | config = Ext.apply({\r | |
36 | id: 'focusableContainer-' + ++autoId,\r | |
37 | width: 1000,\r | |
38 | height: 50,\r | |
39 | style: {\r | |
40 | 'background-color': 'green'\r | |
41 | },\r | |
42 | layout: 'hbox',\r | |
43 | defaults: {\r | |
44 | xtype: 'button'\r | |
45 | },\r | |
46 | renderTo: Ext.getBody()\r | |
47 | }, config);\r | |
48 | \r | |
49 | items = config.items;\r | |
50 | \r | |
51 | if (items) {\r | |
52 | for (i = 0, len = items.length; i < len; i++) {\r | |
53 | item = items[i];\r | |
54 | \r | |
55 | if (item.xtype === 'button') {\r | |
56 | item.id = (item.text || 'button') + '-' + ++autoId;\r | |
57 | };\r | |
58 | }\r | |
59 | }\r | |
60 | \r | |
61 | fc = new Container(config);\r | |
62 | fcEl = fc.getFocusableContainerEl();\r | |
63 | \r | |
64 | return fc;\r | |
65 | }\r | |
66 | \r | |
67 | beforeEach(function() {\r | |
68 | Container = Ext.define('spec.FocusableContainer', {\r | |
69 | extend: 'Ext.container.Container',\r | |
70 | \r | |
71 | mixins: [\r | |
72 | 'Ext.util.FocusableContainer'\r | |
73 | ]\r | |
74 | });\r | |
75 | });\r | |
76 | \r | |
77 | afterEach(function() {\r | |
78 | if (fc) {\r | |
79 | fc.destroy();\r | |
80 | }\r | |
81 | \r | |
82 | Ext.undefine('spec.FocusableContainer');\r | |
83 | Container = fc = fcEl = null;\r | |
84 | });\r | |
85 | \r | |
86 | describe("init/destroy", function() {\r | |
87 | var proto, first, second, third;\r | |
88 | \r | |
89 | function setupContainer(config) {\r | |
90 | config = Ext.apply({\r | |
91 | activeChildTabIndex: 42,\r | |
92 | items: [{\r | |
93 | xtype: 'button',\r | |
94 | itemId: 'first',\r | |
95 | text: 'first'\r | |
96 | }, {\r | |
97 | xtype: 'button',\r | |
98 | itemId: 'second',\r | |
99 | text: 'second',\r | |
100 | disabled: true\r | |
101 | }, {\r | |
102 | xtype: 'button',\r | |
103 | itemId: 'third',\r | |
104 | text: 'third',\r | |
105 | tabIndex: -10\r | |
106 | }]\r | |
107 | }, config);\r | |
108 | \r | |
109 | makeContainer(config);\r | |
110 | \r | |
111 | first = fc.down('#first');\r | |
112 | second = fc.down('#second');\r | |
113 | third = fc.down('#third');\r | |
114 | \r | |
115 | return fc;\r | |
116 | }\r | |
117 | \r | |
118 | beforeEach(function() {\r | |
119 | proto = Container.prototype;\r | |
120 | \r | |
121 | spyOn(proto, 'doInitFocusableContainer').andCallThrough();\r | |
122 | spyOn(proto, 'doDestroyFocusableContainer').andCallThrough();\r | |
123 | });\r | |
124 | \r | |
125 | afterEach(function() {\r | |
126 | proto = first = second = third = null;\r | |
127 | });\r | |
128 | \r | |
129 | describe("enableFocusableContainer === true (default)", function() {\r | |
130 | describe("enableFocusableContainer stays true", function() {\r | |
131 | beforeEach(function() {\r | |
132 | setupContainer();\r | |
133 | });\r | |
134 | \r | |
135 | it("should call init", function() {\r | |
136 | expect(fc.doInitFocusableContainer).toHaveBeenCalled();\r | |
137 | });\r | |
138 | \r | |
139 | it("should place tabindex on container el", function() {\r | |
140 | expectAria(fc, 'tabIndex', '42');\r | |
141 | });\r | |
142 | \r | |
143 | it("should create keyNav", function() {\r | |
144 | expect(fc.focusableKeyNav).toBeDefined();\r | |
145 | });\r | |
146 | \r | |
147 | it("should set tabindex on the first child", function() {\r | |
148 | expectAria(first, 'tabIndex', '-1');\r | |
149 | });\r | |
150 | \r | |
151 | it("should NOT set tabindex on the second child", function() {\r | |
152 | expectNoAria(second, 'tabIndex');\r | |
153 | });\r | |
154 | \r | |
155 | it("should set tabindex on the third child", function() {\r | |
156 | expectAria(third, 'tabIndex', '-1');\r | |
157 | });\r | |
158 | \r | |
159 | it("should call destroy", function() {\r | |
160 | fc.destroy();\r | |
161 | \r | |
162 | expect(fc.doDestroyFocusableContainer).toHaveBeenCalled();\r | |
163 | });\r | |
164 | });\r | |
165 | \r | |
166 | describe("enableFocusableContainer stays true with no enabled children", function() {\r | |
167 | beforeEach(function() {\r | |
168 | setupContainer({ renderTo: undefined });\r | |
169 | \r | |
170 | first.disable();\r | |
171 | third.disable();\r | |
172 | \r | |
173 | fc.render(Ext.getBody());\r | |
174 | });\r | |
175 | \r | |
176 | it("should call init", function() {\r | |
177 | expect(fc.doInitFocusableContainer).toHaveBeenCalled();\r | |
178 | });\r | |
179 | \r | |
180 | it("should NOT set tabindex on container el", function() {\r | |
181 | expectNoAria(fc, 'tabIndex');\r | |
182 | });\r | |
183 | \r | |
184 | it("should create keyNav", function() {\r | |
185 | expect(fc.focusableKeyNav).toBeDefined();\r | |
186 | });\r | |
187 | \r | |
188 | it("should not set tabindex on the first child", function() {\r | |
189 | expectNoAria(first, 'tabIndex');\r | |
190 | });\r | |
191 | \r | |
192 | it("should not set tabindex on the second child", function() {\r | |
193 | expectNoAria(second, 'tabIndex');\r | |
194 | });\r | |
195 | \r | |
196 | it("should not set tabindex on the third child", function() {\r | |
197 | expectNoAria(third, 'tabIndex');\r | |
198 | });\r | |
199 | \r | |
200 | it("should call destroy", function() {\r | |
201 | fc.destroy();\r | |
202 | \r | |
203 | expect(fc.doDestroyFocusableContainer).toHaveBeenCalled();\r | |
204 | });\r | |
205 | });\r | |
206 | \r | |
207 | // This is common case when a toolbar needs to make a late decision to bail out\r | |
208 | // of being a FocusableContainer because one or more of its children needs to handle\r | |
209 | // arrow key presses. See https://sencha.jira.com/browse/EXTJS-17458\r | |
210 | describe("enableFocusableContainer changes to false before rendering", function() {\r | |
211 | beforeEach(function() {\r | |
212 | setupContainer({ renderTo: undefined });\r | |
213 | fc.enableFocusableContainer = false;\r | |
214 | fc.render(Ext.getBody());\r | |
215 | });\r | |
216 | \r | |
217 | it("should not call init", function() {\r | |
218 | expect(fc.doInitFocusableContainer).not.toHaveBeenCalled();\r | |
219 | });\r | |
220 | \r | |
221 | it("should not place tabindex on container el", function() {\r | |
222 | expectNoAria(fc, 'tabIndex');\r | |
223 | });\r | |
224 | \r | |
225 | it("should not create keyNav", function() {\r | |
226 | expect(fc.focusableKeyNav).not.toBeDefined();\r | |
227 | });\r | |
228 | \r | |
229 | it("should not add tabindex to second child", function() {\r | |
230 | expectNoAria(second, 'tabIndex');\r | |
231 | });\r | |
232 | \r | |
233 | it("should not alter tabindex on last child", function() {\r | |
234 | expectAria(third, 'tabIndex', '-10');\r | |
235 | });\r | |
236 | \r | |
237 | it("should not call destroy", function() {\r | |
238 | fc.destroy();\r | |
239 | \r | |
240 | expect(fc.doDestroyFocusableContainer).not.toHaveBeenCalled();\r | |
241 | });\r | |
242 | });\r | |
243 | });\r | |
244 | \r | |
245 | describe("enableFocusableContainer === false", function() {\r | |
246 | beforeEach(function() {\r | |
247 | setupContainer({ enableFocusableContainer: false });\r | |
248 | });\r | |
249 | \r | |
250 | it("should not call init", function() {\r | |
251 | expect(fc.doInitFocusableContainer).not.toHaveBeenCalled();\r | |
252 | });\r | |
253 | \r | |
254 | it("should not place tabindex on container el", function() {\r | |
255 | expectNoAria(fc, 'tabIndex');\r | |
256 | });\r | |
257 | \r | |
258 | it("should not create keyNav", function() {\r | |
259 | expect(fc.focusableKeyNav).not.toBeDefined();\r | |
260 | });\r | |
261 | \r | |
262 | it("should not alter tabindex on first child", function() {\r | |
263 | expectAria(first, 'tabIndex', '0');\r | |
264 | });\r | |
265 | \r | |
266 | it("should not add tabindex to second child", function() {\r | |
267 | expectNoAria(second, 'tabIndex');\r | |
268 | });\r | |
269 | \r | |
270 | it("should not alter tabindex on last child", function() {\r | |
271 | expectAria(third, 'tabIndex', '-10');\r | |
272 | });\r | |
273 | \r | |
274 | it("should not call destroy", function() {\r | |
275 | fc.destroy();\r | |
276 | \r | |
277 | expect(fc.doDestroyFocusableContainer).not.toHaveBeenCalled();\r | |
278 | });\r | |
279 | });\r | |
280 | });\r | |
281 | \r | |
282 | describe("show", function() {\r | |
283 | beforeEach(function() {\r | |
284 | makeContainer({\r | |
285 | items: [{\r | |
286 | xtype: 'button',\r | |
287 | text: 'OK'\r | |
288 | }]\r | |
289 | });\r | |
290 | });\r | |
291 | \r | |
292 | it("should reactivate FC el upon show", function() {\r | |
293 | fc.hide();\r | |
294 | fc.el.set({ tabIndex: -1 });\r | |
295 | fc.show();\r | |
296 | \r | |
297 | expect(fc.el.dom.getAttribute('tabIndex')).toBe('0');\r | |
298 | });\r | |
299 | });\r | |
300 | \r | |
301 | describe("predicates", function() {\r | |
302 | describe("isFocusableContainerActive", function() {\r | |
303 | var button;\r | |
304 | \r | |
305 | function expectActive(want) {\r | |
306 | var have = fc.isFocusableContainerActive();\r | |
307 | \r | |
308 | expect(have).toBe(want);\r | |
309 | }\r | |
310 | \r | |
311 | beforeEach(function() {\r | |
312 | makeContainer({\r | |
313 | items: [{ text: 'foo' }]\r | |
314 | });\r | |
315 | \r | |
316 | button = fc.down('button');\r | |
317 | });\r | |
318 | \r | |
319 | afterEach(function() {\r | |
320 | button = null;\r | |
321 | });\r | |
322 | \r | |
323 | it("should return true by default", function() {\r | |
324 | expectActive(true);\r | |
325 | });\r | |
326 | \r | |
327 | it("should return false when container el is not tabbable", function() {\r | |
328 | fcEl.dom.removeAttribute('tabIndex');\r | |
329 | \r | |
330 | expectActive(false);\r | |
331 | });\r | |
332 | \r | |
333 | describe("after activating a child", function() {\r | |
334 | beforeEach(function() {\r | |
335 | focusAndWait(button);\r | |
336 | });\r | |
337 | \r | |
338 | it("should return true when child is focused", function() {\r | |
339 | expectActive(true);\r | |
340 | });\r | |
341 | \r | |
342 | it("should return false if active child is not tabbable", function() {\r | |
343 | button.getFocusEl().dom.removeAttribute('tabIndex');\r | |
344 | \r | |
345 | expectActive(false);\r | |
346 | });\r | |
347 | });\r | |
348 | });\r | |
349 | });\r | |
350 | \r | |
351 | describe("child lookup", function() {\r | |
352 | describe("first/last child", function() {\r | |
353 | function makeSuite(name, config) {\r | |
354 | describe(name, function() {\r | |
355 | var fooBtn, barBtn;\r | |
356 | \r | |
357 | beforeEach(function() {\r | |
358 | makeContainer(config);\r | |
359 | \r | |
360 | fooBtn = fc.down('button[text=fooBtn]');\r | |
361 | barBtn = fc.down('button[text=barBtn]');\r | |
362 | });\r | |
363 | \r | |
364 | it("finds foo going forward", function() {\r | |
365 | var child = fc.findNextFocusableChild({ step: true });\r | |
366 | \r | |
367 | expect(child).toBe(fooBtn);\r | |
368 | });\r | |
369 | \r | |
370 | it("finds bar going backward", function() {\r | |
371 | var child = fc.findNextFocusableChild({ step: false });\r | |
372 | \r | |
373 | expect(child).toBe(barBtn);\r | |
374 | });\r | |
375 | });\r | |
376 | }\r | |
377 | \r | |
378 | makeSuite('focusable child', {\r | |
379 | items: [\r | |
380 | { xtype: 'button', text: 'fooBtn' },\r | |
381 | { xtype: 'button', text: 'barBtn' }\r | |
382 | ]\r | |
383 | });\r | |
384 | \r | |
385 | makeSuite('non-focusable child', {\r | |
386 | items: [\r | |
387 | { xtype: 'tbtext', text: 'text1' },\r | |
388 | { xtype: 'button', text: 'fooBtn' },\r | |
389 | { xtype: 'button', text: 'barBtn' },\r | |
390 | { xtype: 'tbtext', text: 'text2' }\r | |
391 | ]\r | |
392 | });\r | |
393 | \r | |
394 | makeSuite('focusable but disabled child', {\r | |
395 | items: [\r | |
396 | { xtype: 'button', text: 'disabled1', disabled: true },\r | |
397 | { xtype: 'button', text: 'fooBtn' },\r | |
398 | { xtype: 'button', text: 'barBtn' },\r | |
399 | { xtype: 'button', text: 'disabled2', disabled: true }\r | |
400 | ]\r | |
401 | });\r | |
402 | \r | |
403 | makeSuite('focusable/disabled AND non-focusable child', {\r | |
404 | items: [\r | |
405 | { xtype: 'tbtext', text: 'text1' },\r | |
406 | { xtype: 'button', text: 'disabled1', disabled: true },\r | |
407 | { xtype: 'button', text: 'fooBtn' },\r | |
408 | { xtype: 'button', text: 'barBtn' },\r | |
409 | { xtype: 'tbtext', text: 'text2' },\r | |
410 | { xtype: 'button', text: 'disabled2', disabled: true }\r | |
411 | ]\r | |
412 | });\r | |
413 | });\r | |
414 | \r | |
415 | describe("from existing child", function() {\r | |
416 | var fooBtn, barBtn, fooInput, barInput, disabled1, disabled2;\r | |
417 | \r | |
418 | function expectToFind(whatNext, whereFrom, goingForward) {\r | |
419 | var child = fc.findNextFocusableChild({ child: whereFrom, step: goingForward });\r | |
420 | \r | |
421 | expect(child).toBe(whatNext);\r | |
422 | }\r | |
423 | \r | |
424 | beforeEach(function() {\r | |
425 | makeContainer({\r | |
426 | items: [\r | |
427 | { xtype: 'tbtext', text: 'text1' },\r | |
428 | { xtype: 'button', text: 'disabled1', disabled: true },\r | |
429 | { xtype: 'button', text: 'fooBtn' },\r | |
430 | { xtype: 'tbseparator' },\r | |
431 | { xtype: 'textfield', fieldLabel: 'foo field' },\r | |
432 | { xtype: 'button', text: 'disabled2', disabled: true },\r | |
433 | { xtype: 'button', text: 'barBtn' },\r | |
434 | { xtype: 'tbfill' },\r | |
435 | { xtype: 'combobox', fieldLabel: 'bar combo' }\r | |
436 | ]\r | |
437 | });\r | |
438 | \r | |
439 | fooBtn = fc.down('button[text=fooBtn]');\r | |
440 | barBtn = fc.down('button[text=barBtn]');\r | |
441 | \r | |
442 | disabled1 = fc.down('button[text=disabled1]');\r | |
443 | disabled2 = fc.down('button[text=disabled2]');\r | |
444 | \r | |
445 | fooInput = fc.down('textfield');\r | |
446 | barInput = fc.down('combobox');\r | |
447 | });\r | |
448 | \r | |
449 | afterEach(function() {\r | |
450 | fooBtn = barBtn = fooInput = barInput = disabled1 = disabled2 = null;\r | |
451 | });\r | |
452 | \r | |
453 | describe("forward", function() {\r | |
454 | describe("disabled buttons not changed", function() {\r | |
455 | it("finds fooBtn as the first item", function() {\r | |
456 | expectToFind(fooBtn, null, forward);\r | |
457 | });\r | |
458 | \r | |
459 | it("finds fooInput from fooBtn", function() {\r | |
460 | expectToFind(fooInput, fooBtn, forward);\r | |
461 | });\r | |
462 | \r | |
463 | it("finds barBtn from fooInput", function() {\r | |
464 | expectToFind(barBtn, fooInput, forward);\r | |
465 | });\r | |
466 | \r | |
467 | it("finds barInput from barBtn", function() {\r | |
468 | expectToFind(barInput, barBtn, forward);\r | |
469 | });\r | |
470 | \r | |
471 | it("finds fooBtn from barInput (wraps over)", function() {\r | |
472 | expectToFind(fooBtn, barInput, forward);\r | |
473 | });\r | |
474 | });\r | |
475 | \r | |
476 | describe("disabled1 state changed", function() {\r | |
477 | beforeEach(function() {\r | |
478 | disabled1.enable();\r | |
479 | });\r | |
480 | \r | |
481 | it("finds disabled1 as the first item", function() {\r | |
482 | expectToFind(disabled1, null, forward);\r | |
483 | });\r | |
484 | \r | |
485 | it("finds fooBtn from disabled1", function() {\r | |
486 | expectToFind(fooBtn, disabled1, forward);\r | |
487 | });\r | |
488 | \r | |
489 | it("finds fooInput from fooBtn", function() {\r | |
490 | expectToFind(fooInput, fooBtn, forward);\r | |
491 | });\r | |
492 | \r | |
493 | it("finds barBtn from fooInput", function() {\r | |
494 | expectToFind(barBtn, fooInput, forward);\r | |
495 | });\r | |
496 | \r | |
497 | it("finds barInput from barBtn", function() {\r | |
498 | expectToFind(barInput, barBtn, forward);\r | |
499 | });\r | |
500 | \r | |
501 | it("finds disabled1 from barInput (wraps over)", function() {\r | |
502 | expectToFind(disabled1, barInput, forward);\r | |
503 | });\r | |
504 | });\r | |
505 | \r | |
506 | describe("disabled2 state changed", function() {\r | |
507 | beforeEach(function() {\r | |
508 | disabled2.enable();\r | |
509 | });\r | |
510 | \r | |
511 | it("finds fooBtn as the first item", function() {\r | |
512 | expectToFind(fooBtn, null, forward);\r | |
513 | });\r | |
514 | \r | |
515 | it("finds fooInput from fooBtn", function() {\r | |
516 | expectToFind(fooInput, fooBtn, forward);\r | |
517 | });\r | |
518 | \r | |
519 | it("finds disabled2 from fooInput", function() {\r | |
520 | expectToFind(disabled2, fooInput, forward);\r | |
521 | });\r | |
522 | \r | |
523 | it("finds barBtn from disabled2", function() {\r | |
524 | expectToFind(barBtn, disabled2, forward);\r | |
525 | });\r | |
526 | \r | |
527 | it("finds barInput from barBtn", function() {\r | |
528 | expectToFind(barInput, barBtn, forward);\r | |
529 | });\r | |
530 | \r | |
531 | it("finds fooBtn from barInput (wraps over)", function() {\r | |
532 | expectToFind(fooBtn, barInput, forward);\r | |
533 | });\r | |
534 | });\r | |
535 | });\r | |
536 | \r | |
537 | describe("backward", function() {\r | |
538 | describe("disabled buttons not changed", function() {\r | |
539 | it("finds barInput as the first item", function() {\r | |
540 | expectToFind(barInput, null, backward);\r | |
541 | });\r | |
542 | \r | |
543 | it("finds barBtn from barInput", function() {\r | |
544 | expectToFind(barBtn, barInput, backward);\r | |
545 | });\r | |
546 | \r | |
547 | it("finds fooInput from barBtn", function() {\r | |
548 | expectToFind(fooInput, barBtn, backward);\r | |
549 | });\r | |
550 | \r | |
551 | it("finds fooBtn from fooInput", function() {\r | |
552 | expectToFind(fooBtn, fooInput, backward);\r | |
553 | });\r | |
554 | \r | |
555 | it("finds barInput from fooBtn (wraps over)", function() {\r | |
556 | expectToFind(barInput, fooBtn, backward);\r | |
557 | });\r | |
558 | });\r | |
559 | \r | |
560 | describe("disabled1 state changed", function() {\r | |
561 | beforeEach(function() {\r | |
562 | disabled1.enable();\r | |
563 | });\r | |
564 | \r | |
565 | it("finds barInput as the first item", function() {\r | |
566 | expectToFind(barInput, null, backward);\r | |
567 | });\r | |
568 | \r | |
569 | it("finds barBtn from barInput", function() {\r | |
570 | expectToFind(barBtn, barInput, backward);\r | |
571 | });\r | |
572 | \r | |
573 | it("finds fooInput from barBtn", function() {\r | |
574 | expectToFind(fooInput, barBtn, backward);\r | |
575 | });\r | |
576 | \r | |
577 | it("finds fooBtn from fooInput", function() {\r | |
578 | expectToFind(fooBtn, fooInput, backward);\r | |
579 | });\r | |
580 | \r | |
581 | it("finds disabled1 from fooBtn", function() {\r | |
582 | expectToFind(disabled1, fooBtn, backward);\r | |
583 | });\r | |
584 | \r | |
585 | it("finds barInput from disabled1 (wraps over)", function() {\r | |
586 | expectToFind(barInput, disabled1, backward);\r | |
587 | });\r | |
588 | });\r | |
589 | \r | |
590 | describe("disabled2 state changed", function() {\r | |
591 | beforeEach(function() {\r | |
592 | disabled2.enable();\r | |
593 | });\r | |
594 | \r | |
595 | it("finds barInput as the first item", function() {\r | |
596 | expectToFind(barInput, null, backward);\r | |
597 | });\r | |
598 | \r | |
599 | it("finds barBtn from barInput", function() {\r | |
600 | expectToFind(barBtn, barInput, backward);\r | |
601 | });\r | |
602 | \r | |
603 | it("finds disabled2 from barBtn", function() {\r | |
604 | expectToFind(disabled2, barBtn, backward);\r | |
605 | });\r | |
606 | \r | |
607 | it("finds fooInput from disabled2", function() {\r | |
608 | expectToFind(fooInput, disabled2, backward);\r | |
609 | });\r | |
610 | \r | |
611 | it("finds fooBtn from fooInput", function() {\r | |
612 | expectToFind(fooBtn, fooInput, backward);\r | |
613 | });\r | |
614 | \r | |
615 | it("finds barInput from fooBtn (wraps over)", function() {\r | |
616 | expectToFind(barInput, fooBtn, backward);\r | |
617 | });\r | |
618 | });\r | |
619 | });\r | |
620 | });\r | |
621 | });\r | |
622 | \r | |
623 | describe("child state handling", function() {\r | |
624 | var first, second;\r | |
625 | \r | |
626 | afterEach(function() {\r | |
627 | first = second = null;\r | |
628 | });\r | |
629 | \r | |
630 | describe("initially enabled children", function() {\r | |
631 | beforeEach(function() {\r | |
632 | makeContainer({\r | |
633 | items: [{\r | |
634 | itemId: 'first',\r | |
635 | text: 'first'\r | |
636 | }, {\r | |
637 | itemId: 'second',\r | |
638 | text: 'second'\r | |
639 | }]\r | |
640 | });\r | |
641 | \r | |
642 | first = fc.down('#first');\r | |
643 | second = fc.down('#second');\r | |
644 | });\r | |
645 | \r | |
646 | it("should activate container el", function() {\r | |
647 | expectAria(fc, 'tabIndex', '0');\r | |
648 | });\r | |
649 | \r | |
650 | it("should deactivate container el when all children become disabled", function() {\r | |
651 | first.disable();\r | |
652 | second.disable()\r | |
653 | \r | |
654 | expectNoAria(fc, 'tabIndex');\r | |
655 | });\r | |
656 | });\r | |
657 | \r | |
658 | describe("initially disabled children", function() {\r | |
659 | beforeEach(function() {\r | |
660 | makeContainer({\r | |
661 | items: [{\r | |
662 | itemId: 'first',\r | |
663 | text: 'first',\r | |
664 | disabled: true\r | |
665 | }, {\r | |
666 | itemId: 'second',\r | |
667 | text: 'second',\r | |
668 | disabled: true\r | |
669 | }]\r | |
670 | });\r | |
671 | \r | |
672 | first = fc.down('#first');\r | |
673 | second = fc.down('#second');\r | |
674 | });\r | |
675 | \r | |
676 | it("should not activate container el", function() {\r | |
677 | expectNoAria(fc, 'tabIndex');\r | |
678 | });\r | |
679 | \r | |
680 | it("should activate container el when one child becomes enabled", function() {\r | |
681 | first.enable();\r | |
682 | \r | |
683 | expectAria(fc, 'tabIndex', '0');\r | |
684 | });\r | |
685 | });\r | |
686 | \r | |
687 | describe("child state changes", function() {\r | |
688 | beforeEach(function() {\r | |
689 | makeContainer({\r | |
690 | items: [{\r | |
691 | itemId: 'first',\r | |
692 | text: 'first'\r | |
693 | }, {\r | |
694 | itemId: 'second',\r | |
695 | text: 'second'\r | |
696 | }]\r | |
697 | });\r | |
698 | \r | |
699 | first = fc.down('#first');\r | |
700 | second = fc.down('#second');\r | |
701 | });\r | |
702 | \r | |
703 | it("should set lastFocusedChild when child is focused", function() {\r | |
704 | focusAndWait(first);\r | |
705 | \r | |
706 | runs(function() {\r | |
707 | expect(fc.lastFocusedChild).toBe(first);\r | |
708 | });\r | |
709 | });\r | |
710 | \r | |
711 | describe("children become disabled, none focused", function() {\r | |
712 | beforeEach(function() {\r | |
713 | first.disable();\r | |
714 | second.disable();\r | |
715 | });\r | |
716 | \r | |
717 | it("should deactivate container el", function() {\r | |
718 | expectNoAria(fc, 'tabIndex');\r | |
719 | });\r | |
720 | \r | |
721 | it("should not reset first child tabIndex", function() {\r | |
722 | expectNoAria(first, 'tabIndex');\r | |
723 | });\r | |
724 | \r | |
725 | it("should not reset second child tabIndex", function() {\r | |
726 | expectNoAria(second, 'tabIndex');\r | |
727 | });\r | |
728 | \r | |
729 | describe("one child becoming enabled", function() {\r | |
730 | beforeEach(function() {\r | |
731 | second.enable();\r | |
732 | });\r | |
733 | \r | |
734 | it("should activate container el", function() {\r | |
735 | expectAria(fc, 'tabIndex', '0');\r | |
736 | });\r | |
737 | \r | |
738 | it("should not reset first child tabIndex", function() {\r | |
739 | expectNoAria(first, 'tabIndex');\r | |
740 | });\r | |
741 | \r | |
742 | it("should reset second child tabIndex", function() {\r | |
743 | expectAria(second, 'tabIndex', '-1');\r | |
744 | });\r | |
745 | });\r | |
746 | \r | |
747 | describe("both children become enabled", function() {\r | |
748 | beforeEach(function() {\r | |
749 | first.enable();\r | |
750 | second.enable();\r | |
751 | });\r | |
752 | \r | |
753 | it("should activate container el", function() {\r | |
754 | expectAria(fc, 'tabIndex', '0');\r | |
755 | });\r | |
756 | \r | |
757 | it("should reset first child tabIndex", function() {\r | |
758 | expectAria(first, 'tabIndex', '-1');\r | |
759 | });\r | |
760 | \r | |
761 | it("should reset second child tabIndex", function() {\r | |
762 | expectAria(second, 'tabIndex', '-1');\r | |
763 | });\r | |
764 | });\r | |
765 | });\r | |
766 | \r | |
767 | describe("last focusable child becoming disabled", function() {\r | |
768 | beforeEach(function() {\r | |
769 | runs(function() {\r | |
770 | first.disable();\r | |
771 | });\r | |
772 | \r | |
773 | focusAndWait(second);\r | |
774 | \r | |
775 | runs(function() {\r | |
776 | second.disable();\r | |
777 | });\r | |
778 | });\r | |
779 | \r | |
780 | it("should not reset lastFocusedChild when child is disabled", function() {\r | |
781 | expect(fc.lastFocusedChild).toBe(second);\r | |
782 | });\r | |
783 | \r | |
784 | it("should deactivate container el", function() {\r | |
785 | expectNoAria(fc, 'tabIndex');\r | |
786 | });\r | |
787 | \r | |
788 | it("should not reset tabIndex on the child", function() {\r | |
789 | expectNoAria(second, 'tabIndex');\r | |
790 | });\r | |
791 | \r | |
792 | describe("becoming enabled again", function() {\r | |
793 | beforeEach(function() {\r | |
794 | second.tabIndex = 42;\r | |
795 | second.enable();\r | |
796 | });\r | |
797 | \r | |
798 | it("should not activate container el", function() {\r | |
799 | expectNoAria(fc, 'tabIndex');\r | |
800 | });\r | |
801 | \r | |
802 | it("should not interfere with child tabIndex", function() {\r | |
803 | expectAria(second, 'tabIndex', '42');\r | |
804 | });\r | |
805 | });\r | |
806 | \r | |
807 | describe("all children become enabled", function() {\r | |
808 | beforeEach(function() {\r | |
809 | first.tabIndex = 101;\r | |
810 | second.tabIndex = 102;\r | |
811 | second.enable();\r | |
812 | first.enable();\r | |
813 | });\r | |
814 | \r | |
815 | it("should not activate container el", function() {\r | |
816 | expectNoAria(fc, 'tabIndex');\r | |
817 | });\r | |
818 | \r | |
819 | it("should reset first child tabIndex", function() {\r | |
820 | expectAria(first, 'tabIndex', '-1');\r | |
821 | });\r | |
822 | \r | |
823 | it("should not interfere with second child tabIndex", function() {\r | |
824 | expectAria(second, 'tabIndex', '102');\r | |
825 | });\r | |
826 | });\r | |
827 | });\r | |
828 | });\r | |
829 | });\r | |
830 | \r | |
831 | describe("focus handling", function() {\r | |
832 | var beforeBtn, fooBtn, barBtn;\r | |
833 | \r | |
834 | beforeEach(function() {\r | |
835 | // Before button is outside of the container\r | |
836 | beforeBtn = makeButton({ text: 'beforeBtn' });\r | |
837 | });\r | |
838 | \r | |
839 | afterEach(function() {\r | |
840 | if (beforeBtn) {\r | |
841 | beforeBtn.destroy();\r | |
842 | }\r | |
843 | \r | |
844 | beforeBtn = null;\r | |
845 | });\r | |
846 | \r | |
847 | describe("have focusables", function() {\r | |
848 | beforeEach(function() {\r | |
849 | makeContainer({\r | |
850 | items: [\r | |
851 | { xtype: 'button', text: 'fooBtn' },\r | |
852 | { xtype: 'button', text: 'barBtn' }\r | |
853 | ]\r | |
854 | });\r | |
855 | \r | |
856 | fooBtn = fc.down('button[text=fooBtn]');\r | |
857 | barBtn = fc.down('button[text=barBtn]');\r | |
858 | });\r | |
859 | \r | |
860 | describe("focusing container el", function() {\r | |
861 | beforeEach(function() {\r | |
862 | focusAndWait(fcEl, fooBtn);\r | |
863 | });\r | |
864 | \r | |
865 | describe("in FocusableContainer", function() {\r | |
866 | it("should focus first child", function() {\r | |
867 | expectFocused(fooBtn);\r | |
868 | });\r | |
869 | \r | |
870 | it("should make first child tabbable", function() {\r | |
871 | expectAria(fooBtn, 'tabIndex', '0');\r | |
872 | });\r | |
873 | \r | |
874 | it("should make itself untabbable", function() {\r | |
875 | expectNoAria(fc, 'tabIndex');\r | |
876 | });\r | |
877 | });\r | |
878 | \r | |
879 | describe("out of FocusableContainer", function() {\r | |
880 | beforeEach(function() {\r | |
881 | focusAndWait(beforeBtn);\r | |
882 | });\r | |
883 | \r | |
884 | it("should keep first child tabbable", function() {\r | |
885 | expectAria(fooBtn, 'tabIndex', '0');\r | |
886 | });\r | |
887 | \r | |
888 | it("should not make itself tabbable", function() {\r | |
889 | expectNoAria(fc, 'tabIndex');\r | |
890 | });\r | |
891 | });\r | |
892 | });\r | |
893 | \r | |
894 | describe("focusing children", function() {\r | |
895 | beforeEach(function() {\r | |
896 | focusAndWait(fooBtn);\r | |
897 | });\r | |
898 | \r | |
899 | describe("into FocusableContainer", function() {\r | |
900 | it("should not prevent the child from getting focus", function() {\r | |
901 | expectFocused(fooBtn);\r | |
902 | });\r | |
903 | \r | |
904 | it("should make the child tabbable", function() {\r | |
905 | expectAria(fooBtn, 'tabIndex', '0');\r | |
906 | });\r | |
907 | \r | |
908 | it("should make its el untabbable", function() {\r | |
909 | expectNoAria(fc, 'tabIndex');\r | |
910 | });\r | |
911 | });\r | |
912 | \r | |
913 | describe("out of FocusableContainer", function() {\r | |
914 | beforeEach(function() {\r | |
915 | focusAndWait(beforeBtn);\r | |
916 | });\r | |
917 | \r | |
918 | it("should not prevent focus from leaving", function() {\r | |
919 | expectFocused(beforeBtn);\r | |
920 | });\r | |
921 | \r | |
922 | it("should keep the child tabbable", function() {\r | |
923 | expectAria(fooBtn, 'tabIndex', '0');\r | |
924 | });\r | |
925 | \r | |
926 | it("should keep its el untabbable", function() {\r | |
927 | expectNoAria(fc, 'tabIndex');\r | |
928 | });\r | |
929 | });\r | |
930 | });\r | |
931 | \r | |
932 | describe("disabling currently focused child", function() {\r | |
933 | beforeEach(function() {\r | |
934 | focusAndWait(fooBtn);\r | |
935 | });\r | |
936 | \r | |
937 | describe("when there are other focusable children remaining", function() {\r | |
938 | beforeEach(function() {\r | |
939 | fooBtn.disable();\r | |
940 | });\r | |
941 | \r | |
942 | it("should focus next child", function() {\r | |
943 | expectFocused(barBtn);\r | |
944 | });\r | |
945 | \r | |
946 | it("should not make container el focusable", function() {\r | |
947 | expectNoAria(fcEl, 'tabIndex');\r | |
948 | });\r | |
949 | \r | |
950 | it("should update lastFocusedChild", function() {\r | |
951 | expect(fc.lastFocusedChild).toBe(barBtn);\r | |
952 | });\r | |
953 | });\r | |
954 | \r | |
955 | describe("when there are no focusable children remaining", function() {\r | |
956 | beforeEach(function() {\r | |
957 | barBtn.disable();\r | |
958 | \r | |
959 | fooBtn.findFocusTarget = function() {\r | |
960 | return beforeBtn;\r | |
961 | };\r | |
962 | \r | |
963 | fooBtn.disable();\r | |
964 | });\r | |
965 | \r | |
966 | it("should focus findFocusTarget result", function() {\r | |
967 | expectFocused(beforeBtn);\r | |
968 | });\r | |
969 | \r | |
970 | it("should deactivate container el", function() {\r | |
971 | expectNoAria(fc, 'tabIndex');\r | |
972 | });\r | |
973 | \r | |
974 | it("should not update lastFocusedChild", function() {\r | |
975 | expect(fc.lastFocusedChild).toBe(fooBtn);\r | |
976 | });\r | |
977 | });\r | |
978 | });\r | |
979 | });\r | |
980 | });\r | |
981 | \r | |
982 | describe("mouse event handling", function() {\r | |
983 | var beforeBtn, text, fooBtn, input;\r | |
984 | \r | |
985 | beforeEach(function() {\r | |
986 | beforeBtn = makeButton({ text: 'beforeBtn' });\r | |
987 | \r | |
988 | makeContainer({\r | |
989 | style: {\r | |
990 | 'margin-left': '100px'\r | |
991 | },\r | |
992 | items: [\r | |
993 | { xtype: 'tbtext', text: '****' },\r | |
994 | { xtype: 'button', text: 'fooBtn' },\r | |
995 | { xtype: 'textfield', fieldLabel: 'fooInput' }\r | |
996 | ]\r | |
997 | });\r | |
998 | \r | |
999 | text = fc.down('tbtext');\r | |
1000 | fooBtn = fc.down('button');\r | |
1001 | input = fc.down('textfield');\r | |
1002 | });\r | |
1003 | \r | |
1004 | afterEach(function() {\r | |
1005 | beforeBtn.destroy();\r | |
1006 | \r | |
1007 | beforeBtn = null;\r | |
1008 | });\r | |
1009 | \r | |
1010 | it("should ignore left click on container body el", function() {\r | |
1011 | focusAndWait(beforeBtn);\r | |
1012 | \r | |
1013 | runs(function() {\r | |
1014 | jasmine.fireMouseEvent(fc.el, 'click');\r | |
1015 | });\r | |
1016 | \r | |
1017 | expectFocused(beforeBtn);\r | |
1018 | });\r | |
1019 | \r | |
1020 | it("should ignore right click on container body el", function() {\r | |
1021 | focusAndWait(beforeBtn);\r | |
1022 | \r | |
1023 | runs(function() {\r | |
1024 | jasmine.fireMouseEvent(fc.el, 'click', null, null, 1);\r | |
1025 | });\r | |
1026 | \r | |
1027 | expectFocused(beforeBtn);\r | |
1028 | });\r | |
1029 | \r | |
1030 | it("should not react to clicks in non-focusable children", function() {\r | |
1031 | focusAndWait(beforeBtn);\r | |
1032 | \r | |
1033 | runs(function() {\r | |
1034 | jasmine.fireMouseEvent(text.el, 'click');\r | |
1035 | });\r | |
1036 | \r | |
1037 | expectFocused(beforeBtn);\r | |
1038 | });\r | |
1039 | \r | |
1040 | describe("clicks on focusable child", function() {\r | |
1041 | var spy;\r | |
1042 | \r | |
1043 | // We're listening to mousedown instead of click here because Ext 5/Touch\r | |
1044 | // event system is doing crazy translation of touch/mouse/pointer events\r | |
1045 | // that is browser specific. Click is translated from 'tap' in IE10+\r | |
1046 | // but for some reason firing 'tap' event doesn't seem to be reaching\r | |
1047 | // the proper event plumbing in the Button, so Button's click event never fires.\r | |
1048 | // Mousedown on the element works, and that's good enough for this case.\r | |
1049 | beforeEach(function() {\r | |
1050 | spy = jasmine.createSpy('click');\r | |
1051 | \r | |
1052 | fooBtn.on('click', spy);\r | |
1053 | \r | |
1054 | // Right clicks are blocked by Button's code\r | |
1055 | fooBtn.el.on('mousedown', spy);\r | |
1056 | });\r | |
1057 | \r | |
1058 | it("should not block left click", function() {\r | |
1059 | runs(function() {\r | |
1060 | jasmine.fireMouseEvent(fooBtn.el, 'click');\r | |
1061 | });\r | |
1062 | \r | |
1063 | waitsForSpy(spy, 'left click', 100);\r | |
1064 | \r | |
1065 | runs(function() {\r | |
1066 | expect(spy).toHaveBeenCalled();\r | |
1067 | });\r | |
1068 | });\r | |
1069 | \r | |
1070 | it("should not block right click", function() {\r | |
1071 | runs(function() {\r | |
1072 | jasmine.fireMouseEvent(fooBtn.el, 'click', null, null, 1);\r | |
1073 | });\r | |
1074 | \r | |
1075 | waitsForSpy(spy, 'right click', 100);\r | |
1076 | \r | |
1077 | runs(function() {\r | |
1078 | expect(spy).toHaveBeenCalled();\r | |
1079 | });\r | |
1080 | });\r | |
1081 | });\r | |
1082 | });\r | |
1083 | \r | |
1084 | describe("keyboard event handling", function() {\r | |
1085 | var forward = true,\r | |
1086 | backward = false,\r | |
1087 | beforeBtn, afterBtn, fooBtn, barBtn, fooInput, barInput, slider,\r | |
1088 | disabledBtn1, disabledBtn2;\r | |
1089 | \r | |
1090 | function tabAndExpect(from, direction, to, debug) {\r | |
1091 | pressTab(from, direction);\r | |
1092 | \r | |
1093 | expectFocused(to);\r | |
1094 | }\r | |
1095 | \r | |
1096 | function arrowAndExpect(from, arrow, to) {\r | |
1097 | pressArrow(from, arrow);\r | |
1098 | \r | |
1099 | expectFocused(to);\r | |
1100 | }\r | |
1101 | \r | |
1102 | // Unfortunately we cannot test that the actual problem is solved,\r | |
1103 | // which is scrolling the parent container caused by default action\r | |
1104 | // on arrow keys. This is because synthetic injected events do not cause\r | |
1105 | // default action. The best we can do is to check that event handlers\r | |
1106 | // are calling preventDefault() on the events.\r | |
1107 | // See https://sencha.jira.com/browse/EXTJS-18186\r | |
1108 | describe("preventing parent scroll", function() {\r | |
1109 | var upSpy, downSpy, rightSpy, leftSpy;\r | |
1110 | \r | |
1111 | beforeEach(function() {\r | |
1112 | makeContainer({\r | |
1113 | renderTo: undefined,\r | |
1114 | items: [{\r | |
1115 | xtype: 'button',\r | |
1116 | text: 'fooBtn'\r | |
1117 | }, {\r | |
1118 | xtype: 'button',\r | |
1119 | text: 'barBtn'\r | |
1120 | }]\r | |
1121 | });\r | |
1122 | \r | |
1123 | fooBtn = fc.down('button[text=fooBtn]');\r | |
1124 | barBtn = fc.down('button[text=barBtn]');\r | |
1125 | \r | |
1126 | upSpy = spyOn(fc, 'onFocusableContainerUpKey').andCallThrough();\r | |
1127 | downSpy = spyOn(fc, 'onFocusableContainerDownKey').andCallThrough();\r | |
1128 | rightSpy = spyOn(fc, 'onFocusableContainerRightKey').andCallThrough();\r | |
1129 | leftSpy = spyOn(fc, 'onFocusableContainerLeftKey').andCallThrough();\r | |
1130 | \r | |
1131 | fc.render(Ext.getBody());\r | |
1132 | });\r | |
1133 | \r | |
1134 | afterEach(function() {\r | |
1135 | fooBtn = barBtn = null;\r | |
1136 | upSpy = downSpy = rightSpy = leftSpy = null;\r | |
1137 | });\r | |
1138 | \r | |
1139 | it("should preventDefault on the Up arrow key", function() {\r | |
1140 | pressArrow(barBtn, 'up');\r | |
1141 | \r | |
1142 | waitForFocus(fooBtn);\r | |
1143 | \r | |
1144 | runs(function() {\r | |
1145 | expect(upSpy.mostRecentCall.args[0].defaultPrevented).toBe(true);\r | |
1146 | });\r | |
1147 | });\r | |
1148 | \r | |
1149 | it("should preventDefault on the Down arrow key", function() {\r | |
1150 | pressArrow(fooBtn, 'down');\r | |
1151 | \r | |
1152 | waitForFocus(barBtn);\r | |
1153 | \r | |
1154 | runs(function() {\r | |
1155 | expect(downSpy.mostRecentCall.args[0].defaultPrevented).toBe(true);\r | |
1156 | });\r | |
1157 | });\r | |
1158 | \r | |
1159 | it("should preventDefault on the Right arrow key", function() {\r | |
1160 | pressArrow(fooBtn, 'right');\r | |
1161 | \r | |
1162 | waitForFocus(barBtn);\r | |
1163 | \r | |
1164 | runs(function() {\r | |
1165 | expect(rightSpy.mostRecentCall.args[0].defaultPrevented).toBe(true);\r | |
1166 | });\r | |
1167 | });\r | |
1168 | \r | |
1169 | it("should preventDefault on the Left arrow key", function() {\r | |
1170 | pressArrow(barBtn, 'left');\r | |
1171 | \r | |
1172 | waitForFocus(fooBtn);\r | |
1173 | \r | |
1174 | runs(function() {\r | |
1175 | expect(leftSpy.mostRecentCall.args[0].defaultPrevented).toBe(true);\r | |
1176 | });\r | |
1177 | });\r | |
1178 | });\r | |
1179 | \r | |
1180 | describe("enableFocusableContainer === true", function() {\r | |
1181 | beforeEach(function() {\r | |
1182 | runs(function() {\r | |
1183 | beforeBtn = makeButton({ text: 'beforeBtn' });\r | |
1184 | \r | |
1185 | makeContainer({\r | |
1186 | items: [\r | |
1187 | { xtype: 'tbtext', text: '**' },\r | |
1188 | { xtype: 'button', text: 'disabledBtn1', disabled: true },\r | |
1189 | { xtype: 'button', text: 'fooBtn' },\r | |
1190 | { xtype: 'tbseparator' },\r | |
1191 | { xtype: 'textfield', id: 'fooInput-' + ++autoId },\r | |
1192 | { xtype: 'tbseparator' },\r | |
1193 | {\r | |
1194 | xtype: 'slider',\r | |
1195 | id: 'slider-' + ++autoId,\r | |
1196 | value: 50,\r | |
1197 | width: 100,\r | |
1198 | animate: false\r | |
1199 | },\r | |
1200 | { xtype: 'tbseparator' },\r | |
1201 | { xtype: 'tbfill' },\r | |
1202 | { xtype: 'tbseparator' },\r | |
1203 | { xtype: 'button', text: 'barBtn' },\r | |
1204 | { xtype: 'button', text: 'disabledBtn2', disabled: true },\r | |
1205 | { xtype: 'combobox', id: 'barInput-' + ++autoId },\r | |
1206 | { xtype: 'tbtext', text: '***' }\r | |
1207 | ]\r | |
1208 | });\r | |
1209 | \r | |
1210 | fooBtn = fc.down('button[text=fooBtn]');\r | |
1211 | barBtn = fc.down('button[text=barBtn]');\r | |
1212 | \r | |
1213 | fooInput = fc.down('textfield');\r | |
1214 | barInput = fc.down('combobox');\r | |
1215 | slider = fc.down('slider');\r | |
1216 | \r | |
1217 | disabledBtn1 = fc.down('button[text=disabledBtn1]');\r | |
1218 | disabledBtn2 = fc.down('button[text=disabledBtn2]');\r | |
1219 | \r | |
1220 | afterBtn = makeButton({ text: 'afterBtn' });\r | |
1221 | });\r | |
1222 | \r | |
1223 | jasmine.waitAWhile();\r | |
1224 | });\r | |
1225 | \r | |
1226 | afterEach(function() {\r | |
1227 | beforeBtn.destroy();\r | |
1228 | afterBtn.destroy();\r | |
1229 | });\r | |
1230 | \r | |
1231 | describe("tabbing", function() {\r | |
1232 | describe("clean state in/out", function() {\r | |
1233 | it("should tab from beforeBtn to fooBtn", function() {\r | |
1234 | tabAndExpect(beforeBtn, forward, fooBtn);\r | |
1235 | });\r | |
1236 | \r | |
1237 | it("should shift-tab from fooBtn fo beforeBtn", function() {\r | |
1238 | tabAndExpect(fooBtn, backward, beforeBtn);\r | |
1239 | });\r | |
1240 | \r | |
1241 | it("should tab from fooBtn to afterBtn", function() {\r | |
1242 | tabAndExpect(fooBtn, forward, afterBtn);\r | |
1243 | });\r | |
1244 | \r | |
1245 | it("should shift-tab from afterBtn to fooBtn", function() {\r | |
1246 | tabAndExpect(afterBtn, backward, fooBtn);\r | |
1247 | });\r | |
1248 | });\r | |
1249 | \r | |
1250 | describe("needArrowKeys children", function() {\r | |
1251 | it("should tab from fooInput to slider", function() {\r | |
1252 | tabAndExpect(fooInput, forward, slider);\r | |
1253 | });\r | |
1254 | \r | |
1255 | it("should tab from slider to barBtn", function() {\r | |
1256 | tabAndExpect(slider, forward, barBtn);\r | |
1257 | });\r | |
1258 | \r | |
1259 | it("should tab from barInput to afterBtn", function() {\r | |
1260 | tabAndExpect(barInput, forward, afterBtn);\r | |
1261 | });\r | |
1262 | \r | |
1263 | it("should shift-tab from barInput to barBtn", function() {\r | |
1264 | tabAndExpect(barInput, backward, barBtn);\r | |
1265 | });\r | |
1266 | \r | |
1267 | it("should shift-tab from slider to fooInput", function() {\r | |
1268 | tabAndExpect(slider, backward, fooInput);\r | |
1269 | });\r | |
1270 | \r | |
1271 | it("should shift-tab from fooInput to fooBtn", function() {\r | |
1272 | tabAndExpect(fooInput, backward, fooBtn);\r | |
1273 | });\r | |
1274 | });\r | |
1275 | \r | |
1276 | describe("last focused child", function() {\r | |
1277 | it("should shift-tab back into barInput from afterBtn", function() {\r | |
1278 | tabAndExpect(barInput, forward, afterBtn);\r | |
1279 | tabAndExpect(afterBtn, backward, barInput);\r | |
1280 | });\r | |
1281 | \r | |
1282 | it("should shift-tab back to barBtn from afterBtn", function() {\r | |
1283 | tabAndExpect(barBtn, forward, afterBtn);\r | |
1284 | tabAndExpect(afterBtn, backward, barBtn);\r | |
1285 | });\r | |
1286 | \r | |
1287 | describe("disabled state changes", function() {\r | |
1288 | it("should choose fooBtn when shift-tabbing from afterBtn", function() {\r | |
1289 | tabAndExpect(barBtn, forward, afterBtn);\r | |
1290 | \r | |
1291 | runs(function() {\r | |
1292 | barBtn.disable();\r | |
1293 | });\r | |
1294 | \r | |
1295 | tabAndExpect(afterBtn, backward, fooBtn);\r | |
1296 | });\r | |
1297 | \r | |
1298 | it("should choose disabledBtn1 when tabbing from beforeBtn", function() {\r | |
1299 | tabAndExpect(barBtn, backward, beforeBtn);\r | |
1300 | \r | |
1301 | runs(function() {\r | |
1302 | barBtn.disable();\r | |
1303 | disabledBtn1.enable();\r | |
1304 | });\r | |
1305 | \r | |
1306 | tabAndExpect(beforeBtn, forward, disabledBtn1);\r | |
1307 | });\r | |
1308 | });\r | |
1309 | });\r | |
1310 | });\r | |
1311 | \r | |
1312 | describe("arrow keys", function() {\r | |
1313 | describe("simple children (buttons, etc)", function() {\r | |
1314 | it("should go right from fooBtn to fooInput", function() {\r | |
1315 | arrowAndExpect(fooBtn, 'right', fooInput);\r | |
1316 | });\r | |
1317 | \r | |
1318 | it("should go down from fooBtn to fooInput", function() {\r | |
1319 | arrowAndExpect(fooBtn, 'down', fooInput);\r | |
1320 | });\r | |
1321 | \r | |
1322 | it("should wrap over left from fooBtn to barInput", function() {\r | |
1323 | arrowAndExpect(fooBtn, 'left', barInput);\r | |
1324 | });\r | |
1325 | \r | |
1326 | it("should wrap over up from fooBtn to barInput", function() {\r | |
1327 | arrowAndExpect(fooBtn, 'up', barInput);\r | |
1328 | });\r | |
1329 | \r | |
1330 | it("should go left from barBtn to slider", function() {\r | |
1331 | arrowAndExpect(barBtn, 'left', slider);\r | |
1332 | });\r | |
1333 | \r | |
1334 | it("should go up from barBtn to slider", function() {\r | |
1335 | arrowAndExpect(barBtn, 'up', slider);\r | |
1336 | });\r | |
1337 | });\r | |
1338 | \r | |
1339 | describe("needArrowKeys children", function() {\r | |
1340 | describe("slider", function() {\r | |
1341 | function makeSpec(key) {\r | |
1342 | it("should not block " + key + " arrow key", function() {\r | |
1343 | var changed = false;\r | |
1344 | \r | |
1345 | runs(function() {\r | |
1346 | slider.on('change', function() { changed = true });\r | |
1347 | });\r | |
1348 | \r | |
1349 | pressArrow(slider, key);\r | |
1350 | \r | |
1351 | runs(function() {\r | |
1352 | expect(changed).toBeTruthy();\r | |
1353 | });\r | |
1354 | });\r | |
1355 | }\r | |
1356 | \r | |
1357 | makeSpec('left');\r | |
1358 | makeSpec('right');\r | |
1359 | makeSpec('up');\r | |
1360 | makeSpec('down');\r | |
1361 | });\r | |
1362 | \r | |
1363 | describe("combo box", function() {\r | |
1364 | beforeEach(function() {\r | |
1365 | Ext.apply(barInput, {\r | |
1366 | queryMode: 'local',\r | |
1367 | displayField: 'name'\r | |
1368 | });\r | |
1369 | \r | |
1370 | var store = new Ext.data.Store({\r | |
1371 | fields: ['name'],\r | |
1372 | data: [{ name: 'foo' }]\r | |
1373 | });\r | |
1374 | \r | |
1375 | barInput.setStore(store);\r | |
1376 | });\r | |
1377 | \r | |
1378 | it("should not block down arrow key", function() {\r | |
1379 | pressArrow(barInput, 'down');\r | |
1380 | \r | |
1381 | runs(function() {\r | |
1382 | expect(barInput.isExpanded).toBeTruthy();\r | |
1383 | });\r | |
1384 | });\r | |
1385 | });\r | |
1386 | });\r | |
1387 | });\r | |
1388 | });\r | |
1389 | \r | |
1390 | describe("enableFocusableContainer === false", function() {\r | |
1391 | beforeEach(function() {\r | |
1392 | runs(function() {\r | |
1393 | beforeBtn = makeButton({ text: 'beforeBtn' });\r | |
1394 | \r | |
1395 | makeContainer({\r | |
1396 | renderTo: undefined,\r | |
1397 | items: [\r | |
1398 | { xtype: 'tbtext', text: '**' },\r | |
1399 | { xtype: 'button', text: 'disabledBtn1', disabled: true },\r | |
1400 | { xtype: 'button', text: 'fooBtn' },\r | |
1401 | { xtype: 'tbseparator' },\r | |
1402 | { xtype: 'textfield', id: 'fooInput-' + ++autoId },\r | |
1403 | { xtype: 'tbseparator' },\r | |
1404 | {\r | |
1405 | xtype: 'slider',\r | |
1406 | id: 'slider-' + ++autoId,\r | |
1407 | value: 50,\r | |
1408 | width: 100,\r | |
1409 | animate: false\r | |
1410 | },\r | |
1411 | { xtype: 'tbseparator' },\r | |
1412 | { xtype: 'tbfill' },\r | |
1413 | { xtype: 'tbseparator' },\r | |
1414 | { xtype: 'button', text: 'barBtn' },\r | |
1415 | { xtype: 'button', text: 'disabledBtn2', disabled: true },\r | |
1416 | { xtype: 'combobox', id: 'barInput-' + ++autoId },\r | |
1417 | { xtype: 'tbtext', text: '***' }\r | |
1418 | ]\r | |
1419 | });\r | |
1420 | \r | |
1421 | fooBtn = fc.down('button[text=fooBtn]');\r | |
1422 | barBtn = fc.down('button[text=barBtn]');\r | |
1423 | \r | |
1424 | fooInput = fc.down('textfield');\r | |
1425 | barInput = fc.down('combobox');\r | |
1426 | slider = fc.down('slider');\r | |
1427 | \r | |
1428 | disabledBtn1 = fc.down('button[text=disabledBtn1]');\r | |
1429 | disabledBtn2 = fc.down('button[text=disabledBtn2]');\r | |
1430 | \r | |
1431 | fc.enableFocusableContainer = false;\r | |
1432 | fc.render(Ext.getBody());\r | |
1433 | \r | |
1434 | afterBtn = makeButton({ text: 'afterBtn' });\r | |
1435 | });\r | |
1436 | \r | |
1437 | jasmine.waitAWhile();\r | |
1438 | });\r | |
1439 | \r | |
1440 | afterEach(function() {\r | |
1441 | beforeBtn.destroy();\r | |
1442 | afterBtn.destroy();\r | |
1443 | });\r | |
1444 | \r | |
1445 | describe("tabbing", function() {\r | |
1446 | it("should tab from beforeBtn to fooBtn", function() {\r | |
1447 | tabAndExpect(beforeBtn, forward, fooBtn);\r | |
1448 | });\r | |
1449 | \r | |
1450 | it("should shift-tab from fooBtn to beforeBtn", function() {\r | |
1451 | tabAndExpect(fooBtn, backward, beforeBtn);\r | |
1452 | });\r | |
1453 | \r | |
1454 | it("should tab from fooBtn to fooInput", function() {\r | |
1455 | tabAndExpect(fooBtn, forward, fooInput);\r | |
1456 | });\r | |
1457 | \r | |
1458 | it("should shift-tab from fooInput to fooBtn", function() {\r | |
1459 | tabAndExpect(fooInput, backward, fooBtn);\r | |
1460 | });\r | |
1461 | \r | |
1462 | it("should tab from fooInput to slider", function() {\r | |
1463 | tabAndExpect(fooInput, forward, slider);\r | |
1464 | });\r | |
1465 | \r | |
1466 | it("should shift-tab from slider to fooInput", function() {\r | |
1467 | tabAndExpect(slider, backward, fooInput);\r | |
1468 | });\r | |
1469 | \r | |
1470 | it("should tab from slider to barBtn", function() {\r | |
1471 | tabAndExpect(slider, forward, barBtn);\r | |
1472 | });\r | |
1473 | \r | |
1474 | it("should shift-tab from barBtn to slider", function() {\r | |
1475 | tabAndExpect(barBtn, backward, slider);\r | |
1476 | });\r | |
1477 | \r | |
1478 | it("should tab from barBtn to barInput", function() {\r | |
1479 | tabAndExpect(barBtn, forward, barInput);\r | |
1480 | });\r | |
1481 | \r | |
1482 | it("should shift-tab from barInput to barBtn", function() {\r | |
1483 | tabAndExpect(barInput, backward, barBtn);\r | |
1484 | });\r | |
1485 | \r | |
1486 | it("should tab from barInput to afterBtn", function() {\r | |
1487 | tabAndExpect(barInput, forward, afterBtn);\r | |
1488 | });\r | |
1489 | \r | |
1490 | it("should shift-tab from afterBtn to barInput", function() {\r | |
1491 | tabAndExpect(afterBtn, backward, barInput);\r | |
1492 | });\r | |
1493 | \r | |
1494 | describe("disabled state changes", function() {\r | |
1495 | beforeEach(function() {\r | |
1496 | disabledBtn1.enable();\r | |
1497 | disabledBtn2.enable();\r | |
1498 | });\r | |
1499 | \r | |
1500 | it("should tab from beforeBtn to disabledBtn1", function() {\r | |
1501 | tabAndExpect(beforeBtn, forward, disabledBtn1);\r | |
1502 | });\r | |
1503 | \r | |
1504 | it("should shift-tab from disabledBtn1 to beforeBtn", function() {\r | |
1505 | tabAndExpect(disabledBtn1, backward, beforeBtn);\r | |
1506 | });\r | |
1507 | \r | |
1508 | it("should tab from disabledBtn1 to fooBtn", function() {\r | |
1509 | tabAndExpect(disabledBtn1, forward, fooBtn);\r | |
1510 | });\r | |
1511 | \r | |
1512 | it("should shift-tab from fooBtn to disabledBtn1", function() {\r | |
1513 | tabAndExpect(fooBtn, backward, disabledBtn1);\r | |
1514 | });\r | |
1515 | \r | |
1516 | it("should tab from barBtn to disabledBtn2", function() {\r | |
1517 | tabAndExpect(barBtn, forward, disabledBtn2);\r | |
1518 | });\r | |
1519 | \r | |
1520 | it("should shift-tab from disabledBtn2 to barBtn", function() {\r | |
1521 | tabAndExpect(disabledBtn2, backward, barBtn);\r | |
1522 | });\r | |
1523 | \r | |
1524 | it("should tab from disabledBtn2 to barInput", function() {\r | |
1525 | tabAndExpect(disabledBtn2, forward, barInput);\r | |
1526 | });\r | |
1527 | \r | |
1528 | it("should shift-tab from barInput to disabledBtn2", function() {\r | |
1529 | tabAndExpect(barInput, backward, disabledBtn2);\r | |
1530 | });\r | |
1531 | });\r | |
1532 | });\r | |
1533 | \r | |
1534 | // Arrow keys should not navigate when FocusableContainer is disabled;\r | |
1535 | // we have to make sure of that!\r | |
1536 | describe("arrow keys", function() {\r | |
1537 | describe("fooBtn", function() {\r | |
1538 | it("should stay focused on left arrow", function() {\r | |
1539 | arrowAndExpect(fooBtn, 'left', fooBtn);\r | |
1540 | });\r | |
1541 | \r | |
1542 | it("should stay focused on right arrow", function() {\r | |
1543 | arrowAndExpect(fooBtn, 'right', fooBtn);\r | |
1544 | });\r | |
1545 | \r | |
1546 | it("should stay focused on up arrow", function() {\r | |
1547 | arrowAndExpect(fooBtn, 'up', fooBtn);\r | |
1548 | });\r | |
1549 | \r | |
1550 | it("should stay focused on down arrow", function() {\r | |
1551 | arrowAndExpect(fooBtn, 'down', fooBtn);\r | |
1552 | });\r | |
1553 | });\r | |
1554 | \r | |
1555 | describe("slider", function() {\r | |
1556 | function makeSpec(key) {\r | |
1557 | it("should not block " + key + " arrow key", function() {\r | |
1558 | var changed = false;\r | |
1559 | \r | |
1560 | runs(function() {\r | |
1561 | slider.on('change', function() { changed = true });\r | |
1562 | });\r | |
1563 | \r | |
1564 | pressArrow(slider, key);\r | |
1565 | \r | |
1566 | runs(function() {\r | |
1567 | expect(changed).toBeTruthy();\r | |
1568 | });\r | |
1569 | });\r | |
1570 | }\r | |
1571 | \r | |
1572 | makeSpec('left');\r | |
1573 | makeSpec('right');\r | |
1574 | makeSpec('up');\r | |
1575 | makeSpec('down');\r | |
1576 | });\r | |
1577 | \r | |
1578 | describe("combo box", function() {\r | |
1579 | beforeEach(function() {\r | |
1580 | Ext.apply(barInput, {\r | |
1581 | queryMode: 'local',\r | |
1582 | displayField: 'name'\r | |
1583 | });\r | |
1584 | \r | |
1585 | var store = new Ext.data.Store({\r | |
1586 | fields: ['name'],\r | |
1587 | data: [{ name: 'foo' }]\r | |
1588 | });\r | |
1589 | \r | |
1590 | barInput.setStore(store);\r | |
1591 | });\r | |
1592 | \r | |
1593 | it("should not block down arrow key", function() {\r | |
1594 | pressArrow(barInput, 'down');\r | |
1595 | \r | |
1596 | runs(function() {\r | |
1597 | expect(barInput.isExpanded).toBeTruthy();\r | |
1598 | });\r | |
1599 | });\r | |
1600 | });\r | |
1601 | });\r | |
1602 | });\r | |
1603 | });\r | |
1604 | });\r |