Skip to content

Commit 09d14cf

Browse files
committed
chore(focus-classes): fix failing tests when browser is blurred
On Saucelabs, browsers will run simultaneously and therefore can't focus all browser windows at the same time. This is problematic when testing focus states. Chrome and Firefoxonly fire FocusEvents when the window is focused. This issue also appears locally. Fixes #2903.
1 parent 8d900e0 commit 09d14cf

File tree

2 files changed

+50
-72
lines changed

2 files changed

+50
-72
lines changed

src/lib/core/style/focus-classes.spec.ts

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,16 @@ import {StyleModule} from './index';
44
import {By} from '@angular/platform-browser';
55
import {TAB} from '../keyboard/keycodes';
66
import {FocusOriginMonitor} from './focus-classes';
7-
import {PlatformModule} from '../platform/index';
8-
import {Platform} from '../platform/platform';
97

10-
11-
// NOTE: Firefox only fires focus & blur events when it is the currently active window.
12-
// This is not always the case on our CI setup, therefore we disable tests that depend on these
13-
// events firing for Firefox. We may be able to fix this by configuring our CI to start Firefox with
14-
// the following preference: focusmanager.testmode = true
15-
16-
17-
describe('FocusOriginMonitor', () => {
8+
fdescribe('FocusOriginMonitor', () => {
189
let fixture: ComponentFixture<PlainButton>;
1910
let buttonElement: HTMLElement;
2011
let buttonRenderer: Renderer;
2112
let focusOriginMonitor: FocusOriginMonitor;
22-
let platform: Platform;
2313

2414
beforeEach(async(() => {
2515
TestBed.configureTestingModule({
26-
imports: [StyleModule, PlatformModule],
16+
imports: [StyleModule],
2717
declarations: [
2818
PlainButton,
2919
],
@@ -32,21 +22,27 @@ describe('FocusOriginMonitor', () => {
3222
TestBed.compileComponents();
3323
}));
3424

35-
beforeEach(inject([FocusOriginMonitor, Platform], (fom: FocusOriginMonitor, pfm: Platform) => {
25+
beforeEach(inject([FocusOriginMonitor], (fom: FocusOriginMonitor) => {
3626
fixture = TestBed.createComponent(PlainButton);
3727
fixture.detectChanges();
3828

3929
buttonElement = fixture.debugElement.query(By.css('button')).nativeElement;
4030
buttonRenderer = fixture.componentInstance.renderer;
4131
focusOriginMonitor = fom;
42-
platform = pfm;
4332

4433
focusOriginMonitor.registerElementForFocusClasses(buttonElement, buttonRenderer);
34+
35+
// On Saucelabs, browsers will run simultaneously and therefore can't focus all browser windows
36+
// at the same time. This is problematic when testing focus states. Chrome and Firefox
37+
// only fire FocusEvents when the window is focused. This issue also appears locally.
38+
let _nativeButtonFocus = buttonElement.focus.bind(buttonElement);
39+
buttonElement.focus = () => {
40+
document.hasFocus() ? _nativeButtonFocus() : dispatchFocusEvent(buttonElement);
41+
};
42+
4543
}));
4644

4745
it('manually registered element should receive focus classes', async(() => {
48-
if (platform.FIREFOX) { return; }
49-
5046
buttonElement.focus();
5147
fixture.detectChanges();
5248

@@ -59,8 +55,6 @@ describe('FocusOriginMonitor', () => {
5955
}));
6056

6157
it('should detect focus via keyboard', async(() => {
62-
if (platform.FIREFOX) { return; }
63-
6458
// Simulate focus via keyboard.
6559
dispatchKeydownEvent(document, TAB);
6660
buttonElement.focus();
@@ -79,8 +73,6 @@ describe('FocusOriginMonitor', () => {
7973
}));
8074

8175
it('should detect focus via mouse', async(() => {
82-
if (platform.FIREFOX) { return; }
83-
8476
// Simulate focus via mouse.
8577
dispatchMousedownEvent(document);
8678
buttonElement.focus();
@@ -99,8 +91,6 @@ describe('FocusOriginMonitor', () => {
9991
}));
10092

10193
it('should detect programmatic focus', async(() => {
102-
if (platform.FIREFOX) { return; }
103-
10494
// Programmatically focus.
10595
buttonElement.focus();
10696
fixture.detectChanges();
@@ -118,8 +108,6 @@ describe('FocusOriginMonitor', () => {
118108
}));
119109

120110
it('focusVia keyboard should simulate keyboard focus', async(() => {
121-
if (platform.FIREFOX) { return; }
122-
123111
focusOriginMonitor.focusVia(buttonElement, buttonRenderer, 'keyboard');
124112
fixture.detectChanges();
125113

@@ -136,8 +124,6 @@ describe('FocusOriginMonitor', () => {
136124
}));
137125

138126
it('focusVia mouse should simulate mouse focus', async(() => {
139-
if (platform.FIREFOX) { return; }
140-
141127
focusOriginMonitor.focusVia(buttonElement, buttonRenderer, 'mouse');
142128
fixture.detectChanges();
143129

@@ -154,8 +140,6 @@ describe('FocusOriginMonitor', () => {
154140
}));
155141

156142
it('focusVia program should simulate programmatic focus', async(() => {
157-
if (platform.FIREFOX) { return; }
158-
159143
focusOriginMonitor.focusVia(buttonElement, buttonRenderer, 'program');
160144
fixture.detectChanges();
161145

@@ -176,11 +160,10 @@ describe('FocusOriginMonitor', () => {
176160
describe('cdkFocusClasses', () => {
177161
let fixture: ComponentFixture<ButtonWithFocusClasses>;
178162
let buttonElement: HTMLElement;
179-
let platform: Platform;
180163

181164
beforeEach(async(() => {
182165
TestBed.configureTestingModule({
183-
imports: [StyleModule, PlatformModule],
166+
imports: [StyleModule],
184167
declarations: [
185168
ButtonWithFocusClasses,
186169
],
@@ -189,21 +172,18 @@ describe('cdkFocusClasses', () => {
189172
TestBed.compileComponents();
190173
}));
191174

192-
beforeEach(inject([Platform], (pfm: Platform) => {
175+
beforeEach(() => {
193176
fixture = TestBed.createComponent(ButtonWithFocusClasses);
194177
fixture.detectChanges();
195178

196179
buttonElement = fixture.debugElement.query(By.css('button')).nativeElement;
197-
platform = pfm;
198-
}));
180+
});
199181

200182
it('should initially not be focused', () => {
201183
expect(buttonElement.classList.length).toBe(0, 'button should not have focus classes');
202184
});
203185

204186
it('should detect focus via keyboard', async(() => {
205-
if (platform.FIREFOX) { return; }
206-
207187
// Simulate focus via keyboard.
208188
dispatchKeydownEvent(document, TAB);
209189
buttonElement.focus();
@@ -222,8 +202,6 @@ describe('cdkFocusClasses', () => {
222202
}));
223203

224204
it('should detect focus via mouse', async(() => {
225-
if (platform.FIREFOX) { return; }
226-
227205
// Simulate focus via mouse.
228206
dispatchMousedownEvent(document);
229207
buttonElement.focus();
@@ -242,8 +220,6 @@ describe('cdkFocusClasses', () => {
242220
}));
243221

244222
it('should detect programmatic focus', async(() => {
245-
if (platform.FIREFOX) { return; }
246-
247223
// Programmatically focus.
248224
buttonElement.focus();
249225
fixture.detectChanges();
@@ -291,3 +267,10 @@ function dispatchKeydownEvent(element: Node, keyCode: number) {
291267
});
292268
element.dispatchEvent(event);
293269
}
270+
271+
/** Dispatches a focus event on the specified element. */
272+
function dispatchFocusEvent(element: Node, type = 'focus') {
273+
let event = document.createEvent('Event');
274+
event.initEvent(type, true, true);
275+
element.dispatchEvent(event);
276+
}

test/karma-test-shim.js

Lines changed: 28 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,37 @@
22
Error.stackTraceLimit = Infinity;
33
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
44

5-
__karma__.loaded = function () {
6-
};
5+
__karma__.loaded = function () {};
76

8-
var distPath = '/base/dist/';
7+
var baseDir = '/base/dist/';
8+
var configFile = baseDir + '@angular/material/system-config-spec.js';
9+
var specFiles = Object.keys(window.__karma__.files).filter(isMaterialSpecFile);
910

10-
function isJsFile(path) {
11-
return path.slice(-3) == '.js';
12-
}
11+
// Configure the base path for dist/
12+
System.config({baseURL: baseDir});
1313

14-
function isSpecFile(path) {
15-
return path.slice(-8) == '.spec.js';
16-
}
14+
// Load the spec SystemJS configuration file.
15+
System.import(configFile)
16+
.then(configureTestBed)
17+
.then(runMaterialSpecs)
18+
.then(__karma__.start, __karma__.error);
1719

18-
function isMaterialFile(path) {
19-
return isJsFile(path) && path.indexOf('vendor') == -1;
20-
}
2120

22-
var allSpecFiles = Object.keys(window.__karma__.files)
23-
.filter(isSpecFile)
24-
.filter(isMaterialFile);
21+
/** Runs the Angular Material specs in Karma. */
22+
function runMaterialSpecs() {
23+
// By importing all spec files, Karma will run the tests directly.
24+
return Promise.all(specFiles.map(function(fileName) {
25+
return System.import(fileName);
26+
}));
27+
}
2528

26-
// Load our SystemJS configuration.
27-
System.config({
28-
baseURL: distPath
29-
});
29+
/** Whether the specified file is part of Angular Material. */
30+
function isMaterialSpecFile(path) {
31+
return path.slice(-8) === '.spec.js' && path.indexOf('vendor') === -1;
32+
}
3033

31-
System.import(distPath + '@angular/material/system-config-spec.js').then(function() {
32-
// Load and configure the TestComponentBuilder.
34+
/** Configures Angular's TestBed. */
35+
function configureTestBed() {
3336
return Promise.all([
3437
System.import('@angular/core/testing'),
3538
System.import('@angular/platform-browser-dynamic/testing')
@@ -40,16 +43,8 @@ System.import(distPath + '@angular/material/system-config-spec.js').then(functio
4043
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
4144

4245
testing.TestBed.initTestEnvironment(
43-
testingBrowser.BrowserDynamicTestingModule,
44-
testingBrowser.platformBrowserDynamicTesting());
46+
testingBrowser.BrowserDynamicTestingModule,
47+
testingBrowser.platformBrowserDynamicTesting()
48+
);
4549
});
46-
}).then(function() {
47-
// Finally, load all spec files.
48-
// This will run the tests directly.
49-
return Promise.all(
50-
allSpecFiles.map(function (moduleName) {
51-
return System.import(moduleName).then(function(module) {
52-
return module;
53-
});
54-
}));
55-
}).then(__karma__.start, __karma__.error);
50+
}

0 commit comments

Comments
 (0)