Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query shadow dom focusables #33

Merged
merged 3 commits into from
May 1, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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