Skip to content

Commit

Permalink
Fix checks for EdgeHTML engine, because it mocks Chromes Blink engine.
Browse files Browse the repository at this point in the history
  • Loading branch information
devversion committed Nov 22, 2016
1 parent 29131bb commit bb310a3
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 91 deletions.
1 change: 1 addition & 0 deletions src/demo-app/platform/platform-demo.ts
Expand Up @@ -9,6 +9,7 @@ import {MdPlatform} from '@angular/material';
<p>Is Blink: {{ platform.BLINK }}</p>
<p>Is Webkit: {{ platform.WEBKIT }}</p>
<p>Is Trident: {{ platform.TRIDENT }}</p>
<p>Is Edge: {{ platform.EDGE }}</p>
`
})
Expand Down
182 changes: 95 additions & 87 deletions src/lib/core/a11y/interactivity-checker.spec.ts
Expand Up @@ -237,134 +237,142 @@ describe('InteractivityChecker', () => {
});
});

it('should return true for div and span with tabindex == 0', () => {
let elements = createElements('div', 'span');

elements.forEach(el => el.setAttribute('tabindex', '0'));
appendElements(elements);

elements.forEach(el => {
expect(checker.isFocusable(el))
.toBe(true, `Expected <${el.nodeName} tabindex="0"> to be focusable`);
});
});
});

describe('isTabbable', () => {
it('should return true for native form controls and anchor without tabindex attribute', () => {
let elements = createElements('input', 'textarea', 'select', 'button', 'a');
appendElements(elements);

elements.forEach(el => {
expect(checker.isTabbable(el)).toBe(true, `Expected <${el.nodeName}> to be tabbable`);
});
});

it('should return false for native form controls and anchor with tabindex == -1', () => {
let elements = createElements('input', 'textarea', 'select', 'button', 'a');

elements.forEach(el => el.setAttribute('tabindex', '-1'));
appendElements(elements);
it('should respect the tabindex for video elements with controls',
// Do not run for Blink and Firefox, because those treat video elements
// with controls different.
runIf(!platform.BLINK && !platform.FIREFOX, () => {

elements.forEach(el => {
expect(checker.isTabbable(el))
.toBe(false, `Expected <${el.nodeName} tabindex="-1"> not to be tabbable`);
});
});
let video = createFromTemplate('<video controls>', true);

it('should return true for div and span with tabindex == 0', () => {
let elements = createElements('div', 'span');
expect(checker.isTabbable(video)).toBe(true);

elements.forEach(el => el.setAttribute('tabindex', '0'));
appendElements(elements);
video.tabIndex = -1;

elements.forEach(el => {
expect(checker.isTabbable(el))
.toBe(true, `Expected <${el.nodeName} tabindex="0"> to be tabbable`);
});
});
expect(checker.isTabbable(video)).toBe(false);
})
);

it('should respect the inherited tabindex inside of frame elements', () => {
let iframe = createFromTemplate('<iframe>', true) as HTMLFrameElement;
let button = createFromTemplate('<button tabindex="0">Not Tabbable</button>');
it('should always mark video elements with controls as tabbable (BLINK & FIREFOX)',
// Only run this spec for Blink and Firefox, because those always treat video
// elements with controls as tabbable.
runIf(platform.BLINK || platform.FIREFOX, () => {

appendElements([iframe]);
let video = createFromTemplate('<video controls>', true);

iframe.tabIndex = -1;
iframe.contentDocument.body.appendChild(button);
expect(checker.isTabbable(video)).toBe(true);

expect(checker.isTabbable(iframe)).toBe(false);
expect(checker.isTabbable(button)).toBe(false);
video.tabIndex = -1;

iframe.tabIndex = null;
expect(checker.isTabbable(video)).toBe(true);
})
);

expect(checker.isTabbable(iframe)).toBe(false);
expect(checker.isTabbable(button)).toBe(true);
});
// Some tests should not run inside of iOS browsers, because those only allow specific
// elements to be tabbable and cause the tests to always fail.
describe('for non-iOS browsers', runIf(!platform.IOS, () => {

it('should mark form controls and anchors without tabindex attribute as tabbable', () => {
let elements = createElements('input', 'textarea', 'select', 'button', 'a');
appendElements(elements);

it('should mark elements which are contentEditable as tabbable', async(() => {
let editableEl = createFromTemplate('<div contenteditable="true">', true);
elements.forEach(el => {
expect(checker.isTabbable(el)).toBe(true, `Expected <${el.nodeName}> to be tabbable`);
});
});

// Wait one tick, because the browser takes some time to update the tabIndex
// according to the contentEditable attribute.
setTimeout(() => {
it('should return true for div and span with tabindex == 0', () => {
let elements = createElements('div', 'span');

expect(checker.isTabbable(editableEl)).toBe(true);
elements.forEach(el => el.setAttribute('tabindex', '0'));
appendElements(elements);

editableEl.tabIndex = -1;
elements.forEach(el => {
expect(checker.isFocusable(el))
.toBe(true, `Expected <${el.nodeName} tabindex="0"> to be focusable`);
});
});

expect(checker.isTabbable(editableEl)).toBe(false);
it('should return false for native form controls and anchor with tabindex == -1', () => {
let elements = createElements('input', 'textarea', 'select', 'button', 'a');

}, 1);
elements.forEach(el => el.setAttribute('tabindex', '-1'));
appendElements(elements);

}));
elements.forEach(el => {
expect(checker.isTabbable(el))
.toBe(false, `Expected <${el.nodeName} tabindex="-1"> not to be tabbable`);
});
});

it('should never mark iframe elements as tabbable', () => {
let iframe = createFromTemplate('<iframe>', true);
it('should return true for div and span with tabindex == 0', () => {
let elements = createElements('div', 'span');

// iFrame elements will be never marked as tabbable, because it depends on the content
// which is mostly not detectable due to CORS and also the checks will be not reliable.
expect(checker.isTabbable(iframe)).toBe(false);
});
elements.forEach(el => el.setAttribute('tabindex', '0'));
appendElements(elements);

elements.forEach(el => {
expect(checker.isTabbable(el))
.toBe(true, `Expected <${el.nodeName} tabindex="0"> to be tabbable`);
});
});

it('should respect the inherited tabindex inside of frame elements', () => {
let iframe = createFromTemplate('<iframe>', true) as HTMLFrameElement;
let button = createFromTemplate('<button tabindex="0">Not Tabbable</button>');

it('should always mark audio elements without controls as not tabbable', () => {
let audio = createFromTemplate('<audio>', true);
appendElements([iframe]);

expect(checker.isTabbable(audio)).toBe(false);
});
iframe.tabIndex = -1;
iframe.contentDocument.body.appendChild(button);

it('should respect the tabindex for video elements with controls',
// Do not run for Blink and Firefox, because those treat video elements
// with controls different.
runIf(!platform.BLINK && !platform.FIREFOX, () => {
expect(checker.isTabbable(iframe)).toBe(false);
expect(checker.isTabbable(button)).toBe(false);

let video = createFromTemplate('<video controls>', true);
iframe.tabIndex = null;

expect(checker.isTabbable(video)).toBe(true);
expect(checker.isTabbable(iframe)).toBe(false);
expect(checker.isTabbable(button)).toBe(true);
});

video.tabIndex = -1;
it('should mark elements which are contentEditable as tabbable', async(() => {
let editableEl = createFromTemplate('<div contenteditable="true">', true);

expect(checker.isTabbable(video)).toBe(false);
})
);
// Wait one tick, because the browser takes some time to update the tabIndex
// according to the contentEditable attribute.
setTimeout(() => {

it('should always mark video elements with controls as tabbable (BLINK & FIREFOX)',
// Only run this spec for Blink and Firefox, because those always treat video
// elements with controls as tabbable.
runIf(platform.BLINK || platform.FIREFOX, () => {
expect(checker.isTabbable(editableEl)).toBe(true);

let video = createFromTemplate('<video controls>', true);
editableEl.tabIndex = -1;

expect(checker.isTabbable(video)).toBe(true);
expect(checker.isTabbable(editableEl)).toBe(false);

video.tabIndex = -1;
}, 1);

expect(checker.isTabbable(video)).toBe(true);
})
);
}));

it('should never mark iframe elements as tabbable', () => {
let iframe = createFromTemplate('<iframe>', true);

// iFrame elements will be never marked as tabbable, because it depends on the content
// which is mostly not detectable due to CORS and also the checks will be not reliable.
expect(checker.isTabbable(iframe)).toBe(false);
});

it('should always mark audio elements without controls as not tabbable', () => {
let audio = createFromTemplate('<audio>', true);

expect(checker.isTabbable(audio)).toBe(false);
});

}));

describe('for Blink and Webkit browsers', runIf(platform.BLINK || platform.WEBKIT, () => {

Expand Down
16 changes: 12 additions & 4 deletions src/lib/core/platform/platform.ts
Expand Up @@ -3,15 +3,23 @@ import {Injectable, NgModule, ModuleWithProviders} from '@angular/core';
// Declare window with type of any.
declare const window: any;

// Whether the current platform supports the V8 Break Iterator. The V8 check
// is necessary to detect all Blink based browsers.
const hasV8BreakIterator = (window.Intl && (window.Intl as any).v8BreakIterator);

@Injectable()
export class MdPlatform {

/* Layout Engines */
BLINK = !!(window.chrome || (window.Intl && (window.Intl as any).v8BreakIterator)) && !!CSS;
WEBKIT = /AppleWebKit/i.test(navigator.userAgent) && !this.BLINK;
/** Layout Engines */
EDGE = /(edge)/i.test(navigator.userAgent);
BLINK = !!(window.chrome || hasV8BreakIterator) && !!CSS && !this.EDGE;

// Webkit is part of the userAgent in EdgeHTML and Blink, so we need to ensure that Webkit
// runs standalone.
WEBKIT = /AppleWebKit/i.test(navigator.userAgent) && !this.BLINK && !this.EDGE;
TRIDENT = /(msie|trident)/i.test(navigator.userAgent);

/* Browsers and Platform Types */
/** Browsers and Platform Types */
ANDROID = /android/i.test(navigator.userAgent);
IOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
FIREFOX = /(firefox|minefield)/i.test(navigator.userAgent);
Expand Down

0 comments on commit bb310a3

Please sign in to comment.