UNPKG

vuetify

Version:

Vue Material Component Framework

879 lines (713 loc) 21.7 kB
import Vue from 'vue' import { mount, Wrapper, MountOptions, } from '@vue/test-utils' import VTreeview from '../VTreeview' import { ExtractVue } from '../../../util/mixins' import { wait } from '../../../../test' const singleRootTwoChildren = [ { id: 0, name: 'Root', children: [{ id: 1, name: 'Child' }, { id: 2, name: 'Child 2' }] }, ] const threeLevels = [ { id: 0, name: 'Root', children: [{ id: 1, name: 'Child', children: [{ id: 2, name: 'Grandchild' }] }, { id: 3, name: 'Child' }] }, ] describe('VTreeView.ts', () => { // eslint-disable-line max-statements type Instance = ExtractVue<typeof VTreeview> let mountFunction: (options?: MountOptions<Instance>) => Wrapper<Instance> beforeEach(() => { mountFunction = (options?: MountOptions<Instance>) => { return mount(VTreeview, { // https://github.com/vuejs/vue-test-utils/issues/1130 sync: false, ...options, }) } }) it('should render items', async () => { const wrapper = mountFunction({ propsData: { items: singleRootTwoChildren, }, }) expect(wrapper.html()).toMatchSnapshot() }) it('should render items in dense mode', async () => { const wrapper = mountFunction({ propsData: { items: singleRootTwoChildren, dense: true, }, }) expect(wrapper.html()).toMatchSnapshot() }) // TODO: this fails without sync, nextTick doesn't help // https://github.com/vuejs/vue-test-utils/issues/1130 it.skip('should select all leaf nodes', async () => { const wrapper = mountFunction({ propsData: { items: threeLevels, selectable: true, }, }) const fn = jest.fn() wrapper.vm.$on('input', fn) wrapper.find('.v-treeview-node__checkbox').trigger('click') await wrapper.vm.$nextTick() expect(fn).toHaveBeenCalledTimes(1) expect(fn).toHaveBeenCalledWith([3, 2]) expect(wrapper.html()).toMatchSnapshot() }) // TODO: this fails without sync, nextTick doesn't help // https://github.com/vuejs/vue-test-utils/issues/1130 it.skip('should select only leaf nodes', async () => { const wrapper = mountFunction({ propsData: { items: threeLevels, selectable: true, }, }) const fn = jest.fn() wrapper.vm.$on('input', fn) wrapper.find('.v-treeview-node__toggle').trigger('click') await wrapper.vm.$nextTick() wrapper.findAll('.v-treeview-node__checkbox').at(2).trigger('click') await wrapper.vm.$nextTick() expect(fn).toHaveBeenCalledTimes(1) expect(fn).toHaveBeenCalledWith([3]) expect(wrapper.html()).toMatchSnapshot() }) // TODO: this fails without sync, nextTick doesn't help // https://github.com/vuejs/vue-test-utils/issues/1130 it.skip('should select only root node', async () => { const wrapper = mountFunction({ propsData: { items: threeLevels, selectable: true, selectionType: 'independent', }, }) const fn = jest.fn() wrapper.vm.$on('input', fn) wrapper.find('.v-treeview-node__checkbox').trigger('click') await wrapper.vm.$nextTick() expect(fn).toHaveBeenCalledTimes(1) expect(fn).toHaveBeenCalledWith([0]) expect(wrapper.html()).toMatchSnapshot() }) // TODO: fails with TS 3.9 it.skip('should load children when expanding', async () => { const loadChildren = item => { item.children.push({ id: 1, name: 'Child' }) } const wrapper = mountFunction({ propsData: { items: [{ id: 0, name: 'Root', children: [] }], loadChildren, }, }) expect(wrapper.html()).toMatchSnapshot() wrapper.find('.v-treeview-node__toggle').trigger('click') await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() expect(`[Vue warn]: Error in created hook: "TypeError: Cannot set property 'vnode' of undefined"`).toHaveBeenWarned() expect(`TypeError: Cannot set property 'vnode' of undefined`).toHaveBeenWarned() }) it('should load children when selecting, but not render', async () => { const loadChildren = item => { item.children = [{ id: 1, name: 'Child' }] } const wrapper = mountFunction({ propsData: { items: [{ id: 0, name: 'Root', children: [] }], selectable: true, loadChildren, }, }) const fn = jest.fn() wrapper.vm.$on('input', fn) expect(wrapper.html()).toMatchSnapshot() wrapper.find('.v-treeview-node__checkbox').trigger('click') await wait() expect(fn).toHaveBeenCalledTimes(1) expect(fn).toHaveBeenCalledWith([0]) expect(wrapper.html()).toMatchSnapshot() }) it('should emit active node when clicking on it', async () => { const wrapper = mountFunction({ propsData: { items: [{ id: 0, name: 'Root' }, { id: 1, name: 'Root' }], activatable: true, }, }) const fn = jest.fn() wrapper.vm.$on('update:active', fn) wrapper.find('.v-treeview-node__root').trigger('click') await wrapper.vm.$nextTick() expect(fn).toHaveBeenCalledTimes(1) expect(fn).toHaveBeenCalledWith([0]) wrapper.find('.v-treeview-node__root').trigger('click') await wrapper.vm.$nextTick() expect(fn).toHaveBeenCalledWith([]) }) it('should allow multiple active nodes with prop multipleActive', async () => { const wrapper = mountFunction({ propsData: { items: [{ id: 0, name: 'Root' }, { id: 1, name: 'Root' }], multipleActive: true, activatable: true, }, }) const fn = jest.fn() wrapper.vm.$on('update:active', fn) wrapper.findAll('.v-treeview-node__root').wrappers.forEach(vm => vm.trigger('click')) await wrapper.vm.$nextTick() expect(fn).toHaveBeenCalledTimes(2) expect(fn).toHaveBeenLastCalledWith([0, 1]) }) // TODO: fails with TS 3.9 it.skip('should update selection when selected prop changes', async () => { const wrapper = mountFunction({ propsData: { items: [{ id: 0, name: 'Root', children: [{ id: 1, name: 'Child' }] }], value: [], selectable: true, }, }) expect(wrapper.html()).toMatchSnapshot() wrapper.find('.v-treeview-node__toggle').trigger('click') wrapper.setProps({ value: [1] }) await wrapper.vm.$nextTick() expect(wrapper.findAll('.v-treeview-node')).toHaveLength(2) expect(wrapper.findAll('.v-treeview-node--selected')).toHaveLength(2) expect(wrapper.html()).toMatchSnapshot() wrapper.setProps({ value: [] }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() }) it('should open all children when using open-all prop', async () => { const wrapper = mountFunction({ propsData: { items: threeLevels, openAll: true, }, }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() }) it('should open/close all children when using updateAll', async () => { const wrapper = mountFunction({ propsData: { items: threeLevels, }, }) const updateOpen = jest.fn() wrapper.vm.$on('update:open', updateOpen) wrapper.vm.updateAll(true) expect(updateOpen).toHaveBeenCalledTimes(1) expect(updateOpen).toHaveBeenCalledWith([0, 1]) wrapper.vm.updateAll(false) expect(updateOpen).toHaveBeenCalledTimes(2) expect(updateOpen).toHaveBeenCalledWith([]) }) it('should react to open changes', async () => { const wrapper = mountFunction({ propsData: { items: threeLevels, open: [1], }, }) const fn = jest.fn() wrapper.vm.$on('update:open', fn) wrapper.setProps({ open: [0, 1] }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() wrapper.setProps({ open: [0] }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() wrapper.setProps({ open: [0, 1] }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() expect(fn).toHaveBeenCalledWith([0, 1]) // Should not update open values that do not exist in the tree wrapper.setProps({ open: [7] }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() expect(fn).toHaveBeenCalledWith([]) }) it('should update selected and active on created', async () => { const wrapper = mountFunction({ propsData: { items: threeLevels, active: [2], value: [1], }, }) // TODO: I can not find away in avoriaz // to catch events being emitted from a // lifecycle hook. We should not assert // internal state. expect([...wrapper.vm.activeCache]).toEqual([2]) expect([...wrapper.vm.selectedCache]).toEqual([2]) }) it('should react to changes for active items', async () => { const wrapper = mountFunction({ propsData: { items: threeLevels, active: [2], }, }) const active = jest.fn() wrapper.vm.$on('update:active', active) wrapper.setProps({ active: [] }) await wrapper.vm.$nextTick() expect(active).toHaveBeenCalledWith([]) // without multiple-active, it will use last value in array wrapper.setProps({ active: [1, 3] }) await wrapper.vm.$nextTick() expect(active).toHaveBeenCalledWith([3]) wrapper.setProps({ multipleActive: true, active: [1, 3] }) await wrapper.vm.$nextTick() expect(active).toHaveBeenCalledWith([1, 3]) // 7 does not exist, we get nothing back wrapper.setProps({ active: [7] }) await wrapper.vm.$nextTick() expect(active).toHaveBeenCalledWith([]) wrapper.setProps({ active: [0], items: singleRootTwoChildren }) await wrapper.vm.$nextTick() expect(active).toHaveBeenCalledWith([0]) }) it('should react to changes for selected items', async () => { const wrapper = mountFunction({ propsData: { items: threeLevels, value: [2], }, }) const value = jest.fn() wrapper.vm.$on('input', value) wrapper.setProps({ value: [] }) await wrapper.vm.$nextTick() expect(value).toHaveBeenCalledWith([]) wrapper.setProps({ value: [3] }) await wrapper.vm.$nextTick() expect(value).toHaveBeenCalledWith([3]) // 7 does not exist, we get nothing back wrapper.setProps({ value: [7] }) await wrapper.vm.$nextTick() expect(value).toHaveBeenCalledWith([]) wrapper.setProps({ value: [0] }) await wrapper.vm.$nextTick() expect(value).toHaveBeenLastCalledWith([3, 2]) }) it('should accept string value for id', async () => { const wrapper = mountFunction({ propsData: { itemKey: 'name' }, }) wrapper.setProps({ items: [{ name: 'Foobar' }] }) await wrapper.vm.$nextTick() expect(wrapper.vm.nodes.Foobar).toBeTruthy() wrapper.setProps({ value: ['Foobar'] }) await wrapper.vm.$nextTick() }) it('should warn developer when using non-scoped slots', () => { mountFunction({ slots: { prepend: [{ render: h => h('div') }], append: [{ render: h => h('div') }], }, }) expect('[Vuetify] The prepend and append slots require a slot-scope attribute').toHaveBeenTipped() }) it('should not show expand icon when children is empty', () => { const wrapper = mountFunction({ propsData: { items: [ { text: 'root', children: [], }, ], }, }) expect(wrapper.html()).toMatchSnapshot() expect(wrapper.findAll('.v-treeview-node__toggle')).toHaveLength(0) }) it('should show expand icon when children is empty and load-children prop used', () => { const wrapper = mountFunction({ propsData: { loadChildren: () => {}, items: [ { text: 'root', children: [], }, ], }, }) expect(wrapper.html()).toMatchSnapshot() expect(wrapper.findAll('.v-treeview-node__toggle')).toHaveLength(1) }) it('should recalculate tree when loading async children using custom key', async () => { const items = [ { id: 1, name: 'One', __children: [], }, ] const wrapper = mountFunction({ propsData: { items, itemChildren: '__children', loadChildren: () => { const newItems = [...items] items[0].__children.push({ id: 2, name: 'Two' }) wrapper.setProps({ items: newItems, }) }, }, }) wrapper.find('.v-treeview-node__toggle').trigger('click') await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() }) it('should remove old nodes', async () => { const wrapper = mountFunction({ propsData: { items: [ { id: 1, name: 'one', }, { id: 2, name: 'two', }, ], }, }) expect(wrapper.html()).toMatchSnapshot() wrapper.setProps({ items: [ { id: 1, name: 'one', }, ], }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() wrapper.setProps({ items: [ { id: 1, name: 'one', }, { id: 3, name: 'three', }, ], }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() expect(Object.keys(wrapper.vm.nodes)).toHaveLength(2) }) it('should filter items', async () => { const wrapper = mountFunction({ propsData: { items: [ { id: 1, name: 'one', }, { id: 2, name: 'two', }, ], }, }) expect(wrapper.html()).toMatchSnapshot() wrapper.setProps({ search: 'two', }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() }) it('should filter items using custom item filter', async () => { const wrapper = mountFunction({ propsData: { filter: (item, search, textKey) => item.special === search, items: [ { id: 1, name: 'one', special: 'yes', }, { id: 2, name: 'two', special: 'no', }, ], search: 'NO', }, }) expect(wrapper.html()).toMatchSnapshot() wrapper.setProps({ search: 'yes', }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() }) // TODO: fails with TS 3.9 it.skip('should emit objects when return-object prop is used', async () => { const items = [{ id: 0, name: 'Root', children: [{ id: 1, name: 'Child' }] }] const wrapper = mountFunction({ propsData: { items, activatable: true, selectable: true, returnObject: true, }, }) const active = jest.fn() wrapper.vm.$on('update:active', active) const selected = jest.fn() wrapper.vm.$on('input', selected) const open = jest.fn() wrapper.vm.$on('update:open', open) wrapper.find('.v-treeview-node__root').trigger('click') await wrapper.vm.$nextTick() expect(active).toHaveBeenCalledTimes(1) expect(active).toHaveBeenCalledWith([items[0]]) wrapper.find('.v-treeview-node__checkbox').trigger('click') await wrapper.vm.$nextTick() expect(selected).toHaveBeenCalledTimes(1) expect(selected).toHaveBeenCalledWith([items[0].children[0]]) wrapper.find('.v-treeview-node__toggle').trigger('click') await wrapper.vm.$nextTick() expect(open).toHaveBeenCalledTimes(1) expect(open).toHaveBeenCalledWith([items[0]]) }) it('should handle replacing items with new array of equal length', async () => { const wrapper = mountFunction({ propsData: { items: [ { id: 1, name: 'one', }, { id: 2, name: 'two', }, ], }, }) expect(wrapper.html()).toMatchSnapshot() wrapper.setProps({ items: [ { id: 1, name: 'one', }, { id: 3, name: 'three', }, ], }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() }) // https://github.com/vuetifyjs/vuetify/issues/8709 it('should handle initial active/open/selected values when using return-object prop', async () => { const one = { id: '1', name: 'One' } const three = { id: '3', name: 'Three' } const two = { id: '2', name: 'Two', children: [three] } const wrapper = mountFunction({ propsData: { returnObject: true, selectable: true, activatable: true, items: [one, two], value: [one], open: [two], active: [three], }, }) await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() }) it('should set correct state when updating nodes', async () => { const children = [ { id: 2, name: 'Bar' }, { id: 3, name: 'Fizz' }, { id: 4, name: 'Buzz' }, ] const item = { id: 1, name: 'Foo', } const wrapper = mountFunction({ propsData: { items: [{ ...item, children }], value: [4], }, }) wrapper.setProps({ items: [{ ...item, children: [ ...children, { id: 5, name: 'FizzBuzz' }, ], }], }) await wrapper.vm.$nextTick() expect(wrapper.vm.nodes['5'].isIndeterminate).toBeUndefined() }) // https://github.com/vuetifyjs/vuetify/issues/8720 it('should set correct selection when updating items', async () => { const items = [{ id: 1, name: 'Foo', children: [ { id: 2, name: 'Bar' }, { id: 3, name: 'Fizz' }, { id: 4, name: 'Buzz' }, ], }] const input = jest.fn() const wrapper = mountFunction({ propsData: { items, value: [2, 3, 4], selectionType: 'leaf', selectable: true, }, listeners: { input, }, }) wrapper.setProps({ items: [{ id: 1, name: 'Foo', children: [ { id: 2, name: 'Bar' }, { id: 3, name: 'Fizz' }, { id: 4, name: 'Buzz' }, ], }], }) await wrapper.vm.$nextTick() expect(input).not.toHaveBeenCalled() }) // https://github.com/vuetifyjs/vuetify/issues/8244 // TODO: fails with TS 3.9 it.skip('should not touch disabled items when selecting', async () => { const items = [{ id: 1, name: 'Foo', children: [ { id: 2, name: 'Bar', disabled: true }, { id: 3, name: 'Fizz' }, { id: 4, name: 'Buzz' }, ], }] const input = jest.fn() const wrapper = mountFunction({ propsData: { items, value: [], selectionType: 'leaf', selectable: true, }, listeners: { input, }, }) wrapper.find('.v-treeview-node__checkbox').trigger('click') await wrapper.vm.$nextTick() expect(input).toHaveBeenLastCalledWith([3, 4]) wrapper.setProps({ value: [2, 3, 4], items: [{ id: 1, name: 'Foo', children: [ { id: 2, name: 'Bar', disabled: true }, { id: 3, name: 'Fizz' }, { id: 4, name: 'Buzz' }, ], }], }) await wrapper.vm.$nextTick() wrapper.find('.v-treeview-node__checkbox').trigger('click') await wrapper.vm.$nextTick() expect(input).toHaveBeenLastCalledWith([2]) }) // https://github.com/vuetifyjs/vuetify/issues/10990 // https://github.com/vuetifyjs/vuetify/issues/10770 // TODO: fails with TS 3.9 it.skip('should not disable children of disabled parent when in independent mode', async () => { const items = [{ id: 1, name: 'Foo', disabled: true, children: [ { id: 2, name: 'Bar' }, { id: 3, name: 'Fizz', disabled: true }, { id: 4, name: 'Buzz' }, ], }] const input = jest.fn() const wrapper = mountFunction({ propsData: { items, value: [], open: [1], selectionType: 'independent', selectable: true, }, listeners: { input, }, }) await wrapper.vm.$nextTick() wrapper.findAll('.v-treeview-node__checkbox').at(1).trigger('click') await wrapper.vm.$nextTick() expect(input).toHaveBeenLastCalledWith([2]) wrapper.findAll('.v-treeview-node__checkbox').at(2).trigger('click') await wrapper.vm.$nextTick() expect(input).toHaveBeenCalledTimes(1) }) // https://github.com/vuetifyjs/vuetify/issues/9693 // TODO: fails with TS 3.9 it.skip('should emit opened node when using open-on-click and load-children', async () => { const open = jest.fn() const wrapper = mountFunction({ propsData: { items: [{ id: 0, name: 'Root', children: [] }], loadChildren: () => wrapper.setProps({ items: [{ id: 0, name: 'Root', children: [{ id: 1, name: 'Child' }] }], }), openOnClick: true, }, listeners: { 'update:open': open, }, }) expect(wrapper.html()).toMatchSnapshot() wrapper.find('.v-treeview-node__root').trigger('click') await wrapper.vm.$nextTick() expect(wrapper.html()).toMatchSnapshot() expect(open).toHaveBeenLastCalledWith([0]) }) })