123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- import { nextTick, reactive, ref } from 'vue'
- import { mount } from '@vue/test-utils'
- import { afterEach, describe, expect, it, test, vi } from 'vitest'
- import { EVENT_CODE } from '@element-plus/constants'
- import triggerEvent from '@element-plus/test-utils/trigger-event'
- import { ArrowDown, Check, CircleClose } from '@element-plus/icons-vue'
- import { usePopperContainerId } from '@element-plus/hooks'
- import { hasClass } from '@element-plus/utils'
- import ElForm, { ElFormItem } from '@element-plus/components/form'
- import Cascader from '../src/cascader.vue'
- import type { VNode } from 'vue'
- vi.mock('lodash-unified', async () => {
- return {
- ...((await vi.importActual('lodash-unified')) as Record<string, any>),
- debounce: vi.fn((fn) => {
- fn.cancel = vi.fn()
- fn.flush = vi.fn()
- return fn
- }),
- }
- })
- const OPTIONS = [
- {
- value: 'zhejiang',
- label: 'Zhejiang',
- children: [
- {
- value: 'hangzhou',
- label: 'Hangzhou',
- },
- {
- value: 'ningbo',
- label: 'Ningbo',
- },
- {
- value: 'wenzhou',
- label: 'Wenzhou',
- },
- ],
- },
- ]
- const AXIOM = 'Rem is the best girl'
- const TRIGGER = '.el-cascader'
- const NODE = '.el-cascader-node'
- const TAG = '.el-tag'
- const SUGGESTION_ITEM = '.el-cascader__suggestion-item'
- const SUGGESTION_PANEL = '.el-cascader__suggestion-panel'
- const DROPDOWN = '.el-cascader__dropdown'
- const _mount = (render: () => VNode) =>
- mount(render, {
- attachTo: document.body,
- })
- afterEach(() => {
- document.body.innerHTML = ''
- })
- describe('Cascader.vue', () => {
- test('toggle popper visible', async () => {
- const handleVisibleChange = vi.fn()
- const wrapper = _mount(() => (
- <Cascader onVisibleChange={handleVisibleChange} />
- ))
- const trigger = wrapper.find(TRIGGER)
- const dropdown = wrapper.findComponent(ArrowDown).element as HTMLDivElement
- await trigger.trigger('click')
- expect(dropdown.style.display).not.toBe('none')
- expect(handleVisibleChange).toBeCalledWith(true)
- await trigger.trigger('click')
- expect(handleVisibleChange).toBeCalledWith(false)
- await trigger.trigger('click')
- document.body.click()
- expect(handleVisibleChange).toBeCalledWith(false)
- })
- test('expand and check', async () => {
- const handleChange = vi.fn()
- const handleExpandChange = vi.fn()
- const value = ref([])
- const wrapper = _mount(() => (
- <Cascader
- v-model={value.value}
- options={OPTIONS}
- onChange={handleChange}
- onExpandChange={handleExpandChange}
- />
- ))
- const trigger = wrapper.find(TRIGGER)
- await trigger.trigger('click')
- ;(document.querySelector(NODE) as HTMLElement).click()
- await nextTick()
- expect(handleExpandChange).toBeCalledWith(['zhejiang'])
- ;(document.querySelectorAll(NODE)[1] as HTMLElement).click()
- await nextTick()
- expect(handleChange).toBeCalledWith(['zhejiang', 'hangzhou'])
- expect(value.value).toEqual(['zhejiang', 'hangzhou'])
- expect(wrapper.find('input').element.value).toBe('Zhejiang / Hangzhou')
- })
- test('with default value', async () => {
- const value = ref(['zhejiang', 'hangzhou'])
- const wrapper = _mount(() => (
- <Cascader v-model={value.value} options={OPTIONS} />
- ))
- await nextTick()
- expect(wrapper.find('input').element.value).toBe('Zhejiang / Hangzhou')
- value.value = ['zhejiang', 'ningbo']
- await nextTick()
- expect(wrapper.find('input').element.value).toBe('Zhejiang / Ningbo')
- })
- test('options change', async () => {
- const value = ref(['zhejiang', 'hangzhou'])
- const options = ref(OPTIONS)
- const wrapper = _mount(() => (
- <Cascader v-model={value.value} options={options.value} />
- ))
- options.value = []
- await nextTick()
- expect(wrapper.find('input').element.value).toBe('')
- })
- test('disabled', async () => {
- const handleVisibleChange = vi.fn()
- const wrapper = _mount(() => (
- <Cascader disabled onVisibleChange={handleVisibleChange} />
- ))
- await wrapper.find(TRIGGER).trigger('click')
- expect(handleVisibleChange).not.toBeCalled()
- expect(wrapper.find('input[disabled]').exists()).toBe(true)
- })
- test('custom placeholder', async () => {
- const wrapper = _mount(() => <Cascader placeholder={AXIOM} />)
- expect(wrapper.find('input').attributes().placeholder).toBe(AXIOM)
- })
- test('clearable', async () => {
- const wrapper = _mount(() => (
- <Cascader
- modelValue={['zhejiang', 'hangzhou']}
- clearable
- options={OPTIONS}
- />
- ))
- const trigger = wrapper.find(TRIGGER)
- expect(wrapper.findComponent(ArrowDown).exists()).toBe(true)
- await trigger.trigger('mouseenter')
- expect(wrapper.findComponent(ArrowDown).exists()).toBe(false)
- await wrapper.findComponent(CircleClose).trigger('click')
- expect(wrapper.find('input').element.value).toBe('')
- expect(
- wrapper.findComponent(Cascader).vm.getCheckedNodes(false)?.length
- ).toBe(0)
- await trigger.trigger('mouseleave')
- await trigger.trigger('mouseenter')
- await expect(wrapper.findComponent(CircleClose).exists()).toBe(false)
- })
- test('show last level label', async () => {
- const wrapper = _mount(() => (
- <Cascader
- modelValue={['zhejiang', 'hangzhou']}
- showAllLevels={false}
- options={OPTIONS}
- />
- ))
- await nextTick()
- expect(wrapper.find('input').element.value).toBe('Hangzhou')
- })
- test('multiple mode', async () => {
- const value = ref([
- ['zhejiang', 'hangzhou'],
- ['zhejiang', 'ningbo'],
- ])
- const props = { multiple: true }
- const wrapper = _mount(() => (
- <Cascader v-model={value.value} props={props} options={OPTIONS} />
- ))
- await nextTick()
- const tags = wrapper.findAll(TAG)
- const [firstTag, secondTag] = tags
- expect(tags.length).toBe(2)
- expect(firstTag.text()).toBe('Zhejiang / Hangzhou')
- expect(secondTag.text()).toBe('Zhejiang / Ningbo')
- await firstTag.find('.el-tag__close').trigger('click')
- expect(wrapper.findAll(TAG).length).toBe(1)
- expect(value.value).toEqual([['zhejiang', 'ningbo']])
- })
- test('collapse tags', async () => {
- const props = { multiple: true }
- const wrapper = _mount(() => (
- <Cascader
- modelValue={[
- ['zhejiang', 'hangzhou'],
- ['zhejiang', 'ningbo'],
- ['zhejiang', 'wenzhou'],
- ]}
- collapseTags
- props={props}
- options={OPTIONS}
- />
- ))
- await nextTick()
- const tags = wrapper.findAll(TAG).filter((item) => {
- return !hasClass(item.element, 'in-tooltip')
- })
- expect(tags[0].text()).toBe('Zhejiang / Hangzhou')
- expect(tags.length).toBe(2)
- })
- test('collapse tags tooltip', async () => {
- const props = { multiple: true }
- _mount(() => (
- <Cascader
- modelValue={[
- ['zhejiang', 'hangzhou'],
- ['zhejiang', 'ningbo'],
- ['zhejiang', 'wenzhou'],
- ]}
- collapseTags
- collapseTagsTooltip
- props={props}
- options={OPTIONS}
- />
- ))
- await nextTick()
- const tooltipTags = document.querySelectorAll(
- `.el-cascader__collapse-tags ${TAG}`
- )
- expect(tooltipTags.length).toBe(2)
- expect(tooltipTags[0].textContent).toBe('Zhejiang / Ningbo')
- expect(tooltipTags[1].textContent).toBe('Zhejiang / Wenzhou')
- })
- test('tag type', async () => {
- const props = { multiple: true }
- const wrapper = _mount(() => (
- <Cascader
- modelValue={[['zhejiang', 'hangzhou']]}
- tagType="success"
- props={props}
- options={OPTIONS}
- />
- ))
- await nextTick()
- expect(wrapper.find('.el-tag').classes()).toContain('el-tag--success')
- })
- test('filterable', async () => {
- const value = ref([])
- const wrapper = _mount(() => (
- <Cascader v-model={value.value} filterable options={OPTIONS} />
- ))
- const input = wrapper.find('input')
- input.element.value = 'Ni'
- await input.trigger('compositionstart')
- await input.trigger('input')
- input.element.value = 'Ha'
- await input.trigger('compositionupdate')
- await input.trigger('input')
- await input.trigger('compositionend')
- const suggestions = document.querySelectorAll(
- SUGGESTION_ITEM
- ) as NodeListOf<HTMLElement>
- const hzSuggestion = suggestions[0]
- expect(suggestions.length).toBe(1)
- expect(hzSuggestion.textContent).toBe('Zhejiang / Hangzhou')
- hzSuggestion.click()
- await nextTick()
- expect(wrapper.findComponent(Check).exists()).toBeTruthy()
- expect(value.value).toEqual(['zhejiang', 'hangzhou'])
- hzSuggestion.click()
- await nextTick()
- expect(value.value).toEqual(['zhejiang', 'hangzhou'])
- })
- test('filterable in multiple mode', async () => {
- const value = ref([])
- const props = { multiple: true }
- const wrapper = _mount(() => (
- <Cascader
- v-model={value.value}
- props={props}
- filterable
- options={OPTIONS}
- />
- ))
- const input = wrapper.find('.el-cascader__search-input')
- ;(input.element as HTMLInputElement).value = 'Ha'
- await input.trigger('input')
- await nextTick()
- const hzSuggestion = document.querySelector(SUGGESTION_ITEM) as HTMLElement
- hzSuggestion.click()
- await nextTick()
- expect(value.value).toEqual([['zhejiang', 'hangzhou']])
- hzSuggestion.click()
- await nextTick()
- expect(value.value).toEqual([])
- })
- test('filter method', async () => {
- const filterMethod = vi.fn((node, keyword) => {
- const { text, value } = node
- return text.includes(keyword) || value.includes(keyword)
- })
- const wrapper = _mount(() => (
- <Cascader filterMethod={filterMethod} filterable options={OPTIONS} />
- ))
- const input = wrapper.find('input')
- input.element.value = 'ha'
- await input.trigger('input')
- const hzSuggestion = document.querySelector(SUGGESTION_ITEM) as HTMLElement
- expect(filterMethod).toBeCalled()
- expect(hzSuggestion.textContent).toBe('Zhejiang / Hangzhou')
- })
- test('filterable keyboard selection', async () => {
- const value = ref([])
- const wrapper = _mount(() => (
- <Cascader v-model={value.value} filterable options={OPTIONS} />
- ))
- const input = wrapper.find('input')
- const dropdown = document.querySelector(DROPDOWN)!
- input.element.value = 'h'
- await input.trigger('input')
- const suggestionsPanel = document.querySelector(
- SUGGESTION_PANEL
- ) as HTMLDivElement
- const suggestions = dropdown.querySelectorAll(
- SUGGESTION_ITEM
- ) as NodeListOf<HTMLElement>
- const hzSuggestion = suggestions[0]
- triggerEvent(suggestionsPanel, 'keydown', EVENT_CODE.down)
- expect(document.activeElement!.textContent).toBe('Zhejiang / Hangzhou')
- triggerEvent(hzSuggestion, 'keydown', EVENT_CODE.down)
- expect(document.activeElement!.textContent).toBe('Zhejiang / Ningbo')
- triggerEvent(hzSuggestion, 'keydown', EVENT_CODE.enter)
- await nextTick()
- expect(value.value).toEqual(['zhejiang', 'hangzhou'])
- })
- describe('teleported API', () => {
- it('should mount on popper container', async () => {
- expect(document.body.innerHTML).toBe('')
- const value = ref([])
- _mount(() => (
- <Cascader v-model={value.value} filterable options={OPTIONS} />
- ))
- await nextTick()
- const { selector } = usePopperContainerId()
- expect(document.body.querySelector(selector.value)!.innerHTML).not.toBe(
- ''
- )
- })
- it('should not mount on the popper container', async () => {
- expect(document.body.innerHTML).toBe('')
- const value = ref([])
- _mount(() => (
- <Cascader
- v-model={value.value}
- filterable
- teleported={false}
- options={OPTIONS}
- />
- ))
- await nextTick()
- const { selector } = usePopperContainerId()
- expect(document.body.querySelector(selector.value)!.innerHTML).toBe('')
- })
- })
- test('placeholder disappear when resetForm', async () => {
- const model = reactive({
- name: new Array<string>(),
- })
- const wrapper = _mount(() => (
- <ElForm model={model}>
- <ElFormItem label="Activity name" prop="name">
- <Cascader
- v-model={model.name}
- options={OPTIONS}
- filterable
- placeholder={AXIOM}
- />
- </ElFormItem>
- </ElForm>
- ))
- model.name = ['zhejiang', 'hangzhou']
- await nextTick()
- expect(wrapper.find('input').element.placeholder).toBe('')
- wrapper.findComponent(ElForm).vm.$.exposed!.resetFields()
- await nextTick()
- expect(wrapper.find('input').element.placeholder).toBe(AXIOM)
- })
- test('should be able to trigger togglePopperVisible outside the component', async () => {
- const cascaderRef = ref()
- const clickFn = () => {
- cascaderRef.value.togglePopperVisible()
- }
- const wrapper = _mount(() => (
- <div>
- <Cascader ref="cascaderRef" options={OPTIONS} />
- <button onClick={clickFn} />
- </div>
- ))
- const dropdown = wrapper.findComponent(ArrowDown).element as HTMLDivElement
- expect(dropdown.style.display).not.toBe('none')
- const button = wrapper.find('button')
- await button.trigger('click')
- await nextTick()
- expect(dropdown?.style.display).not.toBe('none')
- })
- })
|