2022-03-25 15:35:56 +08:00
|
|
|
import { h, inject, nextTick } from 'vue'
|
2022-01-04 09:15:15 +08:00
|
|
|
import { mount } from '@vue/test-utils'
|
2022-04-19 12:46:57 +08:00
|
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
2022-02-11 11:03:15 +08:00
|
|
|
import { EVENT_CODE } from '@element-plus/constants'
|
2022-01-04 09:15:15 +08:00
|
|
|
import ElFocusTrap from '../src/focus-trap.vue'
|
|
|
|
import { FOCUS_TRAP_INJECTION_KEY } from '../src/tokens'
|
|
|
|
|
|
|
|
const AXIOM = 'rem is the best girl'
|
|
|
|
|
|
|
|
describe('<ElFocusTrap', () => {
|
|
|
|
const childKls = 'child-class'
|
|
|
|
const TrapChild = {
|
|
|
|
props: {
|
|
|
|
items: Number,
|
|
|
|
},
|
|
|
|
setup() {
|
|
|
|
const { focusTrapRef, onKeydown } = inject(
|
|
|
|
FOCUS_TRAP_INJECTION_KEY,
|
|
|
|
undefined
|
|
|
|
)!
|
|
|
|
return {
|
|
|
|
focusTrapRef,
|
|
|
|
onKeydown,
|
|
|
|
}
|
|
|
|
},
|
|
|
|
template: `<div ref="focusTrapRef" tabindex="0" class="${childKls}" @keydown="onKeydown">
|
|
|
|
<template v-if="!items">${AXIOM}</template>
|
|
|
|
<template v-else v-for="i in items">
|
|
|
|
<span class="item" tabindex="0">{{ i }}</span>
|
|
|
|
</template>
|
|
|
|
</div>`,
|
|
|
|
}
|
|
|
|
|
2022-02-22 16:44:43 +08:00
|
|
|
const createComponent = (props = {}, items = 0) =>
|
2022-01-04 09:15:15 +08:00
|
|
|
mount(ElFocusTrap, {
|
|
|
|
props: {
|
|
|
|
trapped: true,
|
|
|
|
...props,
|
|
|
|
},
|
|
|
|
slots: {
|
|
|
|
default: () => h(TrapChild, { items }),
|
|
|
|
},
|
|
|
|
attachTo: document.body,
|
|
|
|
})
|
|
|
|
|
|
|
|
let wrapper: ReturnType<typeof createComponent>
|
|
|
|
const findFocusComponent = () => wrapper.findComponent(TrapChild as any)
|
|
|
|
const findDescendants = () => wrapper.findAll('.item')
|
|
|
|
|
|
|
|
afterEach(() => {
|
2022-02-22 16:44:43 +08:00
|
|
|
wrapper?.unmount()
|
2022-01-04 09:15:15 +08:00
|
|
|
document.body.innerHTML = ''
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('render', () => {
|
|
|
|
it('should render correctly', async () => {
|
|
|
|
wrapper = createComponent()
|
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
const child = findFocusComponent()
|
|
|
|
expect(child.exists()).toBe(true)
|
|
|
|
expect(child.text()).toBe(AXIOM)
|
|
|
|
expect(document.activeElement).toBe(child.element)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should be able to focus on the first descendant item', async () => {
|
|
|
|
wrapper = createComponent(undefined, 3)
|
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
const descendants = findDescendants()
|
|
|
|
expect(descendants).toHaveLength(3)
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(descendants.at(0)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('events', () => {
|
|
|
|
it('should be able to dispatch focus on mount event', async () => {
|
2022-04-19 12:46:57 +08:00
|
|
|
const focusOnMount = vi.fn()
|
2022-01-04 09:15:15 +08:00
|
|
|
wrapper = createComponent({
|
|
|
|
onMountOnFocus: focusOnMount,
|
|
|
|
})
|
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
expect(focusOnMount).toHaveBeenCalled()
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should be able to dispatch focus on unmount', async () => {
|
2022-04-19 12:46:57 +08:00
|
|
|
const focusOnUnmount = vi.fn()
|
2022-01-04 09:15:15 +08:00
|
|
|
wrapper = createComponent({
|
|
|
|
onUnmountOnFocus: focusOnUnmount,
|
|
|
|
})
|
|
|
|
await nextTick()
|
|
|
|
const child = findFocusComponent()
|
|
|
|
expect(document.activeElement).toBe(child.element)
|
|
|
|
|
|
|
|
wrapper.unmount()
|
|
|
|
expect(focusOnUnmount).toHaveBeenCalled()
|
|
|
|
expect(document.activeElement).toBe(document.body)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
describe('features', () => {
|
|
|
|
it('should be able to navigate via keyboard', async () => {
|
|
|
|
wrapper = createComponent(undefined, 3)
|
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
const childComponent = findFocusComponent()
|
|
|
|
const items = findDescendants()
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(items.at(0)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* NOTE:
|
|
|
|
* JSDOM does not support keyboard navigation simulation so that
|
|
|
|
* dispatching keyboard event with tab key is useless, we cannot test it
|
|
|
|
* it here, maybe turn to cypress for robust e2e test would be a better idea
|
|
|
|
*/
|
|
|
|
// when loop is off
|
|
|
|
await childComponent.trigger('keydown.shift', {
|
|
|
|
key: EVENT_CODE.tab,
|
|
|
|
})
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(items.at(0)?.element)
|
|
|
|
;(items.at(2)?.element as HTMLElement).focus()
|
|
|
|
expect(document.activeElement).toBe(items.at(2)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
|
|
|
|
await childComponent.trigger('keydown', {
|
|
|
|
key: EVENT_CODE.tab,
|
|
|
|
})
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(items.at(2)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
|
|
|
|
// set loop to true so that tab can tabbing from last to first and back forth
|
|
|
|
await wrapper.setProps({
|
|
|
|
loop: true,
|
|
|
|
})
|
|
|
|
|
|
|
|
await childComponent.trigger('keydown', {
|
|
|
|
key: EVENT_CODE.tab,
|
|
|
|
})
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(items.at(0)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
|
|
|
|
await childComponent.trigger('keydown.shift', {
|
|
|
|
key: EVENT_CODE.tab,
|
|
|
|
})
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(items.at(2)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should not be able to navigate when no focusable element contained', async () => {
|
|
|
|
wrapper = createComponent()
|
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
const focusComponent = findFocusComponent()
|
|
|
|
expect(document.activeElement).toBe(focusComponent.element)
|
|
|
|
|
|
|
|
await focusComponent.trigger('keydown', {
|
|
|
|
key: EVENT_CODE.tab,
|
|
|
|
})
|
|
|
|
expect(document.activeElement).toBe(focusComponent.element)
|
|
|
|
})
|
|
|
|
|
|
|
|
it('should not be able to navigate by keyboard when not trapped', async () => {
|
|
|
|
wrapper = createComponent(
|
|
|
|
{
|
|
|
|
trapped: false,
|
|
|
|
},
|
|
|
|
3
|
|
|
|
)
|
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
const focusComponent = findFocusComponent()
|
|
|
|
const items = findDescendants()
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(items.at(0)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
|
|
|
|
await focusComponent.trigger('keydown', {
|
|
|
|
key: EVENT_CODE.tab,
|
|
|
|
})
|
|
|
|
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(items.at(0)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
it('should not be able to navigate if the current layer is paused', async () => {
|
|
|
|
wrapper = createComponent(
|
|
|
|
{
|
|
|
|
loop: true,
|
|
|
|
},
|
|
|
|
3
|
|
|
|
)
|
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
const focusComponent = findFocusComponent()
|
|
|
|
const items = findDescendants()
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(items.at(0)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
|
|
|
|
await focusComponent.trigger('keydown.shift', {
|
|
|
|
key: EVENT_CODE.tab,
|
|
|
|
})
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(items.at(2)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
|
2022-02-22 12:49:28 +08:00
|
|
|
const newFocusTrap = createComponent({ loop: true }, 3)
|
2022-01-04 09:15:15 +08:00
|
|
|
await nextTick()
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(newFocusTrap.find('.item').element)
|
2022-01-04 09:15:15 +08:00
|
|
|
|
|
|
|
await focusComponent.trigger('keydown', {
|
|
|
|
key: EVENT_CODE.tab,
|
|
|
|
})
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).not.toBe(items.at(0)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
newFocusTrap.unmount()
|
2022-02-22 12:49:28 +08:00
|
|
|
await nextTick()
|
|
|
|
|
|
|
|
expect(document.activeElement).toBe(items.at(2)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
|
|
|
|
await focusComponent.trigger('keydown', {
|
|
|
|
key: EVENT_CODE.tab,
|
|
|
|
})
|
2022-02-22 12:49:28 +08:00
|
|
|
expect(document.activeElement).toBe(items.at(0)?.element)
|
2022-01-04 09:15:15 +08:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|