Skip to content

Commit

Permalink
Merge pull request #33 from vbabel/query-shadow-dom-focusables
Browse files Browse the repository at this point in the history
Query shadow dom focusables
  • Loading branch information
theKashey authored May 1, 2022
2 parents 1fe68ec + 70ec30d commit 018b587
Show file tree
Hide file tree
Showing 12 changed files with 400 additions and 29 deletions.
2 changes: 1 addition & 1 deletion .size-limit
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[
{
path: "dist/es2015/index.js",
limit: "2.5 kB"
limit: "2.591 kB"
}
]
4 changes: 2 additions & 2 deletions .size.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"name": "dist/es2015/index.js",
"passed": true,
"size": 2558
"passed": false,
"size": 2653
}
]
97 changes: 97 additions & 0 deletions __tests__/focusInside.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,101 @@ describe('smoke', () => {
expect(focusInside([querySelector('#d3'), querySelector('#d1')])).toBe(true);
});
});

const createShadowTest = (nested?: boolean) => {
const html = `
<div id="app">
<div id="nonshadow">
<input />
<button>I am a button</button>
</div>
<div id="shadowdom"></div>
</div>`;
const shadowHtml = `
<div id="first"></div>
<button id="firstBtn">first button</button>
<div id="last">
<button id="secondBtn">second button</button>
</div>
`;
document.body.innerHTML = html;

const shadowContainer = document.getElementById('shadowdom') as HTMLElement;
const root = shadowContainer.attachShadow({ mode: 'open' });
const shadowDiv = document.createElement('div');
shadowDiv.innerHTML = shadowHtml;
root.appendChild(shadowDiv);

if (nested) {
const firstDiv = root.querySelector('#first') as HTMLDivElement;
const nestedRoot = firstDiv.attachShadow({ mode: 'open' });
const nestedShadowDiv = document.createElement('div');

nestedShadowDiv.innerHTML = shadowHtml;
nestedRoot.appendChild(nestedShadowDiv);
}
};

describe('with shadow dom', () => {
it('false when the focus is within a shadow dom not within the topNode', () => {
createShadowTest();

const nonShadowDiv = querySelector('#nonshadow');

const shadowBtn = querySelector('#shadowdom')?.shadowRoot?.querySelector('#firstBtn') as HTMLButtonElement;

shadowBtn.focus();

expect(focusInside(document.body)).toBe(true);
expect(focusInside(nonShadowDiv)).toBe(false);
});

it('false when topNode is shadow sibling of focused node', () => {
createShadowTest();

const shadowHost = querySelector('#shadowdom');

const shadowBtn = shadowHost.shadowRoot?.querySelector('#firstBtn') as HTMLButtonElement;
const shadowDivLast = shadowHost.shadowRoot?.querySelector('#last') as HTMLDivElement;

shadowBtn.focus();

expect(focusInside(document.body)).toBe(true);
expect(focusInside(shadowDivLast)).toBe(false);
});

it('true when focus is within shadow dom within topNode', () => {
createShadowTest();

const shadowHost = querySelector('#shadowdom');

const shadowDivLast = shadowHost.shadowRoot?.querySelector('#last') as HTMLDivElement;
const shadowBtn = shadowHost.shadowRoot?.querySelector('#secondBtn') as HTMLButtonElement;

shadowBtn.focus();

expect(focusInside(document.body)).toBe(true);
expect(focusInside(shadowHost)).toBe(true);
expect(focusInside(shadowDivLast)).toBe(true);
});

it('true when focus is within nested shadow dom', () => {
createShadowTest(true);

const shadowHost = querySelector('#shadowdom');
const nestedShadowHost = shadowHost.shadowRoot?.querySelector('#first') as HTMLDivElement;

const nestedShadowDiv = nestedShadowHost.shadowRoot?.querySelector('#first') as HTMLDivElement;
const nestedShadowDivLast = nestedShadowHost.shadowRoot?.querySelector('#last') as HTMLDivElement;
const nestedShadowButton = nestedShadowDivLast.querySelector('#secondBtn') as HTMLButtonElement;

nestedShadowButton.focus();

expect(focusInside(document.body)).toBe(true);
expect(focusInside(shadowHost)).toBe(true);
expect(focusInside(nestedShadowHost)).toBe(true);
expect(focusInside(nestedShadowDiv)).toBe(false);
expect(focusInside(nestedShadowDivLast)).toBe(true);
});
});
});
114 changes: 114 additions & 0 deletions __tests__/focusIsHidden.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { focusIsHidden, constants } from '../src';

describe('focusIsHidden', () => {
const setupTest = () => {
document.body.innerHTML = `
<div ${constants.FOCUS_ALLOW}>
<button id="focus-hidden"></button>
</div>
<button id="focus-not-hidden"></button>
`;
};

it('returns true when the focused element is hidden', () => {
setupTest();

const button = document.querySelector('#focus-hidden') as HTMLButtonElement;

button.focus();

expect(focusIsHidden()).toBe(true);
});

it('returns false when the focused element is not hidden', () => {
setupTest();

const button = document.querySelector('#focus-not-hidden') as HTMLButtonElement;

button.focus();

expect(focusIsHidden()).toBe(false);
});

const setupShadowTest = (nested?: boolean) => {
const html = `
<div id="app">
<div id="nonshadow">
<input />
<button>I am a button</button>
</div>
<div id="shadowdom"></div>
</div>`;
const shadowHtml = `
<div id="first"></div>
<button id="firstBtn">first button</button>
<div id="last">
<button id="secondBtn">second button</button>
</div>
`;
document.body.innerHTML = html;

const shadowContainer = document.getElementById('shadowdom') as HTMLElement;
const root = shadowContainer.attachShadow({ mode: 'open' });
const shadowDiv = document.createElement('div');
shadowDiv.innerHTML = shadowHtml;
root.appendChild(shadowDiv);

if (nested) {
const firstDiv = root.querySelector('#first') as HTMLDivElement;
const nestedRoot = firstDiv.attachShadow({ mode: 'open' });
const nestedShadowDiv = document.createElement('div');

nestedShadowDiv.innerHTML = shadowHtml;
nestedRoot.appendChild(nestedShadowDiv);
}
};

it('looks for focus within shadow doms', () => {
setupShadowTest();

const shadowHost = document.querySelector('#shadowdom') as HTMLDivElement;
const button = shadowHost.shadowRoot?.querySelector('#firstBtn') as HTMLButtonElement;

button.focus();

expect(focusIsHidden()).toBe(false);

shadowHost.setAttribute(constants.FOCUS_ALLOW, '');

expect(focusIsHidden()).toBe(true);
});

it('looks for focus within nested shadow doms', () => {
setupShadowTest(true);

const shadowHost = document.querySelector('#shadowdom') as HTMLDivElement;
const nestedShadowHost = shadowHost.shadowRoot?.querySelector('#first') as HTMLDivElement;
const button = nestedShadowHost.shadowRoot?.querySelector('#firstBtn') as HTMLButtonElement;

button.focus();

expect(focusIsHidden()).toBe(false);

shadowHost.setAttribute(constants.FOCUS_ALLOW, '');

expect(focusIsHidden()).toBe(true);
});

it('does not support marking shadow members as FOCUS_ALLOW', () => {
setupShadowTest();

const shadowHost = document.querySelector('#shadowdom') as HTMLDivElement;
const shadowDiv = shadowHost.shadowRoot?.querySelector('#last') as HTMLDivElement;
const button = shadowDiv.children[0] as HTMLButtonElement;

button.focus();

expect(focusIsHidden()).toBe(false);

// Setting elements within shadow doms as FOCUS_ALLOW not supported
shadowDiv.setAttribute(constants.FOCUS_ALLOW, '');

expect(focusIsHidden()).toBe(false);
});
});
Loading

0 comments on commit 018b587

Please sign in to comment.