mirror of
https://github.com/element-plus/element-plus.git
synced 2024-12-27 03:01:14 +08:00
dd19cae2bc
* refactor(components): popper composables - Refactor popper composables * updates * updates for tooltip * Updates for popper. TODO: fix controlled tooltip animation * Fix controlled mode popper animation issue * Add new feature for customizing tooltip theme * Fix popover and popconfirm error * - Add Collection component for wrapping a collection of component - Add FocusTrap component for trap focus for popups - Add RovingFocus component for roving focus component type - Adjust dropdown component based on these newly added components - Add popper-trigger component for placing the trigger - TODO: Finish current dropdown component, and all component's tests plus documents * Refactor popper * Complete organizing popper * Almost finish dropdown * Update popper tests * update only-child test * Finish focus trap component test * Finish tooltip content test * Finish tooltip trigger tests * Finish tooltip tests * finish tests for Collection and RovingFocusGroup * Fix test cases for timeselect & select & popover * Fix popover, popconfirm, menu bug and test cases * Fix select-v2 test error caused by updating popper * Fix date-picker test issue for updating popper * fix test cases * Fix eslint * Rebase dev & fix tests * Remove unused code
217 lines
6.2 KiB
TypeScript
217 lines
6.2 KiB
TypeScript
import { h, nextTick, inject } from 'vue'
|
|
import { mount } from '@vue/test-utils'
|
|
import { EVENT_CODE } from '@element-plus/utils/aria'
|
|
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>`,
|
|
}
|
|
|
|
const createComponent = (props = {}, items = null) =>
|
|
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(() => {
|
|
wrapper?.unmount()
|
|
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)
|
|
expect(document.activeElement).toBe(descendants.at(0).element)
|
|
})
|
|
})
|
|
|
|
describe('events', () => {
|
|
it('should be able to dispatch focus on mount event', async () => {
|
|
const focusOnMount = jest.fn()
|
|
wrapper = createComponent({
|
|
onMountOnFocus: focusOnMount,
|
|
})
|
|
await nextTick()
|
|
|
|
expect(focusOnMount).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should be able to dispatch focus on unmount', async () => {
|
|
const focusOnUnmount = jest.fn()
|
|
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()
|
|
expect(document.activeElement).toBe(items.at(0).element)
|
|
|
|
/**
|
|
* 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,
|
|
})
|
|
expect(document.activeElement).toBe(items.at(0).element)
|
|
;(items.at(2).element as HTMLElement).focus()
|
|
expect(document.activeElement).toBe(items.at(2).element)
|
|
|
|
await childComponent.trigger('keydown', {
|
|
key: EVENT_CODE.tab,
|
|
})
|
|
expect(document.activeElement).toBe(items.at(2).element)
|
|
|
|
// 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,
|
|
})
|
|
expect(document.activeElement).toBe(items.at(0).element)
|
|
|
|
await childComponent.trigger('keydown.shift', {
|
|
key: EVENT_CODE.tab,
|
|
})
|
|
expect(document.activeElement).toBe(items.at(2).element)
|
|
})
|
|
|
|
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()
|
|
expect(document.activeElement).toBe(items.at(0).element)
|
|
|
|
await focusComponent.trigger('keydown', {
|
|
key: EVENT_CODE.tab,
|
|
})
|
|
|
|
expect(document.activeElement).toBe(items.at(0).element)
|
|
})
|
|
|
|
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()
|
|
expect(document.activeElement).toBe(items.at(0).element)
|
|
|
|
await focusComponent.trigger('keydown.shift', {
|
|
key: EVENT_CODE.tab,
|
|
})
|
|
expect(document.activeElement).toBe(items.at(2).element)
|
|
|
|
const newFocusTrap = createComponent()
|
|
await nextTick()
|
|
expect(document.activeElement).toBe(
|
|
newFocusTrap.find(`.${childKls}`).element
|
|
)
|
|
|
|
await focusComponent.trigger('keydown', {
|
|
key: EVENT_CODE.tab,
|
|
})
|
|
expect(document.activeElement).not.toBe(items.at(0).element)
|
|
|
|
newFocusTrap.unmount()
|
|
expect(document.activeElement).toBe(items.at(2).element)
|
|
|
|
await focusComponent.trigger('keydown', {
|
|
key: EVENT_CODE.tab,
|
|
})
|
|
expect(document.activeElement).toBe(items.at(0).element)
|
|
})
|
|
})
|
|
})
|